diff --git a/src/models/index.ts b/src/models/index.ts index bec252b..005ffed 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -8,6 +8,7 @@ export * from './template.data.ts' export * from './platform-product.data.ts' export * from './notice.data.ts' export * from './logs.data.ts' +export * from './system-dict.data.ts' export interface ResponseDTO{ code: number; diff --git a/src/models/system-dict.data.ts b/src/models/system-dict.data.ts new file mode 100644 index 0000000..8d63347 --- /dev/null +++ b/src/models/system-dict.data.ts @@ -0,0 +1,47 @@ +export type DictTypeVO = { + id: number + name: string + type: string + status: number + remark: string + createTime: Date +} + +export interface DictTypePageReqVO extends PageParam { + name?: string + type?: string + status?: number + createTime?: Date[] +} + +export type DictTypeExportReqVO = { + name: string + type: string + status: number + createTime: Date[] +} + +export type DictDataVO = { + id: number + sort: number + label: string + value: string + dictType: string + status: number + colorType: string + cssClass: string + remark: string + createTime: Date +} + +export interface DictDataPageReqVO extends PageParam { + label?: string + dictType: string + status?: number +} + +export type DictDataExportReqVO = { + label: string + dictType: string + status: number +} diff --git a/src/pages/custom/template/dict/dict-detail-editor.tsx b/src/pages/custom/template/dict/dict-detail-editor.tsx index 715cbf7..588c804 100644 --- a/src/pages/custom/template/dict/dict-detail-editor.tsx +++ b/src/pages/custom/template/dict/dict-detail-editor.tsx @@ -1,14 +1,13 @@ import React, { useEffect, useState } from 'react' import { Form, Input, InputNumber, Modal } from 'antd'; -import dictService from '@/request/service/dict'; +import dictService from '@/request/service/template-dict'; import { useRequest } from '@/hooks/use-request'; import type { DataDictDetailVO } from '@/models' import { antdUtils } from '@/utils/antd'; const layout = { labelCol: { span: 4, }, - wrapperCol: { span: 16 }, - bordered: false, + wrapperCol: { span: 16 } }; export default (props: { diff --git a/src/pages/custom/template/dict/dict-editor.tsx b/src/pages/custom/template/dict/dict-editor.tsx index 2d4fe17..96b6baa 100644 --- a/src/pages/custom/template/dict/dict-editor.tsx +++ b/src/pages/custom/template/dict/dict-editor.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react' import { Form, Input, Modal } from 'antd'; -import dictService from '@/request/service/dict'; +import dictService from '@/request/service/template-dict'; import { useRequest } from '@/hooks/use-request'; import type { DataDictVO } from '@/models' import { antdUtils } from '@/utils/antd'; @@ -8,7 +8,6 @@ import { antdUtils } from '@/utils/antd'; const layout = { labelCol: { span: 4, }, wrapperCol: { span: 16 }, - bordered: false, }; export default (props: { diff --git a/src/pages/system/dept/create-department.tsx b/src/pages/system/dept/create-department.tsx index 0a8783c..a96d115 100644 --- a/src/pages/system/dept/create-department.tsx +++ b/src/pages/system/dept/create-department.tsx @@ -8,7 +8,6 @@ import { antdUtils } from '@/utils/antd'; const layout = { labelCol: { span: 4, }, wrapperCol: { span: 16 }, - bordered: false, }; interface EditorProps { diff --git a/src/pages/system/dict/dict-data-editor.tsx b/src/pages/system/dict/dict-data-editor.tsx new file mode 100644 index 0000000..7ed3257 --- /dev/null +++ b/src/pages/system/dict/dict-data-editor.tsx @@ -0,0 +1,137 @@ +import React, { useEffect, useState } from 'react' +import { Form, Input, Radio, Modal, InputNumber, ColorPicker } from 'antd'; +import dictDataService from '@/request/service/system-dictdata'; +import { useRequest } from '@/hooks/use-request'; +import type { DictDataVO } from '@/models' +import { antdUtils } from '@/utils/antd'; + + +const layout = { + labelCol: { span: 4, }, + wrapperCol: { span: 16 } +}; + +interface DataTypeEditorProps { + visible: boolean; + onCancel: (flag?: boolean) => void; + onSave: (role: DictDataVO) => void; + data?: DictDataVO | null; + dictType: string; +} + +export default (props: DataTypeEditorProps) => { + + const { visible, onCancel, onSave, data, dictType } = props; + const { runAsync: update } = useRequest(dictDataService.updateDictDataApi, { manual: true }); + const { runAsync: create } = useRequest(dictDataService.createDictDataApi, { manual: true }); + + const isEdit = !!data; + + + const [saveLoading, setSaveLoading] = useState(false); + const [form] = Form.useForm(); + + useEffect(() => { + if (visible) { + if (data) { + form.setFieldsValue(data); + } + form.setFieldValue('dictType', dictType); + } else { + form.resetFields(); + } + }, [visible]); + + const save = async () => { + setSaveLoading(true); + const fieldValues = form.getFieldsValue(); + const cssClass = fieldValues.cssClass ? fieldValues.cssClass.toHex() : ''; + const newValue = isEdit ? { ...data, ...fieldValues, cssClass } : fieldValues; + const [error, { msg, code }] = isEdit ? await update(newValue) : await create(newValue); + if (!error && code === 0) { + onSave(newValue); + } else { + antdUtils.message?.open({ + type: 'error', + content: msg ?? '操作失败', + }); + } + setSaveLoading(false); + } + + return ( + <> + onCancel()} + confirmLoading={saveLoading} + destroyOnClose + > +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + ) +}; diff --git a/src/pages/system/dict/dict-data.tsx b/src/pages/system/dict/dict-data.tsx new file mode 100644 index 0000000..b7048ce --- /dev/null +++ b/src/pages/system/dict/dict-data.tsx @@ -0,0 +1,206 @@ +import React, { useState, useEffect } from 'react'; +import { Space, Table, Button, Badge, Divider, Input } from 'antd'; +import { useSetState } from 'ahooks'; +import type { TableProps, ColumnsType, ColumnType } from 'antd/es/table'; +import { t } from '@/utils/i18n'; +import { PlusOutlined, ExclamationCircleFilled, SearchOutlined } from '@ant-design/icons'; +import { antdUtils } from '@/utils/antd'; +import { useRequest } from '@/hooks/use-request'; +import type { DictTypeVO, DictDataVO, DictDataPageReqVO } from '@/models' +import dictDataService from '@/request/service/system-dictdata'; +import DictDataEditor from './dict-data-editor'; + +type DataIndex = keyof DictDataVO; + +interface DictDataProps { + data?: DictTypeVO | null; +} + +export default (props: DictDataProps) => { + const { data } = props; + const showDeleteConfirm = (item: DictDataVO) => { + antdUtils.modal?.confirm({ + title: `确认删除值为 ${item.value} 的字典吗?`, + icon: , + content: '请注意删除以后不可恢复!', + okText: '删除', + okType: 'danger', + cancelText: '取消', + onOk() { + return new Promise(async (resolve) => { + const [error, { code, msg }] = await deleteDictDataApi(item.id); + if (error || code !== 0) { + antdUtils.message?.open({ type: 'error', content: msg ?? '操作失败' }) + } else { + antdUtils.message?.open({ type: 'success', content: '删除成功' }) + } + await load(); + resolve('') + }).catch(() => antdUtils.message?.open({ + type: 'error', + content: '操作失败', + })); + }, + onCancel() { + }, + }); + }; + + const [editorVisable, seEditorVisable] = useState(false); + const [editData, seEditData] = useState(); + const [dataSource, setDataSource] = useState([]); + const [total, setTotal] = useState(0); + const [searchState, setSearchState] = useSetState({ dictType: data?.type ?? "" }); + const [onSearching, setOnSearching] = useState(false); + + const { runAsync: getDictDataPageApi } = useRequest(dictDataService.getDictDataPageApi, { manual: true }); + const { runAsync: deleteDictDataApi } = useRequest(dictDataService.deleteDictDataApi, { manual: true }); + + const showEditor = (record: DictDataVO | undefined) => { + seEditData(record); + seEditorVisable(true); + } + + const load = async () => { + setOnSearching(true); + const [error, { data }] = await getDictDataPageApi(searchState); + setOnSearching(false); + if (!error) { + setDataSource(data.list); + setTotal(data.total); + } + }; + + const getColumnSearchProps = (dataIndex: DataIndex): ColumnType => ({ + filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters, close }) => ( +
e.stopPropagation()}> + { + setSelectedKeys(e.target.value && e.target.value !== '' ? [e.target.value] : []) + }} + onSearch={(value) => { + if (value === '' && clearFilters) { + clearFilters!!() + } + confirm(); + }} + onPressEnter={() => confirm()} + allowClear + style={{ marginBottom: 8, display: 'block' }} + enterButton="搜索" + size="middle" + loading={onSearching} + /> +
+ ), + filterIcon: (filtered: boolean) => ( + + ), + }); + + const columns: ColumnsType = [ + { + title: '数据标签', + dataIndex: 'label', + key: 'label', + align: 'center', + width: 150, + ...getColumnSearchProps('label') + }, + { + title: '数据键值', + dataIndex: 'value', + key: 'value', + align: 'center', + width: 120, + }, + + { + title: '状态', + key: 'status', + dataIndex: 'status', + align: 'center', + width: 120, + render: (value: number) => { + return (value === 0 ? : ) + } + }, + { + title: t("QkOmYwne" /* 操作 */), + key: 'action', + align: 'center', + render: (_, record) => ( + + )}> + showEditor(record)}> + 编辑 + + { + showDeleteConfirm(record) + }}> + 删除 + + + ), + width: 150, + }, + ]; + + useEffect(() => { + load(); + }, [searchState]); + + useEffect(() => { + if (props.data) + setSearchState({ dictType: props.data!!.type }) + }, [props]) + + const onChange: TableProps['onChange'] = (pagination, filters, sorter, extra) => { + const state: DictDataPageReqVO = { + label: filters.label ? filters.label[0] as string : undefined, + dictType: props.data?.type ?? "", + pageNo: pagination.current ?? 1, + pageSize: pagination.pageSize + } + setSearchState(state); + }; + + return ( + <> +
+ {data ? (
+ +
) : null} + + + + { + load(); + seEditorVisable(false); + }} + onCancel={() => { seEditorVisable(false) }} + visible={editorVisable} + data={editData} + dictType={data?.type ?? ""} /> + + + ); +}; diff --git a/src/pages/system/dict/dict-type-editor.tsx b/src/pages/system/dict/dict-type-editor.tsx new file mode 100644 index 0000000..d36014b --- /dev/null +++ b/src/pages/system/dict/dict-type-editor.tsx @@ -0,0 +1,115 @@ +import React, { useEffect, useState } from 'react' +import { Form, Input, Radio, Modal } from 'antd'; +import dictTypeService from '@/request/service/system-dicttype'; +import { useRequest } from '@/hooks/use-request'; +import type { DictTypeVO } from '@/models' +import { antdUtils } from '@/utils/antd'; + + +const layout = { + labelCol: { span: 4, }, + wrapperCol: { span: 16 } +}; + +interface DataTypeEditorProps { + visible: boolean; + onCancel: (flag?: boolean) => void; + onSave: (role: DictTypeVO) => void; + data?: DictTypeVO | null; +} + +export default (props: DataTypeEditorProps) => { + + const { visible, onCancel, onSave, data } = props; + + const { runAsync: update } = useRequest(dictTypeService.updateDictTypeApi, { manual: true }); + const { runAsync: create } = useRequest(dictTypeService.createDictTypeApi, { manual: true }); + + const isEdit = !!data; + + + const [saveLoading, setSaveLoading] = useState(false); + const [form] = Form.useForm(); + + useEffect(() => { + if (visible) { + if (data) { + form.setFieldsValue(data); + } + } else { + form.resetFields(); + } + }, [visible]); + + const save = async () => { + setSaveLoading(true); + const fieldValues = form.getFieldsValue(); + const newValue = isEdit ? { ...data, ...fieldValues } : fieldValues; + const [error, { msg, code }] = isEdit ? await update(newValue) : await create(newValue); + if (!error && code === 0) { + onSave(newValue); + } else { + antdUtils.message?.open({ + type: 'error', + content: msg ?? '操作失败', + }); + } + setSaveLoading(false); + } + + return ( + <> + onCancel()} + confirmLoading= {saveLoading} + destroyOnClose + > +
+ + + + + + + + + + + + + + + + + +
+ + ) +}; diff --git a/src/pages/system/dict/dict-type.tsx b/src/pages/system/dict/dict-type.tsx new file mode 100644 index 0000000..4c7069a --- /dev/null +++ b/src/pages/system/dict/dict-type.tsx @@ -0,0 +1,213 @@ +import React, { useState, useEffect, useMemo } from 'react'; +import { Space, Table, Button, Badge, Divider, Input } from 'antd'; +import { useSetState } from 'ahooks'; +import type { TableProps, ColumnsType, ColumnType } from 'antd/es/table'; +import { t } from '@/utils/i18n'; +import { PlusOutlined, ExclamationCircleFilled, SearchOutlined } from '@ant-design/icons'; +import { antdUtils } from '@/utils/antd'; +import { useRequest } from '@/hooks/use-request'; +import type { DictTypeVO, DictTypePageReqVO } from '@/models' +import dictTypeService from '@/request/service/system-dicttype'; +import DictTypeEditor from './dict-type-editor'; + +type DataIndex = keyof DictTypeVO; + +interface DictTypeProps { + onSelectDictType?: (data: DictTypeVO) => void; +} + +export default (props: DictTypeProps) => { + const showDeleteConfirm = (item: DictTypeVO) => { + antdUtils.modal?.confirm({ + title: `确认删除名称为: "${item.name}" 的字典吗?`, + icon: , + content: '请注意删除以后不可恢复!', + okText: '删除', + okType: 'danger', + cancelText: '取消', + onOk() { + return new Promise(async (resolve, reject) => { + const [error, { code, msg }] = await deleteDictTypeApi(item.id); + if (error || code !== 0) { + antdUtils.message?.open({ type: 'error', content: msg ?? '操作失败' }) + } else { + antdUtils.message?.open({ type: 'success', content: '删除成功' }) + } + await load(); + resolve(''); + }).catch(() => antdUtils.message?.open({ + type: 'error', + content: '操作失败', + })); + }, + onCancel() { + }, + }); + }; + + const [editorVisable, seEditorVisable] = useState(false); + const [editData, seEditData] = useState(); + const [dataSource, setDataSource] = useState([]); + const [total, setTotal] = useState(0); + const [searchState, setSearchState] = useSetState({}); + const [onSearching, setOnSearching] = useState(false); + + const { runAsync: getDictTypePageApi } = useRequest(dictTypeService.getDictTypePageApi, { manual: true }); + const { runAsync: deleteDictTypeApi } = useRequest(dictTypeService.deleteDictTypeApi, { manual: true }); + + const showEditor = (record: DictTypeVO | undefined)=> { + seEditData(record); + seEditorVisable(true); + } + + const load = async () => { + const [error, { data, code, msg }] = await getDictTypePageApi(searchState); + setOnSearching(false); + if (!error) { + setDataSource(data.list); + setTotal(data.total); + } else { + if (error || code !== 0) { + antdUtils.message?.open({ type: 'error', content: msg ?? '操作失败' }); + return + } + } + }; + + const getColumnSearchProps = (dataIndex: DataIndex): ColumnType => ({ + filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters, close }) => ( +
e.stopPropagation()}> + { + setSelectedKeys(e.target.value && e.target.value !== '' ? [e.target.value] : []) + }} + onSearch={(value) => { + if (value === '' && clearFilters) { + clearFilters!!() + } + confirm(); + }} + onPressEnter={() => confirm()} + allowClear + style={{ marginBottom: 8, display: 'block' }} + enterButton="搜索" + size="middle" + loading={onSearching} + /> +
+ ), + filterIcon: (filtered: boolean) => ( + + ), + }); + + const columns: ColumnsType = [ + { + title: '字典名称', + dataIndex: 'name', + key: 'name', + align: 'center', + width: 150, + ...getColumnSearchProps('name') + }, + { + title: '字典类型', + dataIndex: 'type', + key: 'type', + align: 'center', + width: 120, + ...getColumnSearchProps('type') + }, + + { + title: '状态', + key: 'status', + dataIndex: 'status', + align: 'center', + width: 120, + render: (value: number) => { + return (value === 0 ? : ) + } + }, + { + title: t("QkOmYwne" /* 操作 */), + key: 'action', + align: 'center', + render: (_, record) => ( + + )}> + showEditor(record) }> + 编辑 + + { + showDeleteConfirm(record) + }}> + 删除 + + + ), + width: 150, + }, + ]; + + useEffect(() => { + load(); + }, [searchState]); + + useMemo(() => { + console.log('onMemChanged: ' + editData) + }, [editData]) + + const onChange: TableProps['onChange'] = (pagination, filters, sorter, extra) => { + const state: DictTypePageReqVO = { + name: filters.name ? filters.name[0] as string : undefined, + type: filters.type ? filters.type[0] as string : undefined, + pageNo: pagination.current, + pageSize: pagination.pageSize + } + setSearchState(state); + }; + + return ( + <> +
+
+ +
+
{ + if (select && props.onSelectDictType) + props.onSelectDictType!!(record) + } + }} + className='bg-transparent' + pagination={{ + pageSize: searchState.pageSize, + current: searchState.pageNo, + total, + position: ['bottomRight'] + }} + /> + + { + load(); + seEditorVisable(false); + }} + onCancel={() => { seEditorVisable(false) }} + visible={editorVisable} + data={editData}/> + + ); +}; diff --git a/src/pages/system/dict/index.tsx b/src/pages/system/dict/index.tsx new file mode 100644 index 0000000..876041d --- /dev/null +++ b/src/pages/system/dict/index.tsx @@ -0,0 +1,31 @@ +import React, { useState } from 'react'; +import { Card } from 'antd'; +import type { DictTypeVO } from '@/models' +import DictData from './dict-data'; +import DictType from './dict-type'; + + +export default () => { + const [ currentDictType, setCurrentDictType ] = useState() + return ( + <> +
+ + setCurrentDictType(data)}/> + + + + +
+ + ); +}; diff --git a/src/pages/system/menu/menu-editor.tsx b/src/pages/system/menu/menu-editor.tsx index 4290d05..f7d3a35 100644 --- a/src/pages/system/menu/menu-editor.tsx +++ b/src/pages/system/menu/menu-editor.tsx @@ -73,7 +73,6 @@ const MenuIcon: React.FC = ({ value = '', onChange }) => { const layout = { labelCol: { span: 4, }, wrapperCol: { span: 16 }, - bordered: false, }; interface MenuEditorProps { diff --git a/src/pages/system/post/create-position.tsx b/src/pages/system/post/create-position.tsx index bfffa1f..201e272 100644 --- a/src/pages/system/post/create-position.tsx +++ b/src/pages/system/post/create-position.tsx @@ -8,7 +8,6 @@ import { antdUtils } from '@/utils/antd'; const layout = { labelCol: { span: 4, }, wrapperCol: { span: 16 }, - bordered: false, }; interface EditorProps { diff --git a/src/pages/system/role/role-editor.tsx b/src/pages/system/role/role-editor.tsx index 31a6d2a..f55a755 100644 --- a/src/pages/system/role/role-editor.tsx +++ b/src/pages/system/role/role-editor.tsx @@ -9,7 +9,6 @@ import { antdUtils } from '@/utils/antd'; const layout = { labelCol: { span: 4, }, wrapperCol: { span: 16 }, - bordered: false, }; interface RoleEditorProps { diff --git a/src/pages/system/tenant/package/tenant-pkg-editor.tsx b/src/pages/system/tenant/package/tenant-pkg-editor.tsx index 127705b..4e3c931 100644 --- a/src/pages/system/tenant/package/tenant-pkg-editor.tsx +++ b/src/pages/system/tenant/package/tenant-pkg-editor.tsx @@ -10,7 +10,6 @@ import { antdUtils } from '@/utils/antd'; const layout = { labelCol: { span: 4, }, wrapperCol: { span: 16 }, - bordered: false, }; interface EditorProps { diff --git a/src/request/service/system-dictdata.ts b/src/request/service/system-dictdata.ts new file mode 100644 index 0000000..7124ddf --- /dev/null +++ b/src/request/service/system-dictdata.ts @@ -0,0 +1,41 @@ +import request from '@/request'; +import { DictDataVO, DictDataPageReqVO, DictDataExportReqVO, PageData } from '@/models'; + +const BASE_URL = '/admin-api/system/dict-data'; + +export default { + + // 查询字典数据(精简)列表 + listSimpleDictDataApi: () => { + return request.get(`${BASE_URL}/list-all-simple`) + }, + + // 查询字典数据列表 + getDictDataPageApi: (params: DictDataPageReqVO) => { + return request.get>(`${BASE_URL}/page`, { params }) + }, + + // 查询字典数据详情 + getDictDataApi: (id: number) => { + return request.get(`${BASE_URL}/get?id=${id}`) + }, + + // 新增字典数据 + createDictDataApi: (data: DictDataVO) => { + return request.post(`${BASE_URL}/create`, data) + }, + + // 修改字典数据 + updateDictDataApi: (data: DictDataVO) => { + return request.put(`${BASE_URL}/update`, data) + }, + + // 删除字典数据 + deleteDictDataApi: (id: number) => { + return request.delete(`${BASE_URL}/delete?id=${id}`) + }, + // 导出字典类型数据 + exportDictDataApi: (params: DictDataExportReqVO) => { + return request.get(`${BASE_URL}/export`, { params }) + } +}; diff --git a/src/request/service/system-dicttype.ts b/src/request/service/system-dicttype.ts new file mode 100644 index 0000000..cbae42b --- /dev/null +++ b/src/request/service/system-dicttype.ts @@ -0,0 +1,43 @@ +import request from '@/request'; +import { DictTypeVO, DictTypePageReqVO, DictTypeExportReqVO, PageData } from '@/models'; + +const BASE_URL = '/admin-api/system/dict-type'; + +export default { + + // 查询字典(精简)列表 + listSimpleDictTypeApi: () => { + return request.get(`${BASE_URL}/list-all-simple`) + }, + + // 查询字典列表 + getDictTypePageApi: (params: DictTypePageReqVO) => { + return request.get>(`${BASE_URL}/page`, { params }) + }, + + // 查询字典详情 + getDictTypeApi: (id: number) => { + return request.get(`${BASE_URL}/get?id=${id}`) + }, + + // 新增字典 + createDictTypeApi: (data: DictTypeVO) => { + return request.post(`${BASE_URL}/create`, data) + }, + + // 修改字典 + updateDictTypeApi: (data: DictTypeVO) => { + return request.put(`${BASE_URL}/update`, data) + }, + + // 删除字典 + deleteDictTypeApi: (id: number) => { + return request.delete(`${BASE_URL}/delete?id=${id}`) + }, + // 导出字典类型 + exportDictTypeApi: (params: DictTypeExportReqVO) => { + return request.get(`${BASE_URL}/export`, { params }) + } +}; + + diff --git a/src/request/service/dict.ts b/src/request/service/template-dict.ts similarity index 100% rename from src/request/service/dict.ts rename to src/request/service/template-dict.ts