style: 移动端响应式优化:MCPPlugins页面完整重构、多处Modal弹窗适配、项目卡片单列显示

This commit is contained in:
xiamuceer-j
2026-01-14 19:48:18 +08:00
parent fb16cc4072
commit dc3dbaaf2c
8 changed files with 188 additions and 99 deletions
+28 -24
View File
@@ -1230,16 +1230,16 @@ export default function Chapters() {
<span style={{ wordBreak: 'break-word' }}>{chapter.chapter_number}</span>
</Space>
),
width: isMobile ? '95%' : 800,
width: isMobile ? 'calc(100vw - 32px)' : 800,
centered: true,
style: isMobile ? {
top: 20,
maxWidth: 'calc(100vw - 16px)',
margin: '0 8px'
maxWidth: 'calc(100vw - 32px)',
margin: '0 auto',
padding: '0 16px'
} : undefined,
styles: {
body: {
maxHeight: isMobile ? 'calc(100vh - 150px)' : 'calc(80vh - 110px)',
maxHeight: isMobile ? 'calc(100vh - 200px)' : 'calc(80vh - 110px)',
overflowY: 'auto'
}
},
@@ -2006,17 +2006,16 @@ export default function Chapters() {
open={isModalOpen}
onCancel={() => setIsModalOpen(false)}
footer={null}
centered={!isMobile}
width={isMobile ? 'calc(100% - 32px)' : 520}
centered
width={isMobile ? 'calc(100vw - 32px)' : 520}
style={isMobile ? {
top: 20,
paddingBottom: 0,
maxWidth: 'calc(100vw - 32px)',
margin: '0 16px'
margin: '0 auto',
padding: '0 16px'
} : undefined}
styles={{
body: {
maxHeight: isMobile ? 'calc(100vh - 150px)' : 'calc(80vh - 110px)',
maxHeight: isMobile ? 'calc(100vh - 200px)' : 'calc(80vh - 110px)',
overflowY: 'auto'
}
}}
@@ -2082,17 +2081,16 @@ export default function Chapters() {
closable={!isGenerating}
maskClosable={!isGenerating}
keyboard={!isGenerating}
width={isMobile ? 'calc(100% - 32px)' : '85%'}
centered={!isMobile}
width={isMobile ? 'calc(100vw - 32px)' : '85%'}
centered
style={isMobile ? {
top: 20,
paddingBottom: 0,
maxWidth: 'calc(100vw - 32px)',
margin: '0 16px'
margin: '0 auto',
padding: '0 16px'
} : undefined}
styles={{
body: {
maxHeight: isMobile ? 'calc(100vh - 150px)' : 'calc(100vh - 110px)',
maxHeight: isMobile ? 'calc(100vh - 200px)' : 'calc(100vh - 110px)',
overflowY: 'auto',
padding: isMobile ? '16px 12px' : '8px'
}
@@ -2366,6 +2364,7 @@ export default function Chapters() {
content: '批量生成正在进行中,确定要取消吗?',
okText: '确定取消',
cancelText: '继续生成',
centered: true,
onOk: () => {
handleCancelBatchGenerate();
setBatchGenerateVisible(false);
@@ -2376,7 +2375,7 @@ export default function Chapters() {
}
}}
footer={!batchGenerating ? (
<Space style={{ width: '100%', justifyContent: 'flex-end' }}>
<Space style={{ width: '100%', justifyContent: 'flex-end', flexWrap: 'wrap' }}>
<Button onClick={() => setBatchGenerateVisible(false)}>
</Button>
@@ -2385,13 +2384,18 @@ export default function Chapters() {
</Button>
</Space>
) : null}
width={700}
width={isMobile ? 'calc(100vw - 32px)' : 700}
centered
closable={!batchGenerating}
maskClosable={!batchGenerating}
style={isMobile ? {
maxWidth: 'calc(100vw - 32px)',
margin: '0 auto',
padding: '0 16px'
} : undefined}
styles={{
body: {
maxHeight: 'calc(100vh - 260px)',
maxHeight: isMobile ? 'calc(100vh - 200px)' : 'calc(100vh - 260px)',
overflowY: 'auto',
overflowX: 'hidden'
}
@@ -2419,7 +2423,7 @@ export default function Chapters() {
/>
{/* 第一行:起始章节 + 生成数量 */}
<div style={{ display: 'flex', gap: 16 }}>
<div style={{ display: 'flex', flexDirection: isMobile ? 'column' : 'row', gap: isMobile ? 0 : 16 }}>
<Form.Item
label="起始章节"
name="startChapterNumber"
@@ -2444,7 +2448,7 @@ export default function Chapters() {
rules={[{ required: true, message: '请选择' }]}
style={{ marginBottom: 12 }}
>
<Radio.Group buttonStyle="solid">
<Radio.Group buttonStyle="solid" size={isMobile ? 'small' : 'middle'}>
<Radio.Button value={5}>5</Radio.Button>
<Radio.Button value={10}>10</Radio.Button>
<Radio.Button value={15}>15</Radio.Button>
@@ -2454,7 +2458,7 @@ export default function Chapters() {
</div>
{/* 第二行:写作风格 + 目标字数 */}
<div style={{ display: 'flex', gap: 16 }}>
<div style={{ display: 'flex', flexDirection: isMobile ? 'column' : 'row', gap: isMobile ? 0 : 16 }}>
<Form.Item
label="写作风格"
name="styleId"
@@ -2494,7 +2498,7 @@ export default function Chapters() {
</div>
{/* 第三行:AI模型 + 同步分析 */}
<div style={{ display: 'flex', gap: 16 }}>
<div style={{ display: 'flex', flexDirection: isMobile ? 'column' : 'row', gap: isMobile ? 0 : 16 }}>
<Form.Item
label="AI模型"
tooltip="不选则使用默认模型"
+132 -57
View File
@@ -37,8 +37,17 @@ const { Paragraph, Text, Title } = Typography;
const { TextArea } = Input;
export default function MCPPluginsPage() {
const isMobile = window.innerWidth <= 768;
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
const [form] = Form.useForm();
// 响应式监听窗口大小变化
useEffect(() => {
const handleResize = () => {
setIsMobile(window.innerWidth <= 768);
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
const [modal, contextHolder] = Modal.useModal();
const [loading, setLoading] = useState(false);
const [plugins, setPlugins] = useState<MCPPlugin[]>([]);
@@ -591,10 +600,9 @@ export default function MCPPluginsPage() {
<div style={{
minHeight: '90vh',
background: 'linear-gradient(180deg, var(--color-bg-base) 0%, #EEF2F3 100%)',
padding: isMobile ? '20px 16px' : '24px 24px',
padding: isMobile ? '20px 16px 70px' : '24px 24px 70px',
display: 'flex',
flexDirection: 'column',
marginBottom: '55px',
}}>
<div style={{
maxWidth: 1400,
@@ -657,7 +665,7 @@ export default function MCPPluginsPage() {
</Col>
</Row>
<div style={{ marginTop: isMobile ? 16 : 24, display: 'flex', gap: 16, flexDirection: isMobile ? 'column' : 'row' }}>
<div style={{ marginTop: isMobile ? 16 : 24, display: 'flex', gap: isMobile ? 12 : 16, flexDirection: isMobile ? 'column' : 'row' }}>
<Card
variant="borderless"
style={{
@@ -668,27 +676,36 @@ export default function MCPPluginsPage() {
backdropFilter: 'blur(10px)',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.03)'
}}
styles={{ body: { padding: 20 } }}
styles={{ body: { padding: isMobile ? 14 : 20 } }}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Space align="start">
<div style={{
display: 'flex',
flexDirection: isMobile ? 'column' : 'row',
justifyContent: 'space-between',
alignItems: isMobile ? 'stretch' : 'center',
gap: isMobile ? 12 : 0
}}>
<Space align="start" style={{ flex: 1 }}>
<div style={{
width: 40, height: 40, borderRadius: '50%',
width: isMobile ? 36 : 40,
height: isMobile ? 36 : 40,
borderRadius: '50%',
background: modelSupportStatus === 'supported' ? 'var(--color-success-bg)' : modelSupportStatus === 'unsupported' ? 'var(--color-error-bg)' : 'var(--color-info-bg)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
border: `1px solid ${modelSupportStatus === 'supported' ? 'var(--color-success-border)' : modelSupportStatus === 'unsupported' ? 'var(--color-error-border)' : 'var(--color-info-border)'}`
border: `1px solid ${modelSupportStatus === 'supported' ? 'var(--color-success-border)' : modelSupportStatus === 'unsupported' ? 'var(--color-error-border)' : 'var(--color-info-border)'}`,
flexShrink: 0
}}>
{modelSupportStatus === 'supported' ? (
<CheckCircleOutlined style={{ fontSize: 20, color: 'var(--color-success)' }} />
<CheckCircleOutlined style={{ fontSize: isMobile ? 18 : 20, color: 'var(--color-success)' }} />
) : modelSupportStatus === 'unsupported' ? (
<CloseCircleOutlined style={{ fontSize: 20, color: 'var(--color-error)' }} />
<CloseCircleOutlined style={{ fontSize: isMobile ? 18 : 20, color: 'var(--color-error)' }} />
) : (
<QuestionCircleOutlined style={{ fontSize: 20, color: 'var(--color-info)' }} />
<QuestionCircleOutlined style={{ fontSize: isMobile ? 18 : 20, color: 'var(--color-info)' }} />
)}
</div>
<div>
<Text strong style={{ fontSize: 16, display: 'block', color: 'var(--color-text-primary)' }}></Text>
<Text type="secondary" style={{ fontSize: 13 }}>
<div style={{ flex: 1, minWidth: 0 }}>
<Text strong style={{ fontSize: isMobile ? 14 : 16, display: 'block', color: 'var(--color-text-primary)' }}></Text>
<Text type="secondary" style={{ fontSize: isMobile ? 12 : 13, display: 'block', lineHeight: 1.5 }}>
{modelSupportStatus === 'supported'
? '当前模型支持 Function Calling,可正常使用 MCP 插件'
: modelSupportStatus === 'unsupported'
@@ -702,7 +719,8 @@ export default function MCPPluginsPage() {
icon={<ApiOutlined />}
onClick={handleCheckFunctionCalling}
loading={checkingFunctionCalling}
style={{ borderRadius: 8 }}
style={{ borderRadius: 8, width: isMobile ? '100%' : 'auto' }}
size={isMobile ? 'middle' : 'middle'}
>
{modelSupportStatus === 'unknown' ? '开始检测' : '重新检测'}
</Button>
@@ -719,13 +737,13 @@ export default function MCPPluginsPage() {
backdropFilter: 'blur(10px)',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.03)'
}}
styles={{ body: { padding: 20 } }}
styles={{ body: { padding: isMobile ? 14 : 20 } }}
>
<Space align="start">
<InfoCircleOutlined style={{ fontSize: 20, color: 'var(--color-primary)', marginTop: 4 }} />
<div>
<Text strong style={{ fontSize: 16, display: 'block', color: 'var(--color-text-primary)', marginBottom: 4 }}> MCP </Text>
<Text style={{ fontSize: 13, display: 'block', color: 'var(--color-text-secondary)', lineHeight: 1.6 }}>
<InfoCircleOutlined style={{ fontSize: isMobile ? 18 : 20, color: 'var(--color-primary)', marginTop: 2, flexShrink: 0 }} />
<div style={{ flex: 1, minWidth: 0 }}>
<Text strong style={{ fontSize: isMobile ? 14 : 16, display: 'block', color: 'var(--color-text-primary)', marginBottom: 4 }}> MCP </Text>
<Text style={{ fontSize: isMobile ? 12 : 13, display: 'block', color: 'var(--color-text-secondary)', lineHeight: 1.6 }}>
MCP (Model Context Protocol) AI AI 访API
</Text>
</div>
@@ -769,7 +787,7 @@ export default function MCPPluginsPage() {
</Button>
</Empty>
) : (
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
<Space direction="vertical" size={isMobile ? 'small' : 'middle'} style={{ width: '100%' }}>
{plugins.map((plugin) => (
<Card
key={plugin.id}
@@ -778,30 +796,62 @@ export default function MCPPluginsPage() {
borderRadius: 8,
border: '1px solid #f0f0f0',
}}
styles={{ body: { padding: isMobile ? 12 : 16 } }}
>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'flex-start',
gap: '16px',
flexWrap: isMobile ? 'wrap' : 'nowrap',
flexDirection: 'column',
gap: isMobile ? 12 : 16,
}}
>
{/* 插件信息区域 */}
<div style={{ flex: 1, minWidth: 0 }}>
<Space direction="vertical" size="small" style={{ width: '100%' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' }}>
<Text strong style={{ fontSize: isMobile ? '14px' : '16px' }}>
{plugin.display_name || plugin.plugin_name}
</Text>
{getStatusTag(plugin)}
<Tag color={plugin.plugin_type === 'http' || plugin.plugin_type === 'streamable_http' || plugin.plugin_type === 'sse' ? 'blue' : 'cyan'}>
{/* 标题和状态标签 */}
<div style={{
display: 'flex',
alignItems: 'center',
gap: '6px',
flexWrap: 'wrap',
justifyContent: 'space-between'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', flexWrap: 'wrap', flex: 1 }}>
<Text strong style={{ fontSize: isMobile ? '14px' : '16px' }}>
{plugin.display_name || plugin.plugin_name}
</Text>
{getStatusTag(plugin)}
</div>
{/* 移动端:开关放在标题行右侧 */}
{isMobile && (
<Switch
title={modelSupportStatus !== 'supported' ? '请先完成模型能力检查' : (plugin.enabled ? '禁用插件' : '启用插件')}
checked={plugin.enabled}
onChange={(checked) => handleToggle(plugin, checked)}
disabled={modelSupportStatus !== 'supported'}
size="small"
checkedChildren="开"
unCheckedChildren="关"
style={{
flexShrink: 0,
height: 16,
minHeight: 16,
lineHeight: '16px'
}}
/>
)}
</div>
{/* 类型和分类标签 */}
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
<Tag color={plugin.plugin_type === 'http' || plugin.plugin_type === 'streamable_http' || plugin.plugin_type === 'sse' ? 'blue' : 'cyan'} style={{ fontSize: isMobile ? 11 : 12 }}>
{plugin.plugin_type?.toUpperCase() || 'UNKNOWN'}
</Tag>
{plugin.category && plugin.category !== 'general' && (
<Tag color="purple">{plugin.category}</Tag>
<Tag color="purple" style={{ fontSize: isMobile ? 11 : 12 }}>{plugin.category}</Tag>
)}
</div>
{plugin.description && (
<Paragraph
type="secondary"
@@ -817,8 +867,13 @@ export default function MCPPluginsPage() {
{/* 只显示有值的URL或命令,脱敏处理敏感信息 */}
{(plugin.plugin_type === 'http' || plugin.plugin_type === 'streamable_http' || plugin.plugin_type === 'sse') && plugin.server_url && (
<div style={{ fontSize: isMobile ? '11px' : '12px' }}>
<Text type="secondary" code>
<div style={{
fontSize: isMobile ? '11px' : '12px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}>
<Text type="secondary" code style={{ fontSize: 'inherit' }}>
{(() => {
// 脱敏处理:隐藏URL中的API Key
const url = plugin.server_url;
@@ -849,8 +904,13 @@ export default function MCPPluginsPage() {
)}
{plugin.plugin_type === 'stdio' && plugin.command && (
<div style={{ fontSize: isMobile ? '11px' : '12px' }}>
<Text type="secondary" code>
<div style={{
fontSize: isMobile ? '11px' : '12px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}>
<Text type="secondary" code style={{ fontSize: 'inherit' }}>
{plugin.command} {plugin.args?.join(' ')}
</Text>
</div>
@@ -865,20 +925,27 @@ export default function MCPPluginsPage() {
</Space>
</div>
<Space size="small" wrap>
<Switch
title={modelSupportStatus !== 'supported' ? '请先完成模型能力检查' : (plugin.enabled ? '禁用插件' : '启用插件')}
checked={plugin.enabled}
onChange={(checked) => handleToggle(plugin, checked)}
disabled={modelSupportStatus !== 'supported'}
size={isMobile ? 'small' : 'default'}
style={{
flexShrink: 0,
height: isMobile ? 16 : 22,
minHeight: isMobile ? 16 : 22,
lineHeight: isMobile ? '16px' : '22px'
}}
/>
{/* 操作按钮区域 */}
<div style={{
display: 'flex',
justifyContent: isMobile ? 'flex-end' : 'flex-start',
alignItems: 'center',
gap: isMobile ? 8 : 8,
flexWrap: 'wrap',
borderTop: isMobile ? '1px solid #f0f0f0' : 'none',
paddingTop: isMobile ? 12 : 0
}}>
{/* 桌面端显示开关 */}
{!isMobile && (
<Switch
title={modelSupportStatus !== 'supported' ? '请先完成模型能力检查' : (plugin.enabled ? '禁用插件' : '启用插件')}
checked={plugin.enabled}
onChange={(checked) => handleToggle(plugin, checked)}
disabled={modelSupportStatus !== 'supported'}
checkedChildren="开"
unCheckedChildren="关"
/>
)}
<Button
title={modelSupportStatus !== 'supported' ? '请先完成模型能力检查' : '测试连接'}
icon={<ThunderboltOutlined />}
@@ -886,21 +953,27 @@ export default function MCPPluginsPage() {
loading={testingPluginId === plugin.id}
disabled={modelSupportStatus !== 'supported'}
size={isMobile ? 'small' : 'middle'}
/>
>
{!isMobile && '测试'}
</Button>
<Button
title={modelSupportStatus !== 'supported' ? '请先完成模型能力检查' : '查看工具'}
icon={<ToolOutlined />}
onClick={() => handleViewTools(plugin.id)}
disabled={modelSupportStatus !== 'supported' || !plugin.enabled || plugin.status !== 'active'}
size={isMobile ? 'small' : 'middle'}
/>
>
{!isMobile && '工具'}
</Button>
<Button
title={modelSupportStatus !== 'supported' ? '请先完成模型能力检查' : '编辑'}
icon={<EditOutlined />}
onClick={() => handleEdit(plugin)}
disabled={modelSupportStatus !== 'supported'}
size={isMobile ? 'small' : 'middle'}
/>
>
{!isMobile && '编辑'}
</Button>
<Button
title={modelSupportStatus !== 'supported' ? '请先完成模型能力检查' : '删除'}
danger
@@ -908,8 +981,10 @@ export default function MCPPluginsPage() {
onClick={() => handleDelete(plugin)}
disabled={modelSupportStatus !== 'supported'}
size={isMobile ? 'small' : 'middle'}
/>
</Space>
>
{!isMobile && '删除'}
</Button>
</div>
</div>
</Card>
))}
@@ -942,7 +1017,7 @@ export default function MCPPluginsPage() {
extra="粘贴标准MCP配置,系统自动提取插件名称。支持HTTP和Stdio类型"
>
<TextArea
rows={16}
rows={isMobile ? 12 : 16}
placeholder={`示例:
{
"mcpServers": {
+1 -2
View File
@@ -253,10 +253,9 @@ export default function PromptTemplates() {
<div style={{
minHeight: '90vh',
background: 'linear-gradient(180deg, var(--color-bg-base) 0%, #EEF2F3 100%)',
padding: isMobile ? '20px 16px' : '24px 24px',
padding: isMobile ? '20px 16px 70px' : '24px 24px 70px',
display: 'flex',
flexDirection: 'column',
marginBottom: '55px',
}}>
<div style={{
maxWidth: 1400,
+1 -2
View File
@@ -794,10 +794,9 @@ export default function SettingsPage() {
<div style={{
minHeight: '90vh',
background: 'linear-gradient(180deg, var(--color-bg-base) 0%, #EEF2F3 100%)',
padding: isMobile ? '20px 16px' : '24px 24px',
padding: isMobile ? '20px 16px 70px' : '24px 24px 70px',
display: 'flex',
flexDirection: 'column',
marginBottom: '55px',
}}>
<div style={{
maxWidth: 1400,
+1 -1
View File
@@ -21,7 +21,7 @@ interface SponsorOption {
const sponsorOptions: SponsorOption[] = [
{ amount: 5, label: '🌶️ 一包辣条', image: '/5.png', description: '¥5' },
{ amount: 10, label: '🍱 一顿拼好饭', image: '/10.png', description: '¥10' },
{ amount: 20, label: '🧋 一杯咖啡', image: '/20.png', description: '¥20' },
{ amount: 20, label: ' 一杯咖啡', image: '/20.png', description: '¥20' },
{ amount: 50, label: '🍖 一次烧烤', image: '/50.png', description: '¥50' },
{ amount: 99, label: '🍲 一顿海底捞', image: '/99.png', description: '¥99' },
];