diff --git a/package-lock.json b/package-lock.json index bc4f6bd..30334db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "axios": "^1.6.7", "localforage": "^1.10.0", "match-sorter": "^6.3.4", + "pinyin-pro": "^3.19.6", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.22.3", @@ -3186,6 +3187,11 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pinyin-pro": { + "version": "3.19.6", + "resolved": "https://registry.npmjs.org/pinyin-pro/-/pinyin-pro-3.19.6.tgz", + "integrity": "sha512-oWb34orr12+DjXf6gtGMB+gIpjRi7DZzyJE66ultbmNzVhpimM/utGtMI8GcbOy/lb26Ph/nogwNYriRPu+SGQ==" + }, "node_modules/postcss": { "version": "8.4.35", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", diff --git a/package.json b/package.json index 98b05b7..6e2bfc0 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "axios": "^1.6.7", "localforage": "^1.10.0", "match-sorter": "^6.3.4", + "pinyin-pro": "^3.19.6", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.22.3", diff --git a/src/components/faicon/FaIconSelect.tsx b/src/components/faicon/FaIconSelect.tsx new file mode 100644 index 0000000..8e360f5 --- /dev/null +++ b/src/components/faicon/FaIconSelect.tsx @@ -0,0 +1,48 @@ +import {Radio} from "antd"; + +type Faicon = { + selectedIcon: string; + handleClick(value: string): void; +} + +export default function FaiconSelect(props: Faicon) { + const faIcons: string[] = [ + 'fa-list', 'fa-list-alt', 'fa-table', 'fa-heart', 'fa-user-md', 'fa-heartbeat', 'fa-file', 'fa-money', + 'fa-taxi', 'fa-tags', 'fa-tag', 'fa-tv', 'fa-user', 'fa-users', 'fa-star', 'fa-support', + 'fa-address-book', 'fa-bandcamp', 'fa-address-card', 'fa-bath', 'fa-eercast', 'fa-adjust', 'fa-asl-interpreting', 'fa-anchor', + 'fa-archive', 'fa-area-chart', 'fa-assistive-listening-systems', 'fa-asterisk', 'fa-audio-description', 'fa-automobile', 'fa-balance-scale', + 'fa-ban', 'fa-bank', 'fa-bar-chart', 'fa-barcode', 'fa-bars', 'fa-battery', 'fa-bicycle', 'fa-bug', + 'fa-calculator', 'fa-calendar', 'fa-camera', 'fa-car', 'fa-check-square', 'fa-child', 'fa-clock-o', 'fa-cloud', + 'fa-coffee', 'fa-cogs', 'fa-cog', 'fa-comments', 'fa-copyright', 'fa-database', 'fa-dashboard', 'fa-desktop', + 'fa-envelope', 'fa-fax', 'fa-female', 'fa-file-code-o', 'fa-file-archive-o', 'fa-file-image-o', 'fa-file-audio-o', 'fa-file-excel-o', + 'fa-file-pdf-o', 'fa-file-movie-o', 'fa-file-powerpoint-o', 'fa-file-word-o', 'fa-flag', 'fa-film', 'fa-folder', 'fa-globe', + 'fa-free-code-camp', 'fa-list-ol', 'fa-list-ul', 'fa-paperclip', 'fa-unlink', 'fa-sliders', 'fa-sitemap', 'fa-terminal', + 'fa-telegram', 'fa-meetup', 'fa-ravelry', 'fa-superpowers', 'fa-window-restore', 'fa-window-maximize', 'fa-linode', 'fa-handshake-o', + 'fa-wpexplorer', 'fa-comment', 'fa-comment-o', 'fa-commenting', 'fa-commenting-o', 'fa-comments-o', + 'fa-microchip', 'fa-podcast', 'fa-at', 'fa-cloud-download', 'fa-cloud-upload', 'fa-code', 'fa-code-fork', 'fa-compass', + 'fa-creative-commons', 'fa-diamond', 'fa-exclamation-circle', 'fa-exclamation-triangle', 'fa-keyboard-o', 'fa-language', 'fa-laptop', 'fa-leaf', + 'fa-lemon-o', 'fa-legal', 'fa-lightbulb-o', 'fa-line-chart', 'fa-lock', 'fa-magic', 'fa-magnet', 'fa-map', + 'fa-map-marker', 'fa-map-o', 'fa-map-pin', 'fa-map-signs', 'fa-meh-o', 'fa-microphone', 'fa-mobile', 'fa-moon-o', + 'fa-mortar-board', 'fa-motorcycle', 'fa-music', 'fa-paper-plane', 'fa-paw', 'fa-object-group', 'fa-pie-chart', 'fa-qrcode', + 'fa-recycle', 'fa-road', 'fa-rocket', 'fa-shopping-bag', 'fa-signing', 'fa-street-view', 'fa-thumb-tack', 'fa-ticket', + 'fa-tint', 'fa-tree', 'fa-trophy', 'fa-truck', 'fa-umbrella', 'fa-universal-access', 'fa-user-secret', 'fa-video-camera', + ] + return ( + <> +
+ { + props.handleClick(e.target.value); + }}> + { + faIcons.map(item => + + + + ) + } + +
+ + ) +} \ No newline at end of file diff --git a/src/components/modfield/ModField.tsx b/src/components/modfield/ModField.tsx new file mode 100644 index 0000000..4b62aeb --- /dev/null +++ b/src/components/modfield/ModField.tsx @@ -0,0 +1,139 @@ +import './modfield.css' +import {Alert, Button, Input, message, Table, TableProps} from "antd"; +import {useEffect, useState} from "react"; +import {DeleteOutlined, PlusOutlined} from "@ant-design/icons"; +import {pinyin} from "pinyin-pro"; + +export interface IModField { + projModFieldId: string; + fieldDesc: string; + fieldName: string; + fieldType: string; +} + +interface PropsType { + scrollHeight?: number; + modFiledArray?: IModField[]; + isEdit?: boolean; + + handleChange?(modFieldArray: undefined | IModField[]): void; +} + +export default function ModField(props: PropsType) { + const [messageApi, contextHolder] = message.useMessage(); + const [tableDataArray, setTableDataArray] = useState([]); + + const isFiledNameExist = function (index: number, fieldName: string) { + for (let i = 0; i < tableDataArray.length; i++) { + if (i != index && tableDataArray[i].fieldName === fieldName) { + return true; + } + } + return false; + } + + const handleChange = () => { + props.handleChange?.(tableDataArray.filter(item => item.fieldName)); + } + + useEffect(() => { + if (props.modFiledArray) { + setTableDataArray(props.modFiledArray) + } + }, [props.modFiledArray]) + + const columns: TableProps['columns'] = [ + { + title: '描述*', + dataIndex: 'fieldDesc', + render: (text, _record, index) => { + if (props.isEdit) { + return ( + { + const item = tableDataArray[index]; + const value = e.target.value; + const fieldName = pinyin(value, { + pattern: 'first', + toneType: 'none', + type: 'array' + }).join(''); + if (isFiledNameExist(index, fieldName)) { + messageApi.open({ + type: 'error', + content: `字段名【${value}】已存在` + }); + item.fieldName = ''; + } else { + item.fieldDesc = value; + item.fieldName = fieldName; + } + setTableDataArray([...tableDataArray]); + handleChange(); + }}/> + ) + } else { + return text; + } + } + }, + { + title: '名称', + dataIndex: 'fieldName', + align: 'center', + width: 150, + }, + { + title: '操作', + dataIndex: 'option', + align: 'center', + width: 100, + render: (_text, _record, index) => { + if (props.isEdit) { + return ( + + ) + } else { + return '' + } + } + }, + ]; + + return ( + <> + {contextHolder} +
+ + { + props.isEdit ? ( +
+ +
+ ) : <> + } + + + + ) +} \ No newline at end of file diff --git a/src/components/modfield/modfield.css b/src/components/modfield/modfield.css new file mode 100644 index 0000000..0722176 --- /dev/null +++ b/src/components/modfield/modfield.css @@ -0,0 +1,7 @@ +.mod-field-container { + +} + +.mod-field-container .mod-field-table-btn { + margin-top: 15px; +} \ No newline at end of file diff --git a/src/layout/body/Body.tsx b/src/layout/body/Body.tsx index 8a9451e..d454e45 100644 --- a/src/layout/body/Body.tsx +++ b/src/layout/body/Body.tsx @@ -13,6 +13,8 @@ import ProjEditStep5 from "../../route/proj/edit/ProjEditStep5.tsx"; import ProjEditStep6 from "../../route/proj/edit/ProjEditStep6.tsx"; import ProjConfigLoginpage from "../../route/proj/edit/ProjConfigLoginpage.tsx"; import ProjConfigModList from "../../route/proj/edit/ProjConfigModList.tsx"; +import ProjConfigModSave from "../../route/proj/edit/ProjConfigModSave.tsx"; +import ProjConfigModEdit from "../../route/proj/edit/ProjConfigModEdit.tsx"; const router = createBrowserRouter([ { @@ -63,6 +65,14 @@ const router = createBrowserRouter([ path: '/proj-edit/config-mod-list/:projId', element: }, + { + path: '/proj-edit/config-mod-save/:projId', + element: + }, + { + path: '/proj-edit/config-mod-edit/:projId/:projModId', + element: + }, { path: '/agent-select/:projId', element: diff --git a/src/route/proj/edit/ProjConfigModEdit.tsx b/src/route/proj/edit/ProjConfigModEdit.tsx new file mode 100644 index 0000000..eda1389 --- /dev/null +++ b/src/route/proj/edit/ProjConfigModEdit.tsx @@ -0,0 +1,198 @@ +import './proj-config-list-mod.css'; +import { + Alert, + Breadcrumb, Button, Col, Flex, Form, Input, + message, Modal, Row, Spin, +} from "antd"; +import {Link, useNavigate, useParams} from "react-router-dom"; +import {useEffect, useState} from "react"; +import FaiconSelect from "../../../components/faicon/FaIconSelect.tsx"; +import ModField, {IModField} from "../../../components/modfield/ModField.tsx"; +import {get, put} from "../../../util/AjaxUtils.ts"; + +type FormFieldType = { + projId: string; + modName: string; + modDesc: string; + modIcon: string; + fields: IModField[]; +} + +export default function ProjConfigModEdit() { + const nav = useNavigate(); + const pathParams = useParams(); + const [messageApi, contextHolder] = message.useMessage(); + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + const [isModIconModalOpen, setIsModIconModalOpen] = useState(false); + const [isEditModalOpen, setIsEditModalOpen] = useState(false); + const [selectedModIcon, setSelectedModIcon] = useState('fa fa-list'); + const [fields, setFields] = useState([]); + + const height = window.innerHeight - 180; + + useEffect(() => { + get({ + messageApi, + url: `api/proj-mod/get/${pathParams.projModId}`, + onSuccess({data}) { + form.setFieldsValue({ + projId: data.projId, + modName: data.modName, + modDesc: data.modDesc, + modIcon: data.modIcon, + fields: data.fields, + }); + setSelectedModIcon(data.modIcon); + setFields(data.fields) + } + }) + }, []) + + return ( + <> + {contextHolder} + 首页}, + {title: 创建项目}, + {title: 编辑项目}, + {title: 系统菜单管理}, + {title: '编辑菜单'}, + ]} + /> +
+ +
+
{ + setIsEditModalOpen(true); + }} + autoComplete="off" + > + +
+ + label="菜单名称" + name="modName" + extra="此内容会作为菜单名生成菜单,最多8个字" + rules={[{required: true, message: '请输入菜单名称'}]} + > + + + + label="菜单说明" + name="modDesc" + extra="此内容会作为功能描述生成到操作手册中,请详细描述,最多600字" + rules={[{required: true, message: '请输入菜单说明'}]} + > + + + + label="菜单图标" + name="modIcon" + extra="菜单图标会显示在菜单栏中" + rules={[{required: true, message: '请输入菜单图标'}]} + > + + + + + + name="fields" + rules={[{required: true, message: '请添加菜单属性'}]} + > + { + if (!dataArray) { + return; + } + form.setFieldValue('fields', dataArray); + }}/> + + + + + + +
+ + + + +
+
+ + + + + { + setIsEditModalOpen(false); + put({ + messageApi, + url: `/api/proj-mod/update/${pathParams.projModId}`, + body: { + projId: form.getFieldValue('projId'), + modName: form.getFieldValue('modName'), + modDesc: form.getFieldValue('modDesc'), + modIcon: form.getFieldValue('modIcon'), + fields: form.getFieldValue('fields'), + }, + onBefore() { + setLoading(true); + }, + onSuccess() { + messageApi.success('编辑成功'); + }, + onFinally() { + setLoading(false); + } + }) + }} + onCancel={() => { + setIsEditModalOpen(false); + }}> +
确定提交吗?
+
+ { + setIsModIconModalOpen(false); + }}> +
+ { + form.setFieldValue('modIcon', icon); + setSelectedModIcon(icon); + }} + /> +
+
+ + + ) + +} \ No newline at end of file diff --git a/src/route/proj/edit/ProjConfigModList.tsx b/src/route/proj/edit/ProjConfigModList.tsx index 430887d..9e0b22b 100644 --- a/src/route/proj/edit/ProjConfigModList.tsx +++ b/src/route/proj/edit/ProjConfigModList.tsx @@ -20,51 +20,14 @@ interface DataType { modIcon: string; } -const columns: TableProps['columns'] = [ - { - title: '菜单标题', - dataIndex: 'modName', - align: 'center', - width: 180 - }, - { - title: '菜单说明', - dataIndex: 'modDesc', - align: 'center', - }, - { - title: '图标', - dataIndex: 'modIcon', - align: 'center', - width: 80 - }, - { - title: '菜单地址', - dataIndex: 'menuUrl', - align: 'center', - }, - { - title: '创建时间', - dataIndex: 'gmtCreate', - align: 'center', - width: 180 - }, - { - title: '操作', - dataIndex: 'option', - align: 'center', - width: 100 - }, -]; - export default function ProjConfigModList() { const nav = useNavigate(); const pathParams = useParams(); const [messageApi, contextHolder] = message.useMessage(); const [dataArray, setDataArray] = useState(); - const height = window.innerHeight - 210; + const height = window.innerHeight - 165; - useEffect(() => { + const renderData = () => { get({ messageApi, url: '/api/proj-mod/list', @@ -74,18 +37,70 @@ export default function ProjConfigModList() { } }, onSuccess({data}) { - data.forEach(item => { - item.modIcon = - item.option = ( - <> - - - - ); - }) setDataArray([...data]) } }) + } + + const columns: TableProps['columns'] = [ + { + title: '菜单标题', + dataIndex: 'modName', + align: 'center', + width: 180 + }, + { + title: '菜单说明', + dataIndex: 'modDesc', + align: 'center', + }, + { + title: '图标', + dataIndex: 'modIcon', + align: 'center', + width: 80, + render: (_text) => { + return + } + }, + { + title: '菜单地址', + dataIndex: 'menuUrl', + align: 'center', + }, + { + title: '创建时间', + dataIndex: 'gmtCreate', + align: 'center', + width: 180 + }, + { + title: '操作', + dataIndex: 'option', + align: 'center', + width: 100, + render: (_text, record, index) => { + return ( + <> + + + + ) + } + }, + ]; + + useEffect(() => { + renderData(); }, []) return ( @@ -100,13 +115,15 @@ export default function ProjConfigModList() { ]} />
- +
- - +
-
+
diff --git a/src/route/proj/edit/ProjConfigModSave.tsx b/src/route/proj/edit/ProjConfigModSave.tsx new file mode 100644 index 0000000..f4b457d --- /dev/null +++ b/src/route/proj/edit/ProjConfigModSave.tsx @@ -0,0 +1,185 @@ +import './proj-config-list-mod.css'; +import { + Alert, + Breadcrumb, Button, Col, Flex, Form, Input, + message, Modal, Row, Spin, +} from "antd"; +import {Link, useNavigate, useParams} from "react-router-dom"; +import {useEffect, useState} from "react"; +import FaiconSelect from "../../../components/faicon/FaIconSelect.tsx"; +import ModField, {IModField} from "../../../components/modfield/ModField.tsx"; +import {post} from "../../../util/AjaxUtils.ts"; + +type FormFieldType = { + modName: string; + modDesc: string; + modIcon: string; + fields: IModField[]; +} + +export default function ProjConfigModSave() { + const nav = useNavigate(); + const pathParams = useParams(); + const [messageApi, contextHolder] = message.useMessage(); + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + const [isModIconModalOpen, setIsModIconModalOpen] = useState(false); + const [isEditModalOpen, setIsEditModalOpen] = useState(false); + const [selectedModIcon, setSelectedModIcon] = useState('fa fa-list'); + + const height = window.innerHeight - 180; + + useEffect(() => { + form.setFieldsValue({ + modIcon: 'fa fa-list' + }) + }, []) + + return ( + <> + {contextHolder} + 首页}, + {title: 创建项目}, + {title: 编辑项目}, + {title: 系统菜单管理}, + {title: '添加菜单'}, + ]} + /> +
+ +
+
{ + setIsEditModalOpen(true); + }} + autoComplete="off" + > + +
+ + label="菜单名称" + name="modName" + extra="此内容会作为菜单名生成菜单,最多8个字" + rules={[{required: true, message: '请输入菜单名称'}]} + > + + + + label="菜单说明" + name="modDesc" + extra="此内容会作为功能描述生成到操作手册中,请详细描述,最多600字" + rules={[{required: true, message: '请输入菜单说明'}]} + > + + + + label="菜单图标" + name="modIcon" + extra="菜单图标会显示在菜单栏中" + rules={[{required: true, message: '请输入菜单图标'}]} + > + + + + + + name="fields" + rules={[{required: true, message: '请添加菜单属性'}]} + > + { + if (!dataArray) { + return; + } + form.setFieldValue('fields', dataArray); + }}/> + + + + + + +
+ + + + +
+
+ + + + + { + setIsEditModalOpen(false); + post({ + messageApi, + url: `/api/proj-mod/save`, + body: { + projId: pathParams.projId, + modName: form.getFieldValue('modName'), + modDesc: form.getFieldValue('modDesc'), + modIcon: form.getFieldValue('modIcon'), + fields: form.getFieldValue('fields'), + }, + onBefore() { + setLoading(true); + }, + onSuccess() { + messageApi.success('添加成功'); + nav(0); + }, + onFinally() { + setLoading(false); + } + }) + }} + onCancel={() => { + setIsEditModalOpen(false); + }}> +
确定提交吗?
+
+ { + setIsModIconModalOpen(false); + }}> +
+ { + form.setFieldValue('modIcon', icon); + setSelectedModIcon(icon); + }} + /> +
+
+ + + ) + +} \ No newline at end of file diff --git a/src/route/proj/edit/proj-config-list-mod.css b/src/route/proj/edit/proj-config-list-mod.css index 7719d8c..afc2dc5 100644 --- a/src/route/proj/edit/proj-config-list-mod.css +++ b/src/route/proj/edit/proj-config-list-mod.css @@ -10,4 +10,13 @@ .mod-list-container .mod-list .table-btn-group .ant-btn { margin-right: 5px; +} + +.mod-edit-container { + background-color: var(--color-light); + padding: 15px; +} + +.mod-edit-container .mod-content { + margin-top: 15px; } \ No newline at end of file