前端重构

This commit is contained in:
Yvan
2026-01-08 11:20:29 +08:00
parent d76655a30a
commit f12f2e0f00
201 changed files with 27517 additions and 537 deletions
@@ -0,0 +1,107 @@
.pub-plug-container {
padding: 16px;
.pub-plug-alert {
margin-bottom: 16px;
}
.plugin-name-input {
margin-bottom: 16px;
}
.transfer-card {
margin-bottom: 16px;
.transfer-warning {
margin-bottom: 16px;
}
.parent-menu-input {
margin-bottom: 16px;
}
.transfer-wrapper {
display: flex;
justify-content: center;
padding: 16px 0;
:global {
.ant-transfer {
.ant-transfer-list {
width: 400px;
height: 350px;
}
.ant-transfer-list-header {
background: #fafafa;
}
.ant-transfer-list-body {
.ant-transfer-list-content-item {
padding: 8px 12px;
.transfer-item-content {
display: flex;
flex-direction: column;
gap: 2px;
.item-title {
font-weight: 500;
color: #333;
}
.item-path {
font-size: 12px;
color: #999;
}
}
}
}
}
}
}
.transfer-actions {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}
}
.pub-actions {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}
}
// 暗色主题适配
.ant-layout-content[data-theme='dark'] {
.pub-plug-container {
.transfer-card {
.transfer-wrapper {
:global {
.ant-transfer {
.ant-transfer-list-header {
background: #1f1f1f;
}
.ant-transfer-list-body {
.ant-transfer-list-content-item {
.transfer-item-content {
.item-title {
color: #d9d9d9;
}
.item-path {
color: #8c8c8c;
}
}
}
}
}
}
}
}
}
}
+305
View File
@@ -0,0 +1,305 @@
import React, { useState, useEffect } from 'react';
import { Card, Input, Transfer, Button, Alert, message, Modal, Space } from 'antd';
import type { TransferProps } from 'antd';
import { pubPlug, initMenu, initAPI } from '@/services/kratos/autoCode';
import { getAllApis } from '@/services/kratos/api';
import { getMenuList } from '@/services/kratos/menu';
import './index.less';
interface MenuItem {
ID: number;
meta: {
title: string;
};
component: string;
children?: MenuItem[];
}
interface ApiItem {
ID: number;
description: string;
path: string;
}
/**
* 插件打包页面
*/
const PubPlug: React.FC = () => {
const [plugName, setPlugName] = useState('');
const [parentMenu, setParentMenu] = useState('');
// 菜单穿梭框
const [menuTargetKeys, setMenuTargetKeys] = useState<string[]>([]);
const [menuDataSource, setMenuDataSource] = useState<any[]>([]);
// API穿梭框
const [apiTargetKeys, setApiTargetKeys] = useState<string[]>([]);
const [apiDataSource, setApiDataSource] = useState<any[]>([]);
// 递归展开菜单到一级
const flattenMenus = (menus: MenuItem[]): MenuItem[] => {
const result: MenuItem[] = [];
menus.forEach((item) => {
if (item.children && item.children.length > 0) {
result.push(...flattenMenus(item.children));
} else {
result.push(item);
}
});
return result;
};
// 初始化数据
const initData = async () => {
try {
// 获取菜单列表
const menuRes = await getMenuList();
if (menuRes.code === 0 && menuRes.data) {
const flatMenus = flattenMenus(menuRes.data);
setMenuDataSource(flatMenus.map((item) => ({
key: String(item.ID),
ID: item.ID,
title: item.meta?.title || '',
component: item.component || '',
})));
}
// 获取API列表
const apiRes = await getAllApis();
if (apiRes.code === 0 && apiRes.data?.apis) {
setApiDataSource(apiRes.data.apis.map((item: ApiItem) => ({
key: String(item.ID),
ID: item.ID,
description: item.description || '',
path: item.path || '',
})));
}
} catch (error) {
console.error('初始化数据失败:', error);
}
};
useEffect(() => {
initData();
}, []);
// 菜单穿梭框筛选
const filterMenuOption: TransferProps['filterOption'] = (inputValue, option) => {
return (
option.title?.toLowerCase().includes(inputValue.toLowerCase()) ||
option.component?.toLowerCase().includes(inputValue.toLowerCase())
);
};
// API穿梭框筛选
const filterApiOption: TransferProps['filterOption'] = (inputValue, option) => {
return (
option.description?.toLowerCase().includes(inputValue.toLowerCase()) ||
option.path?.toLowerCase().includes(inputValue.toLowerCase())
);
};
// 定义安装菜单
const handleInitMenu = () => {
if (!parentMenu) {
message.error('请填写菜单组名');
return;
}
if (menuTargetKeys.length === 0) {
message.error('请至少选择一个菜单');
return;
}
if (!plugName) {
message.error('请填写插件名');
return;
}
Modal.confirm({
title: '生成初始菜单',
content: `点击后将会覆盖 server 下的 /plugin/${plugName}/initialize/menu. 是否继续?`,
okText: '生成',
cancelText: '取消',
onOk: async () => {
try {
const res = await initMenu({
plugName,
parentMenu,
menus: menuTargetKeys.map(Number),
});
if (res.code === 0) {
message.success('菜单初始化成功');
} else {
message.error(res.msg || '菜单初始化失败');
}
} catch (error) {
message.error('菜单初始化失败');
}
},
});
};
// 定义安装API
const handleInitAPI = () => {
if (apiTargetKeys.length === 0) {
message.error('请至少选择一个API');
return;
}
if (!plugName) {
message.error('请填写插件名');
return;
}
Modal.confirm({
title: '生成初始API',
content: `点击后将会覆盖 server 下的 /plugin/${plugName}/initialize/api. 是否继续?`,
okText: '生成',
cancelText: '取消',
onOk: async () => {
try {
const res = await initAPI({
plugName,
apis: apiTargetKeys.map(Number),
});
if (res.code === 0) {
message.success('API初始化成功');
} else {
message.error(res.msg || 'API初始化失败');
}
} catch (error) {
message.error('API初始化失败');
}
},
});
};
// 打包插件
const handlePubPlugin = () => {
if (!plugName) {
message.error('请填写插件名');
return;
}
Modal.confirm({
title: '打包',
content: `请检查 server 下的 /plugin/${plugName}/plugin.go 是否已放开需要的 initialize.Api(ctx) 和 initialize.Menu(ctx)?`,
okText: '打包',
cancelText: '取消',
onOk: async () => {
try {
const res = await pubPlug({ plugName });
if (res.code === 0) {
message.success(res.msg || '打包成功');
} else {
message.error(res.msg || '打包失败');
}
} catch (error) {
message.error('打包失败');
}
},
});
};
return (
<div className="pub-plug-container">
<Alert
message="目前只支持标准插件(通过插件模板生成的标准目录插件),非标准插件请自行打包"
type="warning"
showIcon
className="pub-plug-alert"
/>
<div className="plugin-name-input">
<Input
value={plugName}
onChange={(e) => setPlugName(e.target.value)}
placeholder="插件模板处填写的【插件名】"
size="large"
/>
</div>
{/* 菜单选择 */}
<Card title="菜单选择" className="transfer-card">
<Alert
message="穿梭框请只选择子级菜单即可"
type="info"
showIcon
className="transfer-warning"
/>
<Input
value={parentMenu}
onChange={(e) => setParentMenu(e.target.value)}
placeholder="请输入菜单组名,例:公告管理"
className="parent-menu-input"
/>
<div className="transfer-wrapper">
<Transfer
dataSource={menuDataSource}
targetKeys={menuTargetKeys}
onChange={(newTargetKeys) => setMenuTargetKeys(newTargetKeys as string[])}
filterOption={filterMenuOption}
showSearch
titles={['可选菜单', '使用菜单']}
operations={['选中', '移除']}
render={(item) => (
<div className="transfer-item-content">
<span className="item-title">{item.title}</span>
<span className="item-path">{item.component}</span>
</div>
)}
listStyle={{
width: 400,
height: 350,
}}
/>
</div>
<div className="transfer-actions">
<Button type="primary" onClick={handleInitMenu}>
定义安装菜单
</Button>
</div>
</Card>
{/* API选择 */}
<Card title="API选择" className="transfer-card">
<div className="transfer-wrapper">
<Transfer
dataSource={apiDataSource}
targetKeys={apiTargetKeys}
onChange={(newTargetKeys) => setApiTargetKeys(newTargetKeys as string[])}
filterOption={filterApiOption}
showSearch
titles={['可选API', '使用API']}
operations={['选中', '移除']}
render={(item) => (
<div className="transfer-item-content">
<span className="item-title">{item.description}</span>
<span className="item-path">{item.path}</span>
</div>
)}
listStyle={{
width: 400,
height: 350,
}}
/>
</div>
<div className="transfer-actions">
<Button type="primary" onClick={handleInitAPI}>
定义安装API
</Button>
</div>
</Card>
<div className="pub-actions">
<Button type="primary" size="large" onClick={handlePubPlugin}>
打包插件
</Button>
</div>
</div>
);
};
export default PubPlug;