前端重构

This commit is contained in:
Yvan 2026-01-08 15:12:59 +08:00
parent 010a96db19
commit 5f66394e7c
5 changed files with 883 additions and 87 deletions

View File

@ -102,8 +102,10 @@ export default [
name: '插件系统',
hideInMenu: true,
routes: [
{ path: '/plugin/announcement', name: '公告管理', component: './plugin/announcement' },
{ path: '/plugin/email', name: '邮件插件', component: './plugin/email' },
{ path: '/plugin/installPlugin', name: '插件安装', component: './systemTools/installPlugin' },
{ path: '/plugin/pubPlug', name: '打包插件', component: './systemTools/pubPlug' },
{ path: '/plugin/plugin-email', name: '邮件插件', component: './plugin/email' },
{ path: '/plugin/anInfo', name: '公告管理', component: './plugin/announcement' },
],
},

View File

@ -1,55 +1,57 @@
/**
* KRA -
* GVA: view/systemTools/system/system.vue
* GVA: view/system/state.vue
*/
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import { PageContainer } from '@ant-design/pro-components';
import { Card, Row, Col, Progress, Statistic, Spin } from 'antd';
import { Card, Row, Col, Progress, Spin } from 'antd';
import { getServerInfo } from '@/services/kratos/system';
interface ServerInfo {
cpu: {
cpuNum: number;
cpuPercent: number[];
};
mem: {
total: number;
used: number;
usedPercent: number;
};
disk: {
total: number;
used: number;
usedPercent: number;
};
os: {
goVersion: string;
os: string;
arch: string;
numCpu: number;
compiler: string;
version: string;
numGoroutine: number;
};
interface DiskInfo {
mountPoint: string;
totalMb: number;
usedMb: number;
totalGb: number;
usedGb: number;
usedPercent: number;
}
const formatBytes = (bytes: number): string => {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
interface ServerState {
os?: {
goos: string;
numCpu: number;
compiler: string;
goVersion: string;
numGoroutine: number;
};
cpu?: {
cores: number;
cpus: number[];
};
ram?: {
totalMb: number;
usedMb: number;
usedPercent: number;
};
disk?: DiskInfo[];
}
const getProgressColor = (percent: number) => {
if (percent < 20) return '#5cb87a';
if (percent < 40) return '#e6a23c';
return '#f56c6c';
};
const State: React.FC = () => {
const [serverInfo, setServerInfo] = useState<ServerInfo | null>(null);
const [state, setState] = useState<ServerState>({});
const [loading, setLoading] = useState(true);
const timerRef = useRef<NodeJS.Timeout | null>(null);
const fetchServerInfo = async () => {
const reload = async () => {
try {
const res = await getServerInfo();
if (res.code === 0) {
setServerInfo(res.data);
setState(res.data?.server || res.data || {});
}
} finally {
setLoading(false);
@ -57,9 +59,13 @@ const State: React.FC = () => {
};
useEffect(() => {
fetchServerInfo();
const timer = setInterval(fetchServerInfo, 5000);
return () => clearInterval(timer);
reload();
timerRef.current = setInterval(reload, 10000); // 10秒刷新一次
return () => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
};
}, []);
if (loading) {
@ -75,56 +81,134 @@ const State: React.FC = () => {
return (
<PageContainer>
<Row gutter={[16, 16]}>
{/* CPU 使用率 */}
<Col xs={24} sm={12} lg={6}>
<Card title="CPU 使用率">
<Progress
type="dashboard"
percent={serverInfo?.cpu?.cpuPercent?.[0] || 0}
format={(percent) => `${percent?.toFixed(1)}%`}
/>
<Statistic title="CPU 核心数" value={serverInfo?.cpu?.cpuNum || 0} />
</Card>
{/* Runtime */}
<Col xs={24} md={12}>
{state.os && (
<Card title="Runtime" style={{ height: 320 }}>
<Row gutter={[8, 8]}>
<Col span={12}>os:</Col>
<Col span={12}>{state.os.goos}</Col>
</Row>
<Row gutter={[8, 8]}>
<Col span={12}>cpu nums:</Col>
<Col span={12}>{state.os.numCpu}</Col>
</Row>
<Row gutter={[8, 8]}>
<Col span={12}>compiler:</Col>
<Col span={12}>{state.os.compiler}</Col>
</Row>
<Row gutter={[8, 8]}>
<Col span={12}>go version:</Col>
<Col span={12}>{state.os.goVersion}</Col>
</Row>
<Row gutter={[8, 8]}>
<Col span={12}>goroutine nums:</Col>
<Col span={12}>{state.os.numGoroutine}</Col>
</Row>
</Card>
)}
</Col>
{/* 内存使用 */}
<Col xs={24} sm={12} lg={6}>
<Card title="内存使用">
<Progress
type="dashboard"
percent={serverInfo?.mem?.usedPercent || 0}
format={(percent) => `${percent?.toFixed(1)}%`}
/>
<Statistic
title="已用/总量"
value={`${formatBytes(serverInfo?.mem?.used || 0)} / ${formatBytes(serverInfo?.mem?.total || 0)}`}
/>
</Card>
{/* Disk */}
<Col xs={24} md={12}>
{state.disk && (
<Card title="Disk" style={{ height: 320, overflow: 'auto' }}>
{state.disk.map((item, index) => (
<Row key={index} gutter={[8, 8]} style={{ marginBottom: 24 }}>
<Col span={12}>
<Row gutter={[8, 4]}>
<Col span={12}>MountPoint</Col>
<Col span={12}>{item.mountPoint}</Col>
</Row>
<Row gutter={[8, 4]}>
<Col span={12}>total (MB)</Col>
<Col span={12}>{item.totalMb}</Col>
</Row>
<Row gutter={[8, 4]}>
<Col span={12}>used (MB)</Col>
<Col span={12}>{item.usedMb}</Col>
</Row>
<Row gutter={[8, 4]}>
<Col span={12}>total (GB)</Col>
<Col span={12}>{item.totalGb}</Col>
</Row>
<Row gutter={[8, 4]}>
<Col span={12}>used (GB)</Col>
<Col span={12}>{item.usedGb}</Col>
</Row>
</Col>
<Col span={12}>
<Progress
type="dashboard"
percent={Math.round(item.usedPercent)}
strokeColor={getProgressColor(item.usedPercent)}
/>
</Col>
</Row>
))}
</Card>
)}
</Col>
</Row>
<Row gutter={[16, 16]} style={{ marginTop: 16 }}>
{/* CPU */}
<Col xs={24} md={12}>
{state.cpu && (
<Card title="CPU" style={{ height: 320, overflow: 'auto' }}>
<Row gutter={[8, 8]}>
<Col span={12}>physical number of cores:</Col>
<Col span={12}>{state.cpu.cores}</Col>
</Row>
{state.cpu.cpus?.map((item, index) => (
<Row key={index} gutter={[8, 8]}>
<Col span={12}>core {index}:</Col>
<Col span={12}>
<Progress
percent={Math.round(item)}
strokeColor={getProgressColor(item)}
size="small"
/>
</Col>
</Row>
))}
</Card>
)}
</Col>
{/* 磁盘使用 */}
<Col xs={24} sm={12} lg={6}>
<Card title="磁盘使用">
<Progress
type="dashboard"
percent={serverInfo?.disk?.usedPercent || 0}
format={(percent) => `${percent?.toFixed(1)}%`}
/>
<Statistic
title="已用/总量"
value={`${formatBytes(serverInfo?.disk?.used || 0)} / ${formatBytes(serverInfo?.disk?.total || 0)}`}
/>
</Card>
</Col>
{/* 系统信息 */}
<Col xs={24} sm={12} lg={6}>
<Card title="系统信息">
<p><strong>Go :</strong> {serverInfo?.os?.goVersion}</p>
<p><strong>:</strong> {serverInfo?.os?.os}</p>
<p><strong>:</strong> {serverInfo?.os?.arch}</p>
<p><strong>Goroutine:</strong> {serverInfo?.os?.numGoroutine}</p>
</Card>
{/* Ram */}
<Col xs={24} md={12}>
{state.ram && (
<Card title="Ram" style={{ height: 320 }}>
<Row gutter={[8, 8]}>
<Col span={12}>
<Row gutter={[8, 4]}>
<Col span={12}>total (MB)</Col>
<Col span={12}>{state.ram.totalMb}</Col>
</Row>
<Row gutter={[8, 4]}>
<Col span={12}>used (MB)</Col>
<Col span={12}>{state.ram.usedMb}</Col>
</Row>
<Row gutter={[8, 4]}>
<Col span={12}>total (GB)</Col>
<Col span={12}>{(state.ram.totalMb / 1024).toFixed(2)}</Col>
</Row>
<Row gutter={[8, 4]}>
<Col span={12}>used (GB)</Col>
<Col span={12}>{(state.ram.usedMb / 1024).toFixed(2)}</Col>
</Row>
</Col>
<Col span={12}>
<Progress
type="dashboard"
percent={Math.round(state.ram.usedPercent)}
strokeColor={getProgressColor(state.ram.usedPercent)}
/>
</Col>
</Row>
</Card>
)}
</Col>
</Row>
</PageContainer>

View File

@ -0,0 +1,264 @@
/**
* KRA - MCP Tools模板
* GVA: view/systemTools/autoCode/mcp.vue
*/
import React from 'react';
import { PageContainer } from '@ant-design/pro-components';
import { Card, Form, Input, Button, Table, Select, Checkbox, message } from 'antd';
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons';
import { mcp } from '@/services/kratos/autoCode';
interface ParamItem {
key: string;
name: string;
description: string;
type: string;
default?: string;
required: boolean;
}
interface ResponseItem {
key: string;
type: string;
}
const MCP: React.FC = () => {
const [form] = Form.useForm();
const [params, setParams] = React.useState<ParamItem[]>([]);
const [response, setResponse] = React.useState<ResponseItem[]>([]);
const addParam = () => {
setParams([...params, {
key: Date.now().toString(),
name: '',
description: '',
type: 'string',
default: '',
required: false,
}]);
};
const removeParam = (key: string) => {
setParams(params.filter(p => p.key !== key));
};
const updateParam = (key: string, field: string, value: any) => {
setParams(params.map(p => p.key === key ? { ...p, [field]: value } : p));
};
const addResponse = () => {
setResponse([...response, { key: Date.now().toString(), type: 'text' }]);
};
const removeResponse = (key: string) => {
setResponse(response.filter(r => r.key !== key));
};
const handleSubmit = async () => {
try {
const values = await form.validateFields();
// 验证参数
for (const p of params) {
if (!p.name || !p.description || !p.type) {
message.error('请完善所有参数信息');
return;
}
}
// 验证返回参数
for (const r of response) {
if (!r.type) {
message.error('请完善所有返回参数类型');
return;
}
}
const res = await mcp({
...values,
params: params.map(({ key, ...rest }) => rest),
response: response.map(({ key, ...rest }) => rest),
});
if (res.code === 0) {
message.success(res.msg || '生成成功');
}
} catch (error) {
// 表单验证失败
}
};
const paramColumns = [
{
title: '参数名',
dataIndex: 'name',
width: 120,
render: (_: any, record: ParamItem) => (
<Input
value={record.name}
placeholder="参数名"
onChange={(e) => updateParam(record.key, 'name', e.target.value)}
/>
),
},
{
title: '描述',
dataIndex: 'description',
render: (_: any, record: ParamItem) => (
<Input
value={record.description}
placeholder="描述"
onChange={(e) => updateParam(record.key, 'description', e.target.value)}
/>
),
},
{
title: '类型',
dataIndex: 'type',
width: 120,
render: (_: any, record: ParamItem) => (
<Select
value={record.type}
style={{ width: '100%' }}
onChange={(value) => updateParam(record.key, 'type', value)}
>
<Select.Option value="string">string</Select.Option>
<Select.Option value="number">number</Select.Option>
<Select.Option value="boolean">boolean</Select.Option>
<Select.Option value="object">object</Select.Option>
<Select.Option value="array">array</Select.Option>
</Select>
),
},
{
title: '默认值',
dataIndex: 'default',
width: 150,
render: (_: any, record: ParamItem) => (
<Input
value={record.default}
disabled={record.type === 'object'}
onChange={(e) => updateParam(record.key, 'default', e.target.value)}
/>
),
},
{
title: '必填',
dataIndex: 'required',
width: 80,
render: (_: any, record: ParamItem) => (
<Checkbox
checked={record.required}
onChange={(e) => updateParam(record.key, 'required', e.target.checked)}
/>
),
},
{
title: '操作',
width: 80,
render: (_: any, record: ParamItem) => (
<Button type="link" danger onClick={() => removeParam(record.key)}>
</Button>
),
},
];
const responseColumns = [
{
title: '类型',
dataIndex: 'type',
render: (_: any, record: ResponseItem) => (
<Select
value={record.type}
style={{ width: 200 }}
onChange={(value) => {
setResponse(response.map(r => r.key === record.key ? { ...r, type: value } : r));
}}
>
<Select.Option value="text">text</Select.Option>
<Select.Option value="image">image</Select.Option>
</Select>
),
},
{
title: '操作',
width: 80,
render: (_: any, record: ResponseItem) => (
<Button type="link" danger onClick={() => removeResponse(record.key)}>
</Button>
),
},
];
return (
<PageContainer>
<Card>
<Form form={form} layout="vertical">
<Form.Item
label="工具名称"
name="name"
rules={[{ required: true, message: '请输入工具名称' }]}
>
<Input placeholder="例:CurrentTime" />
</Form.Item>
<Form.Item
label="工具描述"
name="description"
rules={[{ required: true, message: '请输入工具描述' }]}
>
<Input.TextArea placeholder="请输入工具描述" />
</Form.Item>
<Form.Item label="参数列表">
<Table
columns={paramColumns}
dataSource={params}
rowKey="key"
pagination={false}
size="small"
/>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={addParam}
style={{ marginTop: 10 }}
>
</Button>
</Form.Item>
<Form.Item label="返回参数">
<Table
columns={responseColumns}
dataSource={response}
rowKey="key"
pagination={false}
size="small"
/>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={addResponse}
style={{ marginTop: 10 }}
>
</Button>
</Form.Item>
<Form.Item>
<div style={{ textAlign: 'right' }}>
<Button type="primary" onClick={handleSubmit}>
</Button>
</div>
</Form.Item>
</Form>
</Card>
</PageContainer>
);
};
export default MCP;

View File

@ -0,0 +1,235 @@
/**
* KRA - MCP Tools测试
* GVA: view/systemTools/autoCode/mcpTest.vue
*/
import React, { useState, useEffect } from 'react';
import { PageContainer } from '@ant-design/pro-components';
import { Card, Row, Col, Button, Modal, Form, Input, Select, message, Tooltip, Image } from 'antd';
import { PlayCircleOutlined, CopyOutlined } from '@ant-design/icons';
import { mcpList, mcpTest } from '@/services/kratos/autoCode';
interface McpTool {
name: string;
description: string;
inputSchema?: {
properties?: Record<string, {
type: string;
description?: string;
enum?: string[];
default?: any;
}>;
required?: string[];
};
}
interface McpServerConfig {
mcpServers: Record<string, { url: string }>;
}
const MCPTest: React.FC = () => {
const [tools, setTools] = useState<McpTool[]>([]);
const [serverConfig, setServerConfig] = useState<string>('');
const [testModalVisible, setTestModalVisible] = useState(false);
const [currentTool, setCurrentTool] = useState<McpTool | null>(null);
const [testResult, setTestResult] = useState<any>(null);
const [form] = Form.useForm();
const fetchTools = async () => {
const res = await mcpList();
if (res.code === 0 && res.data?.list?.tools) {
setTools(res.data.list.tools);
setServerConfig(JSON.stringify(res.data.mcpServerConfig, null, 2));
}
};
useEffect(() => {
fetchTools();
}, []);
const copyConfig = () => {
navigator.clipboard.writeText(serverConfig);
message.success('配置已复制到剪贴板');
};
const openTestModal = (tool: McpTool) => {
setCurrentTool(tool);
setTestResult(null);
form.resetFields();
// 设置默认值
if (tool.inputSchema?.properties) {
const initialValues: Record<string, any> = {};
Object.entries(tool.inputSchema.properties).forEach(([key, prop]) => {
if (prop.default !== undefined) {
initialValues[key] = prop.default;
} else if (prop.type === 'boolean') {
initialValues[key] = false;
}
});
form.setFieldsValue(initialValues);
}
setTestModalVisible(true);
};
const handleTest = async () => {
if (!currentTool) return;
try {
const values = await form.validateFields();
// 处理 object/array 类型的 JSON 解析
if (currentTool.inputSchema?.properties) {
Object.entries(currentTool.inputSchema.properties).forEach(([key, prop]) => {
if ((prop.type === 'object' || prop.type === 'array') && values[key]) {
try {
values[key] = JSON.parse(values[key]);
} catch (e) {
message.error(`参数 ${key} 的JSON格式无效`);
throw e;
}
}
});
}
const res = await mcpTest({
name: currentTool.name,
arguments: values,
});
setTestResult(res.data);
if (res.code === 0) {
message.success('API调用成功');
}
} catch (error) {
// 验证失败或JSON解析失败
}
};
const renderTestResult = () => {
if (!testResult) return null;
if (typeof testResult === 'string') {
return <pre style={{ background: '#f5f5f5', padding: 10, borderRadius: 4, whiteSpace: 'pre-wrap' }}>{testResult}</pre>;
}
if (testResult.type === 'image' && testResult.content) {
return <Image src={testResult.content} style={{ maxWidth: '100%', maxHeight: 300 }} />;
}
if (testResult.type === 'text' && testResult.content) {
return <pre style={{ background: '#f5f5f5', padding: 10, borderRadius: 4, whiteSpace: 'pre-wrap' }}>{testResult.content}</pre>;
}
return <pre style={{ background: '#f5f5f5', padding: 10, borderRadius: 4, whiteSpace: 'pre-wrap' }}>{JSON.stringify(testResult, null, 2)}</pre>;
};
return (
<PageContainer>
<Card
title="MCP 服务器配置示例"
extra={
<Tooltip title="复制配置">
<Button icon={<CopyOutlined />} onClick={copyConfig} />
</Tooltip>
}
style={{ marginBottom: 16 }}
>
<pre style={{ background: '#f5f5f5', padding: 10, borderRadius: 4, whiteSpace: 'pre-wrap' }}>
{serverConfig}
</pre>
</Card>
<Row gutter={[16, 16]}>
{tools.map((tool) => (
<Col key={tool.name} xs={24} sm={12} lg={8}>
<Card
title={tool.name}
extra={
<Tooltip title="测试工具">
<Button
icon={<PlayCircleOutlined />}
onClick={() => openTestModal(tool)}
/>
</Tooltip>
}
style={{ minHeight: 200 }}
>
<p style={{ marginBottom: 8 }}>{tool.description}</p>
{tool.inputSchema?.properties && Object.keys(tool.inputSchema.properties).length > 0 && (
<div style={{ fontSize: 12, color: '#666', borderTop: '1px solid #f0f0f0', paddingTop: 8 }}>
<strong> ({Object.keys(tool.inputSchema.properties).length})</strong>
<div style={{ maxHeight: 100, overflow: 'auto', marginTop: 8 }}>
{Object.entries(tool.inputSchema.properties).map(([name, prop]) => (
<div key={name} style={{ padding: '4px 0', borderBottom: '1px solid #f5f5f5' }}>
<span style={{ fontWeight: 500 }}>{name}</span>
{tool.inputSchema?.required?.includes(name) && <span style={{ color: 'red' }}>*</span>}
<span style={{ marginLeft: 8, background: '#e6f7ff', padding: '0 4px', borderRadius: 2 }}>{prop.type}</span>
<div style={{ color: '#999', fontSize: 11 }}>{prop.description || '无描述'}</div>
</div>
))}
</div>
</div>
)}
{(!tool.inputSchema?.properties || Object.keys(tool.inputSchema.properties).length === 0) && (
<div style={{ color: '#999', fontStyle: 'italic', textAlign: 'center', padding: 16 }}>
</div>
)}
</Card>
</Col>
))}
</Row>
<Modal
title={currentTool ? `${currentTool.name} - 参数测试` : '参数测试'}
open={testModalVisible}
onCancel={() => setTestModalVisible(false)}
onOk={handleTest}
okText="测试"
width={600}
>
{currentTool && (
<Form form={form} layout="vertical">
{currentTool.inputSchema?.properties && Object.entries(currentTool.inputSchema.properties).map(([name, prop]) => (
<Form.Item
key={name}
label={prop.description || name}
name={name}
rules={currentTool.inputSchema?.required?.includes(name) ? [{ required: true, message: `请输入 ${prop.description || name}` }] : []}
>
{prop.type === 'boolean' ? (
<Select>
<Select.Option value={true}>True</Select.Option>
<Select.Option value={false}>False</Select.Option>
</Select>
) : prop.enum ? (
<Select>
{prop.enum.map((v) => (
<Select.Option key={v} value={v}>{v}</Select.Option>
))}
</Select>
) : prop.type === 'number' ? (
<Input type="number" placeholder={prop.description || `请输入${name}`} />
) : prop.type === 'object' || prop.type === 'array' ? (
<Input.TextArea rows={3} placeholder={`${prop.description || name} (请输入JSON格式)`} />
) : (
<Input placeholder={prop.description || `请输入${name}`} />
)}
</Form.Item>
))}
</Form>
)}
{testResult && (
<div style={{ marginTop: 16, padding: 16, border: '1px solid #f0f0f0', borderRadius: 4 }}>
<h4>API :</h4>
{renderTestResult()}
</div>
)}
</Modal>
</PageContainer>
);
};
export default MCPTest;

View File

@ -0,0 +1,211 @@
/**
* KRA - AI页面绘制
* GVA: view/systemTools/autoCode/picture.vue
*/
import React, { useState } from 'react';
import { PageContainer } from '@ant-design/pro-components';
import { Card, Form, Input, Button, Radio, Checkbox, Tabs, Empty, message } from 'antd';
import WarningBar from '@/components/WarningBar/WarningBar';
import { createWeb } from '@/services/kratos/autoCode';
const { TextArea } = Input;
// 页面用途与内容板块的推荐映射
const pageTypeContentMap: Record<string, string[]> = {
'企业官网': ['Banner轮播图', '产品/服务介绍', '功能特点展示', '客户案例', '联系表单'],
'电商页面': ['Banner轮播图', '商品列表', '商品卡片', '购物车', '商品分类', '热门推荐', '限时特惠', '结算页面', '用户评价'],
'个人博客': ['Banner轮播图', '新闻/博客列表', '用户评价', '联系表单'],
'产品介绍': ['Banner轮播图', '产品/服务介绍', '功能特点展示', '价格表', 'FAQ/常见问题'],
'活动落地页': ['Banner轮播图', '功能特点展示', '联系表单', '数据统计'],
};
const contentBlockOptions = [
'Banner轮播图', '产品/服务介绍', '功能特点展示', '客户案例', '团队介绍',
'联系表单', '新闻/博客列表', '价格表', 'FAQ/常见问题', '用户评价',
'数据统计', '商品列表', '商品卡片', '购物车', '结算页面',
'订单跟踪', '商品分类', '热门推荐', '限时特惠', '其他',
];
const Picture: React.FC = () => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [output, setOutput] = useState<string>('');
const [pageType, setPageType] = useState('企业官网');
const [contentBlocks, setContentBlocks] = useState<string[]>(['Banner轮播图', '产品/服务介绍']);
const handlePageTypeChange = (value: string) => {
setPageType(value);
if (value !== '其他' && pageTypeContentMap[value]) {
setContentBlocks([...pageTypeContentMap[value]]);
form.setFieldsValue({ contentBlocks: pageTypeContentMap[value] });
}
};
const handleGenerate = async () => {
const values = await form.validateFields();
let fullPrompt = '';
fullPrompt += `页面用途: ${values.pageType === '其他' ? values.pageTypeCustom : values.pageType}\n`;
const blocks = (values.contentBlocks || []).filter((b: string) => b !== '其他');
if (values.contentBlocksCustom) blocks.push(values.contentBlocksCustom);
fullPrompt += `主要内容板块: ${blocks.join(', ')}\n`;
fullPrompt += `风格偏好: ${values.stylePreference === '其他' ? values.stylePreferenceCustom : values.stylePreference}\n`;
fullPrompt += `设计布局: ${values.layoutDesign === '其他' ? values.layoutDesignCustom : values.layoutDesign}\n`;
fullPrompt += `配色方案: ${values.colorScheme === '其他' ? values.colorSchemeCustom : values.colorScheme}\n`;
if (values.prompt) {
fullPrompt += `\n详细描述: ${values.prompt}`;
}
setLoading(true);
try {
const res = await createWeb({ web: fullPrompt, command: 'createWeb' });
if (res.code === 0) {
setOutput(res.data);
message.success('生成成功');
}
} finally {
setLoading(false);
}
};
const copyCode = () => {
navigator.clipboard.writeText(output);
message.success('复制成功');
};
return (
<PageContainer>
<WarningBar
title="此功能只针对授权用户开放"
href="https://www.gin-vue-admin.com/empower/"
/>
<Card title="AI前端工程师" style={{ marginBottom: 16 }}>
<Form form={form} layout="vertical" initialValues={{
pageType: '企业官网',
contentBlocks: ['Banner轮播图', '产品/服务介绍'],
stylePreference: '简约',
layoutDesign: '响应式',
colorScheme: '蓝色系',
}}>
<Form.Item label="页面用途" name="pageType">
<Radio.Group onChange={(e) => handlePageTypeChange(e.target.value)}>
<Radio value="企业官网"></Radio>
<Radio value="电商页面"></Radio>
<Radio value="个人博客"></Radio>
<Radio value="产品介绍"></Radio>
<Radio value="活动落地页"></Radio>
<Radio value="其他"></Radio>
</Radio.Group>
</Form.Item>
<Form.Item noStyle shouldUpdate={(prev, cur) => prev.pageType !== cur.pageType}>
{({ getFieldValue }) => getFieldValue('pageType') === '其他' && (
<Form.Item name="pageTypeCustom">
<Input placeholder="请输入页面用途" />
</Form.Item>
)}
</Form.Item>
<Form.Item label="主要内容板块" name="contentBlocks">
<Checkbox.Group options={contentBlockOptions} />
</Form.Item>
<Form.Item label="风格偏好" name="stylePreference">
<Radio.Group>
<Radio value="简约"></Radio>
<Radio value="科技感"></Radio>
<Radio value="温馨"></Radio>
<Radio value="专业"></Radio>
<Radio value="创意"></Radio>
<Radio value="复古"></Radio>
<Radio value="奢华"></Radio>
<Radio value="其他"></Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="设计布局" name="layoutDesign">
<Radio.Group>
<Radio value="单栏布局"></Radio>
<Radio value="双栏布局"></Radio>
<Radio value="三栏布局"></Radio>
<Radio value="网格布局"></Radio>
<Radio value="瀑布流"></Radio>
<Radio value="卡片式"></Radio>
<Radio value="响应式"></Radio>
<Radio value="其他"></Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="配色方案" name="colorScheme">
<Radio.Group>
<Radio value="蓝色系"></Radio>
<Radio value="绿色系">绿</Radio>
<Radio value="红色系"></Radio>
<Radio value="黑白灰"></Radio>
<Radio value="暖色调"></Radio>
<Radio value="冷色调"></Radio>
<Radio value="其他"></Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="详细描述(可选)" name="prompt">
<TextArea
rows={5}
maxLength={2000}
placeholder="补充您对页面的其他要求或特殊需求"
/>
</Form.Item>
<Form.Item>
<Button type="primary" loading={loading} onClick={handleGenerate}>
</Button>
</Form.Item>
</Form>
</Card>
{output ? (
<Card>
<Tabs items={[
{
key: 'preview',
label: '页面预览',
children: (
<div style={{ height: 500, overflow: 'auto', background: '#f5f5f5', padding: 16 }}>
<div dangerouslySetInnerHTML={{ __html: output }} />
</div>
),
},
{
key: 'source',
label: '源代码',
children: (
<div style={{ position: 'relative' }}>
<Button
style={{ position: 'absolute', top: 8, right: 8 }}
onClick={copyCode}
>
</Button>
<pre style={{ height: 500, overflow: 'auto', background: '#f5f5f5', padding: 16, marginTop: 40 }}>
{output}
</pre>
</div>
),
},
]} />
</Card>
) : (
<Card>
<Empty description="暂无生成内容" />
</Card>
)}
</PageContainer>
);
};
export default Picture;