From 9363dc0d6ea6b2bb2bb6e10f60a8d5b840f85098 Mon Sep 17 00:00:00 2001 From: qianpw <2233607957@qq.com> Date: Tue, 23 Sep 2025 16:00:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=B3=BB=E7=BB=9F=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + src/constants/index.ts | 33 +- src/pages/system/dept/config.tsx | 170 ++++++++ src/pages/system/dept/index.tsx | 133 +++++- src/pages/system/dict/config.tsx | 115 ++++++ src/pages/system/dict/index.tsx | 128 +++++- src/pages/system/menu/config.tsx | 125 ++++++ src/pages/system/menu/index.tsx | 127 +++++- src/pages/system/post/config.tsx | 121 ++++++ src/pages/system/post/index.tsx | 129 +++++- src/pages/system/role/config.tsx | 143 +++++++ src/pages/system/role/index.tsx | 129 +++++- src/pages/system/tenant/list/config.tsx | 168 ++++---- src/pages/system/tenant/list/index.tsx | 62 ++- src/pages/system/tenant/package/config.tsx | 109 +++++ src/pages/system/tenant/package/index.tsx | 134 +++++- src/pages/system/user/config.tsx | 220 ++++++++++ src/pages/system/user/index.tsx | 127 +++++- src/services/system/dept/index.ts | 89 ++++ src/services/system/dict/dict.data.ts | 96 +++++ src/services/system/dict/dict.type.ts | 101 +++++ src/services/system/menu/index.ts | 49 +++ src/services/system/post/index.ts | 87 ++++ src/services/system/role/index.ts | 106 +++++ src/services/system/tenant/package.ts | 16 + src/services/system/user/index.ts | 154 +++++++ src/types/constant.ts | 5 + src/utils/constant.ts | 22 + src/utils/tree.ts | 451 +++++++++++++++++++++ 29 files changed, 3227 insertions(+), 123 deletions(-) create mode 100644 src/pages/system/dept/config.tsx create mode 100644 src/pages/system/dict/config.tsx create mode 100644 src/pages/system/menu/config.tsx create mode 100644 src/pages/system/post/config.tsx create mode 100644 src/pages/system/role/config.tsx create mode 100644 src/pages/system/tenant/package/config.tsx create mode 100644 src/pages/system/user/config.tsx create mode 100644 src/services/system/dept/index.ts create mode 100644 src/services/system/dict/dict.data.ts create mode 100644 src/services/system/dict/dict.type.ts create mode 100644 src/services/system/post/index.ts create mode 100644 src/services/system/role/index.ts create mode 100644 src/services/system/user/index.ts create mode 100644 src/types/constant.ts create mode 100644 src/utils/constant.ts create mode 100644 src/utils/tree.ts diff --git a/package.json b/package.json index a863fba..bfd74e2 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "jest": "jest", "lint": "npm run biome:lint && npm run tsc", "lint-staged": "lint-staged", + "lint:fix": "eslint --fix --quiet", "biome:lint": "npx @biomejs/biome lint", "openapi": "max openapi", "prepare": "husky", diff --git a/src/constants/index.ts b/src/constants/index.ts index 043f68d..81db288 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1 +1,32 @@ -export const formStatusType = { create: "创建", update: "编辑" }; +export const formStatusType = { create: '创建', update: '编辑' }; + +export const tenantStatus = [ + { label: '开启', value: 0 }, + { label: '关闭', value: 1 }, +]; + +export const modlaTypeText: { [key: string]: string } = { + edit: '编辑', + add: '新增', + menu: '菜单权限', + data: '数据权限', + prod_status: '商品状态控制', + prod_audit: '商品上架审核', + prod_sku: '', +}; + +export const SystemDataScopeEnum = { + ALL: 1, // 全部数据权限 + DEPT_CUSTOM: 2, // 指定部门数据权限 + DEPT_ONLY: 3, // 部门数据权限 + DEPT_AND_CHILD: 4, // 部门及以下数据权限 + DEPT_SELF: 5, // 仅本人数据权限 +}; + +export const systemDataScopetStatus = [ + { label: '全部数据权限', value: 1 }, + { label: '指定部门数据权限', value: 2 }, + { label: '部门数据权限', value: 3 }, + { label: '部门及以下数据权限', value: 4 }, + { label: '仅本人数据权限', value: 5 }, +]; diff --git a/src/pages/system/dept/config.tsx b/src/pages/system/dept/config.tsx new file mode 100644 index 0000000..52b524c --- /dev/null +++ b/src/pages/system/dept/config.tsx @@ -0,0 +1,170 @@ +import type { + ProColumns, + ProFormColumnsType, +} from '@ant-design/pro-components'; +import { Tag } from 'antd'; +import dayjs from 'dayjs'; +import { tenantStatus } from '@/constants'; +import type { DeptVO } from '@/services/system/dept'; +import { getStatusLabel } from '@/utils/constant'; + +export const baseDeptColumns: ProColumns[] = [ + { + title: '部门名称', + dataIndex: 'name', + ellipsis: true, + hideInSearch: true, + }, + // { prop: "leaderUserId", label: "负责人", width: 100 }, + { + title: '负责人', + dataIndex: 'leaderUserId', + hideInSearch: true, + }, + // { prop: "sort", label: "排序", width: 100 }, + { + title: '排序', + dataIndex: 'sort', + hideInSearch: true, + }, + // { + // prop: "status", + // label: "状态", + // width: 100, + // render: (record) => ( + // + // {getStatusLabel(tenantStatus, record.status)} + // + // ) + // }, + { + title: '状态', + dataIndex: 'status', + hideInSearch: true, + render: (_, record: DeptVO) => [ + + {getStatusLabel(tenantStatus, record.status)} + , + ], + }, + // { + // prop: "createTime", + // label: "创建时间", + // width: 160, + // render: (row) => dateFormatter(row.createTime) + // }, + { + title: '创建时间', + dataIndex: 'createTime', + valueType: 'dateRange', + search: { + transform: (value) => { + return { + 'createTime[0]': dayjs(value[0]) + .startOf('day') + .format('YYYY-MM-DD HH:mm:ss'), + 'createTime[1]': dayjs(value[1]) + .endOf('day') + .format('YYYY-MM-DD HH:mm:ss'), + }; + }, + }, + render: (_, record: DeptVO) => + dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss'), + }, +]; + +export const formColumns = (_type: string): ProFormColumnsType[] => [ + { + title: '上级部门', + dataIndex: 'parentId', + valueType: 'treeSelect', + fieldProps: () => { + return { + multiple: true, + placeholder: '请选择上级部门', + options: [{ label: '11', value: 5016 }], + }; + }, + }, + { + title: '部门名称', + dataIndex: 'name', + valueType: 'text', + formItemProps: { + rules: [ + { + required: true, + message: '请输入部门名称', + }, + ], + }, + }, + { + title: '显示顺序', + dataIndex: 'sort', + valueType: 'digit', + fieldProps: { + min: 0, + }, + formItemProps: { + rules: [ + { + required: true, + message: '请输入显示顺序', + }, + ], + }, + }, + { + title: '负责人', + dataIndex: 'leaderUserId', + formItemProps: { + rules: [ + { + required: true, + message: '请选择负责人', + }, + ], + }, + }, + { + title: '邮箱', + dataIndex: 'email', + formItemProps: { + rules: [ + { + required: true, + message: '请输入邮箱', + }, + ], + }, + }, + { + title: '状态', + dataIndex: 'status', + valueType: 'select', + fieldProps: { + options: [ + { + label: '正常', + value: 1, + }, + { + label: '禁用', + value: 2, + }, + ], + }, + formItemProps: { + rules: [ + { + required: true, + }, + ], + }, + }, +]; diff --git a/src/pages/system/dept/index.tsx b/src/pages/system/dept/index.tsx index 6aa7795..fa6788e 100644 --- a/src/pages/system/dept/index.tsx +++ b/src/pages/system/dept/index.tsx @@ -1,5 +1,136 @@ +import { PlusOutlined } from '@ant-design/icons'; +import type { ActionType, ProColumns } from '@ant-design/pro-components'; +import { Popconfirm } from 'antd'; +import React, { useCallback, useRef, useState } from 'react'; +import ConfigurableDrawerForm, { + type ConfigurableDrawerFormRef, +} from '@/components/DrawerForm'; +import EnhancedProTable from '@/components/EnhancedProTable'; +import type { ToolbarAction } from '@/components/EnhancedProTable/types'; +import { formStatusType } from '@/constants'; +import { + createDept, + type DeptReqVO, + type DeptVO, + deleteDept, + getDeptPage, + updateDept, +} from '@/services/system/dept'; +import { handleTree } from '@/utils/tree'; +import { baseDeptColumns, formColumns } from './config'; + const SyStemDept = () => { - return <>SyStemDept; + const configurableDrawerRef = useRef(null); + const tableRef = useRef(null); + + const [type, setType] = useState<'create' | 'update'>('create'); + const [id, setId] = useState(0); + + const handleEdit = (record: DeptVO) => { + setType('update'); + setId(record.id); + + configurableDrawerRef.current?.open(record); + }; + + const onFetch = async ( + params: DeptReqVO & { + pageSize?: number; + current?: number; + }, + ) => { + const data = await getDeptPage({ + ...params, + pageNo: params.current, + pageSize: params.pageSize, + }); + return { + data: handleTree(data), + success: true, + total: data.total, + }; + }; + + const handleAdd = () => { + setType('create'); + configurableDrawerRef.current?.open({}); + }; + + const handleSubmit = useCallback( + async (values: DeptVO) => { + if (type === 'create') { + await createDept(values); + } else { + await updateDept({ + ...values, + id, + }); + } + tableRef.current?.reload(); + return true; + }, + [type, id], + ); + + const toolbarActions: ToolbarAction[] = [ + { + key: 'add', + label: '新建', + type: 'primary', + icon: , + onClick: handleAdd, + }, + ]; + + const actionColumns: ProColumns = { + title: '操作', + dataIndex: 'option', + valueType: 'option', + fixed: 'right', + width: 120, + render: (_text: React.ReactNode, record: DeptVO, _: any, action: any) => [ + handleEdit(record)}> + 编辑 + , + { + await deleteDept(record.id); + action?.reload(); + }} + okText="是" + cancelText="否" + > + 删除 + , + ], + }; + + const columns = [...baseDeptColumns, actionColumns]; + return ( + <> + + ref={tableRef} + columns={columns} + request={onFetch} + toolbarActions={toolbarActions} + headerTitle="租户列表" + showIndex={false} + showSelection={false} + expandable={{ + defaultExpandAllRows: true, + }} + /> + + + + ); }; export default SyStemDept; diff --git a/src/pages/system/dict/config.tsx b/src/pages/system/dict/config.tsx new file mode 100644 index 0000000..c2092d8 --- /dev/null +++ b/src/pages/system/dict/config.tsx @@ -0,0 +1,115 @@ +import type { + ProColumns, + ProFormColumnsType, +} from '@ant-design/pro-components'; +import { Tag } from 'antd'; +import dayjs from 'dayjs'; +import { tenantStatus } from '@/constants'; +import type { DictTypeVO } from '@/services/system/dict/dict.type'; + +export const baseDictTypeColumns: ProColumns[] = [ + { + title: '字典编号', + dataIndex: 'id', + width: 80, + align: 'left', + }, + { + title: '字典名称', + dataIndex: 'name', + width: 120, + }, + { + title: '字典类型', + dataIndex: 'type', + width: 120, + }, + { + title: '状态', + dataIndex: 'status', + width: 100, + render: (_, record) => ( + + {record.status === 1 ? '正常' : '禁用'} + + ), + }, + { + title: '备注', + dataIndex: 'remark', + width: 120, + }, + { + title: '创建时间', + dataIndex: 'createTime', + valueType: 'dateRange', + search: { + transform: (value) => { + return { + 'createTime[0]': dayjs(value[0]) + .startOf('day') + .format('YYYY-MM-DD HH:mm:ss'), + 'createTime[1]': dayjs(value[1]) + .endOf('day') + .format('YYYY-MM-DD HH:mm:ss'), + }; + }, + }, + render: (_, record: DictTypeVO) => + dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss'), + }, +]; + +export const formColumns = (_type: string): ProFormColumnsType[] => [ + { + title: '字典名称', + dataIndex: 'name', + valueType: 'text', + colProps: { + span: 12, + }, + formItemProps: { + rules: [ + { + required: true, + message: '请输入字典名称', + }, + ], + }, + }, + { + title: '字典类型', + dataIndex: 'type', + valueType: 'text', + colProps: { + span: 12, + }, + formItemProps: { + rules: [ + { + required: true, + message: '请输入字典类型', + }, + ], + }, + }, + { + title: '状态', + dataIndex: 'status', + valueType: 'select', + fieldProps: { + options: tenantStatus, + }, + }, + { + title: '备注', + dataIndex: 'remark', + valueType: 'textarea', + colProps: { + span: 24, + }, + }, +]; diff --git a/src/pages/system/dict/index.tsx b/src/pages/system/dict/index.tsx index 1fa62c3..302b631 100644 --- a/src/pages/system/dict/index.tsx +++ b/src/pages/system/dict/index.tsx @@ -1,5 +1,131 @@ +import { PlusOutlined } from '@ant-design/icons'; +import type { ActionType, ProColumns } from '@ant-design/pro-components'; +import { Popconfirm } from 'antd'; +import React, { useCallback, useRef, useState } from 'react'; +import ConfigurableDrawerForm, { + type ConfigurableDrawerFormRef, +} from '@/components/DrawerForm'; +import EnhancedProTable from '@/components/EnhancedProTable'; +import type { ToolbarAction } from '@/components/EnhancedProTable/types'; +import { formStatusType } from '@/constants'; +import { + createDictType, + type DictTypeVO, + deleteDictType, + getDictTypePage, + updateDictType, +} from '@/services/system/dict/dict.type'; +import { baseDictTypeColumns, formColumns } from './config'; + const SyStemDict = () => { - return <>SyStemDict; + const configurableDrawerRef = useRef(null); + const tableRef = useRef(null); + + const [type, setType] = useState<'create' | 'update'>('create'); + const [id, setId] = useState(0); + + const handleEdit = (record: DictTypeVO) => { + setType('update'); + setId(record.id); + + configurableDrawerRef.current?.open(record); + }; + + const onFetch = async (params: { pageSize?: number; current?: number }) => { + const data = await getDictTypePage({ + ...params, + pageNo: params.current, + pageSize: params.pageSize, + }); + return { + data: data.list, + success: true, + total: data.total, + }; + }; + + const handleAdd = () => { + setType('create'); + configurableDrawerRef.current?.open({}); + }; + + const handleSubmit = useCallback( + async (values: DictTypeVO) => { + if (type === 'create') { + await createDictType(values); + } else { + await updateDictType({ + ...values, + id, + }); + } + tableRef.current?.reload(); + return true; + }, + [type, id], + ); + + const toolbarActions: ToolbarAction[] = [ + { + key: 'add', + label: '新建', + type: 'primary', + icon: , + onClick: handleAdd, + }, + ]; + + const actionColumns: ProColumns = { + title: '操作', + dataIndex: 'option', + valueType: 'option', + fixed: 'right', + width: 120, + render: ( + _text: React.ReactNode, + record: DictTypeVO, + _: any, + action: any, + ) => [ + handleEdit(record)}> + 编辑 + , + { + await deleteDictType(record.id); + action?.reload(); + }} + okText="是" + cancelText="否" + > + 删除 + , + ], + }; + + const columns = [...baseDictTypeColumns, actionColumns]; + return ( + <> + + ref={tableRef} + columns={columns} + request={onFetch} + toolbarActions={toolbarActions} + headerTitle="租户列表" + showIndex={false} + showSelection={false} + /> + + + + ); }; export default SyStemDict; diff --git a/src/pages/system/menu/config.tsx b/src/pages/system/menu/config.tsx new file mode 100644 index 0000000..8018186 --- /dev/null +++ b/src/pages/system/menu/config.tsx @@ -0,0 +1,125 @@ +// src/pages/system/menu/config.tsx +import type { + ProColumns, + ProFormColumnsType, +} from '@ant-design/pro-components'; +import { Switch } from 'antd'; +import type { MenuVO } from '@/services/system/menu'; + +export const baseMenuColumns: ProColumns[] = [ + { + title: '菜单名称', + dataIndex: 'name', + width: 150, + }, + { + title: '图标', + dataIndex: 'icon', + width: 60, + render: (_, record: MenuVO) => ( + + {record.icon ? : '—'} + + ), + }, + { + title: '排序', + dataIndex: 'sort', + width: 80, + }, + { + title: '权限标识', + dataIndex: 'permission', + width: 150, + }, + { + title: '组件路径', + dataIndex: 'component', + width: 150, + }, + { + title: '组件名称', + dataIndex: 'componentName', + width: 150, + }, + { + title: '状态', + dataIndex: 'status', + width: 80, + render: (_, record: MenuVO) => ( + + ), + }, +]; + +export const formColumns = (_type: string): ProFormColumnsType[] => [ + { + title: '菜单名称', + dataIndex: 'name', + formItemProps: { + rules: [ + { + required: true, + message: '请输入菜单名称', + }, + ], + }, + }, + { + title: '图标', + dataIndex: 'icon', + valueType: 'select', + fieldProps: { + placeholder: '请选择图标', + options: [ + { label: '商品', value: 'icon-product' }, + { label: '系统', value: 'icon-system' }, + { label: '用户', value: 'icon-user' }, + { label: '设置', value: 'icon-setting' }, + ], + }, + }, + { + title: '排序', + dataIndex: 'sort', + valueType: 'digit', + fieldProps: { + placeholder: '请输入排序值', + }, + }, + { + title: '权限标识', + dataIndex: 'permission', + fieldProps: { + placeholder: '请输入权限标识', + }, + }, + { + title: '组件路径', + dataIndex: 'component', + fieldProps: { + placeholder: '请输入组件路径', + }, + }, + { + title: '组件名称', + dataIndex: 'componentName', + fieldProps: { + placeholder: '请输入组件名称', + }, + }, + { + title: '状态', + dataIndex: 'status', + valueType: 'select', + fieldProps: { + options: [ + { label: '启用', value: 1 }, + { label: '禁用', value: 0 }, + ], + }, + }, +]; diff --git a/src/pages/system/menu/index.tsx b/src/pages/system/menu/index.tsx index d0c7045..383a9cc 100644 --- a/src/pages/system/menu/index.tsx +++ b/src/pages/system/menu/index.tsx @@ -1,5 +1,126 @@ -const SyStemMenu = () => { - return <>SyStemMenu; +// src/pages/system/menu/index.tsx + +import { PlusOutlined } from '@ant-design/icons'; +import type { ActionType, ProColumns } from '@ant-design/pro-components'; +import { Popconfirm } from 'antd'; +import React, { useCallback, useRef, useState } from 'react'; +import ConfigurableDrawerForm, { + type ConfigurableDrawerFormRef, +} from '@/components/DrawerForm'; +import EnhancedProTable from '@/components/EnhancedProTable'; +import type { ToolbarAction } from '@/components/EnhancedProTable/types'; +import { formStatusType } from '@/constants'; +import { + createMenu, + deleteMenu, + getMenuList, + type MenuReqVO, + type MenuVO, + updateMenu, +} from '@/services/system/menu'; +import { handleTree } from '@/utils/tree'; +import { baseMenuColumns, formColumns } from './config'; + +const SystemMenu = () => { + const configurableDrawerRef = useRef(null); + const tableRef = useRef(null); + + const [type, setType] = useState<'create' | 'update'>('create'); + const [id, setId] = useState(0); + + const handleEdit = (record: MenuVO) => { + setType('update'); + setId(record.id); + + configurableDrawerRef.current?.open(record); + }; + + const onFetch = async (params: MenuReqVO) => { + const data = await getMenuList({ + ...params, + }); + return { + data: handleTree(data), + success: true, + total: data.total, + }; + }; + const handleAdd = () => { + setType('create'); + configurableDrawerRef.current?.open({}); + }; + + const handleSubmit = useCallback( + async (values: MenuVO) => { + if (type === 'create') { + await createMenu(values); + } else { + await updateMenu({ + ...values, + id, + }); + } + tableRef.current?.reload(); + return true; + }, + [type, id], + ); + + const toolbarActions: ToolbarAction[] = [ + { + key: 'add', + label: '新建', + type: 'primary', + icon: , + onClick: handleAdd, + }, + ]; + + const actionColumns: ProColumns = { + title: '操作', + dataIndex: 'option', + valueType: 'option', + fixed: 'right', + width: 120, + render: (_text: React.ReactNode, record: MenuVO, _: any, action: any) => [ + handleEdit(record)}> + 编辑 + , + { + await deleteMenu(record.id); + action?.reload(); + }} + okText="是" + cancelText="否" + > + 删除 + , + ], + }; + const columns = [...baseMenuColumns, actionColumns]; + return ( + <> + + ref={tableRef} + columns={columns} + request={onFetch} + toolbarActions={toolbarActions} + headerTitle="租户列表" + showIndex={false} + showSelection={false} + /> + + + + ); }; -export default SyStemMenu; +export default SystemMenu; diff --git a/src/pages/system/post/config.tsx b/src/pages/system/post/config.tsx new file mode 100644 index 0000000..f745f9d --- /dev/null +++ b/src/pages/system/post/config.tsx @@ -0,0 +1,121 @@ +import type { + ProColumns, + ProFormColumnsType, +} from '@ant-design/pro-components'; +import { Tag } from 'antd'; +import dayjs from 'dayjs'; +import { tenantStatus } from '@/constants'; +import type { PostVO } from '@/services/system/post'; +import { getStatusLabel } from '@/utils/constant'; + +export const basePostColumns: ProColumns[] = [ + { + title: '岗位编号', + dataIndex: 'id', + width: 80, + }, + { + title: '岗位名称', + dataIndex: 'name', + width: 120, + }, + { + title: '岗位编码', + dataIndex: 'code', + width: 120, + }, + { + title: '岗位顺序', + dataIndex: 'sort', + width: 80, + }, + { + title: '岗位备注', + dataIndex: 'remark', + width: 120, + }, + { + title: '状态', + dataIndex: 'status', + width: 100, + render: (_, record: PostVO) => [ + + {getStatusLabel(tenantStatus, record.status)} + , + ], + }, + { + title: '创建时间', + dataIndex: 'createTime', + valueType: 'dateRange', + search: { + transform: (value) => { + return { + 'createTime[0]': dayjs(value[0]) + .startOf('day') + .format('YYYY-MM-DD HH:mm:ss'), + 'createTime[1]': dayjs(value[1]) + .endOf('day') + .format('YYYY-MM-DD HH:mm:ss'), + }; + }, + }, + render: (_, record: PostVO) => + dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss'), + }, +]; + +export const formColumns = (_type: string): ProFormColumnsType[] => [ + { + title: '岗位名称', + dataIndex: 'name', + formItemProps: { + rules: [ + { + required: true, + message: '请输入岗位名称', + }, + ], + }, + }, + { + title: '岗位编码', + dataIndex: 'code', + formItemProps: { + rules: [ + { + required: true, + message: '请输入岗位编码', + }, + ], + }, + }, + { + title: '岗位顺序', + dataIndex: 'sort', + valueType: 'digit', + fieldProps: { + placeholder: '请输入排序值', + }, + }, + { + title: '状态', + dataIndex: 'status', + valueType: 'select', + fieldProps: { + placeholder: '请选择状态', + options: tenantStatus, + }, + }, + { + title: '备注', + dataIndex: 'remark', + valueType: 'textarea', + fieldProps: { + placeholder: '请输入备注', + }, + }, +]; diff --git a/src/pages/system/post/index.tsx b/src/pages/system/post/index.tsx index 195d9a8..a91c8e8 100644 --- a/src/pages/system/post/index.tsx +++ b/src/pages/system/post/index.tsx @@ -1,5 +1,132 @@ +import { PlusOutlined } from '@ant-design/icons'; +import type { ActionType, ProColumns } from '@ant-design/pro-components'; +import { Popconfirm } from 'antd'; +import React, { useCallback, useRef, useState } from 'react'; +import ConfigurableDrawerForm, { + type ConfigurableDrawerFormRef, +} from '@/components/DrawerForm'; +import EnhancedProTable from '@/components/EnhancedProTable'; +import type { ToolbarAction } from '@/components/EnhancedProTable/types'; +import { formStatusType } from '@/constants'; +import { + createPost, + deletePost, + getPostPage, + type PostReqVO, + type PostVO, + updatePost, +} from '@/services/system/post'; +import { basePostColumns, formColumns } from './config'; + const SyStemPost = () => { - return <>SyStemPost; + const configurableDrawerRef = useRef(null); + const tableRef = useRef(null); + + const [type, setType] = useState<'create' | 'update'>('create'); + const [id, setId] = useState(0); + + const handleEdit = (record: PostVO) => { + setType('update'); + setId(record.id); + + configurableDrawerRef.current?.open(record); + }; + + const onFetch = async ( + params: PostReqVO & { + pageSize?: number; + current?: number; + }, + ) => { + const data = await getPostPage({ + ...params, + pageNo: params.current, + pageSize: params.pageSize, + }); + return { + data: data.list, + success: true, + total: data.total, + }; + }; + + const handleAdd = () => { + setType('create'); + configurableDrawerRef.current?.open({}); + }; + + const handleSubmit = useCallback( + async (values: PostVO) => { + if (type === 'create') { + await createPost(values); + } else { + await updatePost({ + ...values, + id, + }); + } + tableRef.current?.reload(); + return true; + }, + [type, id], + ); + + const toolbarActions: ToolbarAction[] = [ + { + key: 'add', + label: '新建', + type: 'primary', + icon: , + onClick: handleAdd, + }, + ]; + + const actionColumns: ProColumns = { + title: '操作', + dataIndex: 'option', + valueType: 'option', + fixed: 'right', + width: 120, + render: (_text: React.ReactNode, record: PostVO, _: any, action: any) => [ + handleEdit(record)}> + 编辑 + , + { + await deletePost(record.id); + action?.reload(); + }} + okText="是" + cancelText="否" + > + 删除 + , + ], + }; + + const columns = [...basePostColumns, actionColumns]; + return ( + <> + + ref={tableRef} + columns={columns} + request={onFetch} + toolbarActions={toolbarActions} + headerTitle="租户列表" + showIndex={false} + showSelection={false} + /> + + + + ); }; export default SyStemPost; diff --git a/src/pages/system/role/config.tsx b/src/pages/system/role/config.tsx new file mode 100644 index 0000000..2b10507 --- /dev/null +++ b/src/pages/system/role/config.tsx @@ -0,0 +1,143 @@ +import type { + ProColumns, + ProFormColumnsType, +} from '@ant-design/pro-components'; +import { Tag } from 'antd'; +import dayjs from 'dayjs'; +import { tenantStatus } from '@/constants'; +import type { RoleVO } from '@/services/system/role'; +import { getStatusLabel } from '@/utils/constant'; + +export const baseTenantColumns: ProColumns[] = [ + { + title: '角色编号', + dataIndex: 'id', + width: 100, + hideInSearch: true, // 在搜索表单中隐藏 + }, + { + title: '角色名称', + dataIndex: 'name', + width: 100, + }, + { + title: '角色类型', + dataIndex: 'type', + width: 100, + render: (_, record: RoleVO) => [ + + {record.type === 1 ? '内置' : '自定义'} + , + ], + hideInSearch: true, + }, + { + title: '角色标识', + dataIndex: 'code', + width: 150, + render: (_, record: RoleVO) => [ + + {record.code} + , + ], + hideInSearch: true, + }, + { + title: '显示顺序', + dataIndex: 'sort', + width: 100, + hideInSearch: true, + }, + { + title: '备注', + dataIndex: 'remark', + hideInSearch: true, // 在搜索表单中隐藏 + }, + { + title: '租户状态', + dataIndex: 'status', + width: 100, + render: (_, record: RoleVO) => [ + + {getStatusLabel(tenantStatus, record.status)} + , + ], + }, + { + title: '创建时间', + dataIndex: 'createTime', + valueType: 'dateRange', + search: { + transform: (value) => { + return { + 'createTime[0]': dayjs(value[0]) + .startOf('day') + .format('YYYY-MM-DD HH:mm:ss'), + 'createTime[1]': dayjs(value[1]) + .endOf('day') + .format('YYYY-MM-DD HH:mm:ss'), + }; + }, + }, + render: (_, record: RoleVO) => + dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss'), + }, +]; + +export const formColumns = (_type: string): ProFormColumnsType[] => [ + { + title: '角色名称', + dataIndex: 'name', + formItemProps: { + rules: [ + { + required: true, + message: '请输入角色名称', + }, + ], + }, + }, + { + title: '角色标识', + dataIndex: 'code', + formItemProps: { + rules: [ + { + required: true, + message: '请输入角色名称', + }, + ], + }, + }, + { + title: '显示顺序', + dataIndex: 'sort', + valueType: 'digit', + fieldProps: { + placeholder: '请输入显示顺序', + }, + }, + { + title: '租户状态', + dataIndex: 'status', + valueType: 'select', + fieldProps: { + multiple: true, + placeholder: '请选择租户状态', + options: tenantStatus, + }, + }, + { + title: '备注', + dataIndex: 'remark', + valueType: 'textarea', + fieldProps: { + placeholder: '请输入备注', + min: 5, + max: 20, + }, + }, +]; diff --git a/src/pages/system/role/index.tsx b/src/pages/system/role/index.tsx index cc9b280..1fa8b6b 100644 --- a/src/pages/system/role/index.tsx +++ b/src/pages/system/role/index.tsx @@ -1,5 +1,132 @@ +import { PlusOutlined } from '@ant-design/icons'; +import type { ActionType, ProColumns } from '@ant-design/pro-components'; +import { Popconfirm } from 'antd'; +import React, { useCallback, useRef, useState } from 'react'; +import ConfigurableDrawerForm, { + type ConfigurableDrawerFormRef, +} from '@/components/DrawerForm'; +import EnhancedProTable from '@/components/EnhancedProTable'; +import type { ToolbarAction } from '@/components/EnhancedProTable/types'; +import { formStatusType } from '@/constants'; +import { + createRole, + deleteRole, + getRolePage, + type RolePageReqVO, + type RoleVO, + updateRole, +} from '@/services/system/role'; +import { baseTenantColumns, formColumns } from './config'; + const SyStemRole = () => { - return <>SyStemRole; + const configurableDrawerRef = useRef(null); + const tableRef = useRef(null); + + const [type, setType] = useState<'create' | 'update'>('create'); + const [id, setId] = useState(0); + + const handleEdit = (record: RoleVO) => { + setType('update'); + setId(record.id); + + configurableDrawerRef.current?.open(record); + }; + + const onFetch = async ( + params: RolePageReqVO & { + pageSize?: number; + current?: number; + }, + ) => { + const data = await getRolePage({ + ...params, + pageNo: params.current, + pageSize: params.pageSize, + }); + return { + data: data.list, + success: true, + total: data.total, + }; + }; + + const handleAdd = () => { + setType('create'); + configurableDrawerRef.current?.open({}); + }; + + const handleSubmit = useCallback( + async (values: RoleVO) => { + if (type === 'create') { + await createRole(values); + } else { + await updateRole({ + ...values, + id, + }); + } + tableRef.current?.reload(); + return true; + }, + [type, id], + ); + + const toolbarActions: ToolbarAction[] = [ + { + key: 'add', + label: '新建', + type: 'primary', + icon: , + onClick: handleAdd, + }, + ]; + + const actionColumns: ProColumns = { + title: '操作', + dataIndex: 'option', + valueType: 'option', + fixed: 'right', + width: 120, + render: (_text: React.ReactNode, record: RoleVO, _: any, action: any) => [ + handleEdit(record)}> + 编辑 + , + { + await deleteRole(record.id); + action?.reload(); + }} + okText="是" + cancelText="否" + > + 删除 + , + ], + }; + + const columns = [...baseTenantColumns, actionColumns]; + return ( + <> + + ref={tableRef} + columns={columns} + request={onFetch} + toolbarActions={toolbarActions} + headerTitle="租户列表" + showIndex={false} + showSelection={false} + /> + + + + ); }; export default SyStemRole; diff --git a/src/pages/system/tenant/list/config.tsx b/src/pages/system/tenant/list/config.tsx index 276810c..18de6a4 100644 --- a/src/pages/system/tenant/list/config.tsx +++ b/src/pages/system/tenant/list/config.tsx @@ -1,30 +1,31 @@ -import { type TenantVO, deleteTenant } from "@/services/system/tenant/list"; -import { ProColumns, ProFormColumnsType } from "@ant-design/pro-components"; -import { DatePicker, Modal, Popconfirm } from "antd"; -import { FormInstance } from "antd/lib"; -import dayjs from "dayjs"; +import type { + ProColumns, + ProFormColumnsType, +} from '@ant-design/pro-components'; +import dayjs from 'dayjs'; +import type { TenantVO } from '@/services/system/tenant/list'; export const baseTenantColumns: ProColumns[] = [ { - title: "租户编号", - dataIndex: "id", - tip: "租户编号", + title: '租户编号', + dataIndex: 'id', + tip: '租户编号', width: 100, hideInSearch: true, // 在搜索表单中隐藏 }, { - title: "租户名", - dataIndex: "name", - tip: "租户名", // 提示信息 + title: '租户名', + dataIndex: 'name', + tip: '租户名', // 提示信息 }, { - title: "租户套餐", - dataIndex: "packageId", - valueType: "select", + title: '租户套餐', + dataIndex: 'packageId', + valueType: 'select', request: async () => { return [ { - label: "默认套餐", + label: '默认套餐', value: 1, }, ]; @@ -36,60 +37,67 @@ export const baseTenantColumns: ProColumns[] = [ // }, }, { - title: "联系人", - dataIndex: "contactName", + title: '联系人', + dataIndex: 'contactName', }, { - title: "联系手机", - dataIndex: "contactMobile", + title: '联系手机', + dataIndex: 'contactMobile', }, { - title: "账号额度", - dataIndex: "accountCount", + title: '账号额度', + dataIndex: 'accountCount', hideInSearch: true, // 在搜索表单中隐藏 }, { - title: "过期时间", - dataIndex: "expireTime", - valueType: "dateTime", + title: '过期时间', + dataIndex: 'expireTime', + valueType: 'dateTime', hideInSearch: true, // 在搜索表单中隐藏 }, - { title: "绑定域名", dataIndex: "website", width: 100 }, + { title: '绑定域名', dataIndex: 'website', width: 100 }, { - title: "租户状态", - dataIndex: "status", - valueType: "select", + title: '租户状态', + dataIndex: 'status', + valueType: 'select', valueEnum: { - all: { text: "全部", status: "Default" }, - open: { text: "未解决", status: "Error" }, - closed: { text: "已解决", status: "Success" }, + all: { text: '全部', status: 'Default' }, + open: { text: '未解决', status: 'Error' }, + closed: { text: '已解决', status: 'Success' }, }, }, { - title: "创建时间", - dataIndex: "createTime", - valueType: "dateRange", + title: '创建时间', + dataIndex: 'createTime', + valueType: 'dateRange', search: { transform: (value) => { - return [`${value[0]} 00:00:00`, `${value[1]} 00:00:00`]; + return { + 'createTime[0]': dayjs(value[0]) + .startOf('day') + .format('YYYY-MM-DD HH:mm:ss'), + 'createTime[1]': dayjs(value[1]) + .endOf('day') + .format('YYYY-MM-DD HH:mm:ss'), + }; }, }, render: (_, record: TenantVO) => - dayjs(record.createTime).format("YYYY-MM-DD HH:mm:ss"), + dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss'), }, ]; export const formColumns = (type: string): ProFormColumnsType[] => [ { - title: "租户名", - dataIndex: "name", - tip: "租户名", // 提示信息 + title: '租户名', + dataIndex: 'name', + tip: '租户名', // 提示信息 formItemProps: { rules: [ { required: true, - message: "请输入用户名", + message: '请输入用户名', }, // { // min: 2, @@ -100,121 +108,121 @@ export const formColumns = (type: string): ProFormColumnsType[] => [ }, }, { - title: "租户套餐", - dataIndex: "packageId", - valueType: "select", + title: '租户套餐', + dataIndex: 'packageId', + valueType: 'select', formItemProps: { rules: [ { required: true, - message: "请选择租户套餐", + message: '请选择租户套餐', }, ], }, fieldProps: { - placeholder: "请选择套餐类型", + placeholder: '请选择套餐类型', options: [ { - label: "普通套餐", + label: '普通套餐', value: 111, }, ], }, }, { - title: "联系人", - dataIndex: "contactName", + title: '联系人', + dataIndex: 'contactName', }, { - title: "联系手机", - dataIndex: "contactMobile", + title: '联系手机', + dataIndex: 'contactMobile', formItemProps: { rules: [ { required: true, - message: "请输入联系手机", + message: '请输入联系手机', }, ], }, }, { - title: "用户名称", - dataIndex: "username", - hideInForm: type === "update", + title: '用户名称', + dataIndex: 'username', + hideInForm: type === 'update', formItemProps: { rules: [ { required: true, - message: "请输入用户名称", + message: '请输入用户名称', }, { pattern: /^[a-zA-Z0-9]+$/, - message: "用户账号由 0-9、a-z、A-Z 组成", + message: '用户账号由 0-9、a-z、A-Z 组成', }, // 用户账号由 数字、字母组成 ], }, }, { - title: "用户密码", - dataIndex: "password", - valueType: "password", - hideInForm: type === "update", + title: '用户密码', + dataIndex: 'password', + valueType: 'password', + hideInForm: type === 'update', fieldProps: { - placeholder: "请输入用户密码", - autoComplete: "new-password", + placeholder: '请输入用户密码', + autoComplete: 'new-password', }, formItemProps: { rules: [ { required: true, - message: "请输入用户密码", + message: '请输入用户密码', }, { min: 4, max: 16, - message: "密码长度为4-16个字符", + message: '密码长度为4-16个字符', }, ], }, }, { - title: "账号额度", - dataIndex: "accountCount", - valueType: "digit", + title: '账号额度', + dataIndex: 'accountCount', + valueType: 'digit', }, { - title: "过期时间", - dataIndex: "expireTime", - valueType: "date", + title: '过期时间', + dataIndex: 'expireTime', + valueType: 'date', fieldProps: { - placeholder: "请选择过期时间", - format: "YYYY-MM-DD", + placeholder: '请选择过期时间', + format: 'YYYY-MM-DD', }, }, - { title: "绑定域名", dataIndex: "website" }, + { title: '绑定域名', dataIndex: 'website' }, { - title: "租户状态", - dataIndex: "status", - valueType: "radio", + title: '租户状态', + dataIndex: 'status', + valueType: 'radio', formItemProps: { rules: [ { required: true, - message: "请选择租户状态", + message: '请选择租户状态', }, ], }, fieldProps: { - placeholder: "请选择套餐类型", + placeholder: '请选择套餐类型', options: [ { - label: "启用", + label: '启用', value: 1, }, { - label: "禁用", + label: '禁用', value: 0, }, ], diff --git a/src/pages/system/tenant/list/index.tsx b/src/pages/system/tenant/list/index.tsx index c69b4fc..f8b13a7 100644 --- a/src/pages/system/tenant/list/index.tsx +++ b/src/pages/system/tenant/list/index.tsx @@ -1,25 +1,23 @@ -import EnhancedProTable from "@/components/EnhancedProTable"; +import { PlusOutlined } from '@ant-design/icons'; +import type { ActionType, ProColumns } from '@ant-design/pro-components'; +import { Popconfirm } from 'antd'; +import dayjs from 'dayjs'; +import React, { useCallback, useRef, useState } from 'react'; +import ConfigurableDrawerForm, { + type ConfigurableDrawerFormRef, +} from '@/components/DrawerForm'; +import EnhancedProTable from '@/components/EnhancedProTable'; +import type { ToolbarAction } from '@/components/EnhancedProTable/types'; +import { formStatusType } from '@/constants'; import { - getTenantPage, createTenant, + deleteTenant, + getTenantPage, type TenantPageReqVO, type TenantVO, - deleteTenant, updateTenant, -} from "@/services/system/tenant/list"; -import React, { createContext, useCallback } from "react"; -import ConfigurableDrawerForm, { - ConfigurableDrawerFormRef, -} from "@/components/DrawerForm"; -import { useRef, useState } from "react"; -import { formColumns, baseTenantColumns } from "./config"; -import { PlusOutlined } from "@ant-design/icons"; -import { ref } from "process"; -import { ActionType, ProColumns } from "@ant-design/pro-components"; -import { ToolbarAction } from "@/components/EnhancedProTable/types"; -import { Modal, Popconfirm } from "antd"; -import { formStatusType } from "@/constants"; -import dayjs from "dayjs"; +} from '@/services/system/tenant/list'; +import { baseTenantColumns, formColumns } from './config'; export const waitTimePromise = async (time: number = 90) => { return new Promise((resolve) => { setTimeout(() => { @@ -30,13 +28,13 @@ export const waitTimePromise = async (time: number = 90) => { const TenantList = () => { const configurableDrawerRef = useRef(null); const tableRef = useRef(null); - const [type, setType] = useState<"create" | "update">("create"); + const [type, setType] = useState<'create' | 'update'>('create'); const [id, setId] = useState(0); const onFetch = async ( params: TenantPageReqVO & { pageSize?: number; current?: number; - } + }, ) => { await waitTimePromise(); const data = await getTenantPage({ @@ -53,7 +51,7 @@ const TenantList = () => { const handleSubmit = useCallback( async (values: TenantVO) => { - if (type === "create") { + if (type === 'create') { await createTenant(values); } else { await updateTenant({ @@ -65,36 +63,36 @@ const TenantList = () => { tableRef.current?.reload(); return true; }, - [type] + [type], ); const handleAdd = () => { - setType("create"); + setType('create'); configurableDrawerRef.current?.open({}); }; const handleEdit = (record: TenantVO) => { - setType("update"); + setType('update'); setId(record.id); configurableDrawerRef.current?.open(record); }; const toolbarActions: ToolbarAction[] = [ { - key: "add", - label: "新建", - type: "primary", + key: 'add', + label: '新建', + type: 'primary', icon: , onClick: handleAdd, }, ]; const actionColumns: ProColumns = { - title: "操作", - dataIndex: "option", - valueType: "option", - fixed: "right", + title: '操作', + dataIndex: 'option', + valueType: 'option', + fixed: 'right', width: 120, - render: (text: React.ReactNode, record: TenantVO, _: any, action: any) => [ + render: (_text: React.ReactNode, record: TenantVO, _: any, action: any) => [ handleEdit(record)}> 编辑 , @@ -123,8 +121,6 @@ const TenantList = () => { headerTitle="租户列表" showIndex={false} showSelection={false} - showActions - maxActionCount={3} /> [] = [ + { + title: '套餐编号', + dataIndex: 'id', + width: 100, + hideInSearch: true, // 在搜索表单中隐藏 + }, + { + title: '套餐名', + dataIndex: 'name', + }, + { + title: '状态', + dataIndex: 'status', + valueType: 'select', + valueEnum: { + all: { text: '全部', status: 'Default' }, + open: { text: '未解决', status: 'Error' }, + closed: { text: '已解决', status: 'Success' }, + }, + }, + { + title: '备注', + dataIndex: 'remark', + hideInSearch: true, // 在搜索表单中隐藏 + }, + { + title: '创建时间', + dataIndex: 'createTime', + valueType: 'dateRange', + search: { + transform: (value) => { + return { + 'createTime[0]': dayjs(value[0]) + .startOf('day') + .format('YYYY-MM-DD HH:mm:ss'), + 'createTime[1]': dayjs(value[1]) + .endOf('day') + .format('YYYY-MM-DD HH:mm:ss'), + }; + }, + }, + render: (_, record: TenantPackageVO) => + dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss'), + }, +]; + +export const formColumns = (_type: string): ProFormColumnsType[] => [ + { + title: '套餐名', + dataIndex: 'name', + formItemProps: { + rules: [ + { + required: true, + message: '请输入用户名', + }, + ], + }, + }, + { + title: '套餐权限', + dataIndex: 'menuIds', + valueType: 'treeSelect', + fieldProps: { + multiple: true, + placeholder: '请选择租户权限', + options: [{ lable: '11', value: 5016 }], + }, + }, + { + title: '租户状态', + dataIndex: 'status', + valueType: 'radio', + formItemProps: { + rules: [ + { + required: true, + message: '请选择租户状态', + }, + ], + }, + fieldProps: { + placeholder: '请选择套餐类型', + options: [ + { + label: '开启', + value: 1, + }, + { + label: '关闭', + value: 0, + }, + ], + }, + }, + { + title: '备注', + dataIndex: 'remark', + valueType: 'textarea', + }, +]; diff --git a/src/pages/system/tenant/package/index.tsx b/src/pages/system/tenant/package/index.tsx index 34286af..1b3ee4b 100644 --- a/src/pages/system/tenant/package/index.tsx +++ b/src/pages/system/tenant/package/index.tsx @@ -1,5 +1,137 @@ +import { PlusOutlined } from '@ant-design/icons'; +import type { ActionType, ProColumns } from '@ant-design/pro-components'; +import { Popconfirm } from 'antd'; +import React, { useCallback, useRef, useState } from 'react'; +import ConfigurableDrawerForm, { + type ConfigurableDrawerFormRef, +} from '@/components/DrawerForm'; +import EnhancedProTable from '@/components/EnhancedProTable'; +import type { ToolbarAction } from '@/components/EnhancedProTable/types'; +import { formStatusType } from '@/constants'; +import { + createTenantPackage, + deleteTenantPackage, + getTenantPackagePage, + type TenantPackagePageReqVO, + type TenantPackageVO, + updateTenantPackage, +} from '@/services/system/tenant/package'; +import { baseTenantColumns, formColumns } from './config'; + const TenantPackage = () => { - return
TenantPackage
; + const configurableDrawerRef = useRef(null); + const tableRef = useRef(null); + + const [type, setType] = useState<'create' | 'update'>('create'); + const [id, setId] = useState(0); + + const handleEdit = (record: TenantPackageVO) => { + setType('update'); + setId(record.id); + + configurableDrawerRef.current?.open(record); + }; + + const onFetch = async ( + params: TenantPackagePageReqVO & { + pageSize?: number; + current?: number; + }, + ) => { + const data = await getTenantPackagePage({ + ...params, + pageNo: params.current, + pageSize: params.pageSize, + }); + return { + data: data.list, + success: true, + total: data.total, + }; + }; + + const handleAdd = () => { + setType('create'); + configurableDrawerRef.current?.open({}); + }; + + const handleSubmit = useCallback( + async (values: TenantPackageVO) => { + if (type === 'create') { + await createTenantPackage(values); + } else { + await updateTenantPackage({ + ...values, + id, + }); + } + tableRef.current?.reload(); + return true; + }, + [type, id], + ); + + const toolbarActions: ToolbarAction[] = [ + { + key: 'add', + label: '新建', + type: 'primary', + icon: , + onClick: handleAdd, + }, + ]; + + const actionColumns: ProColumns = { + title: '操作', + dataIndex: 'option', + valueType: 'option', + fixed: 'right', + width: 120, + render: ( + _text: React.ReactNode, + record: TenantPackageVO, + _: any, + action: any, + ) => [ + handleEdit(record)}> + 编辑 + , + { + await deleteTenantPackage(record.id); + action?.reload(); + }} + okText="是" + cancelText="否" + > + 删除 + , + ], + }; + + const columns = [...baseTenantColumns, actionColumns]; + return ( + <> + + ref={tableRef} + columns={columns} + request={onFetch} + toolbarActions={toolbarActions} + headerTitle="租户列表" + showIndex={false} + showSelection={false} + /> + + + + ); }; export default TenantPackage; diff --git a/src/pages/system/user/config.tsx b/src/pages/system/user/config.tsx new file mode 100644 index 0000000..f22bb43 --- /dev/null +++ b/src/pages/system/user/config.tsx @@ -0,0 +1,220 @@ +import type { + ProColumns, + ProCoreActionType, + ProFormColumnsType, +} from '@ant-design/pro-components'; +import { Modal, message, Switch } from 'antd'; +import dayjs from 'dayjs'; +import { updateUserStatus } from '@/services/system/user'; +import type { UserVO } from '@/services/system/user/index'; + +export const baseTenantColumns: ProColumns[] = [ + { + title: '用户编号', + dataIndex: 'id', + width: 80, + hideInSearch: true, // 在搜索表单中隐藏 + }, + { + title: '用户名称', + dataIndex: 'username', + width: 100, + }, + { + title: '用户昵称', + dataIndex: 'nickname', + width: 100, + hideInSearch: true, + }, + { + title: '部门', + dataIndex: 'deptName', + width: 100, + }, + { + title: '手机号码', + dataIndex: 'mobile', + width: 100, + }, + { + title: '状态', + dataIndex: 'status', + valueType: 'select', + valueEnum: { + all: { text: '全部', status: 'Default' }, + open: { text: '未解决', status: 'Error' }, + closed: { text: '已解决', status: 'Success' }, + }, + render: ( + _dom: React.ReactNode, + record: UserVO, + _index: number, + action: ProCoreActionType | undefined, + ) => [ + { + const text = record.status ? '启用' : '停用'; + Modal.confirm({ + title: '确认操作', + content: `确认要"${text}""${record.username}"用户吗?`, + onOk: async () => { + const status = record.status === 0 ? 1 : 0; + await updateUserStatus(record.id, status); + message.success('修改成功'); + action?.reload(); + // 执行状态变更逻辑 + }, + }); + // await message.confirm("修改成功"); + }} + >, + ], + }, + { + title: '创建时间', + dataIndex: 'createTime', + valueType: 'dateRange', + search: { + transform: (value) => { + return { + 'createTime[0]': dayjs(value[0]) + .startOf('day') + .format('YYYY-MM-DD HH:mm:ss'), + 'createTime[1]': dayjs(value[1]) + .endOf('day') + .format('YYYY-MM-DD HH:mm:ss'), + }; + }, + }, + render: (_, record: UserVO) => + dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss'), + }, + { + title: '备注', + dataIndex: 'remark', + hideInSearch: true, // 在搜索表单中隐藏 + }, +]; + +export const formColumns = (type: string): ProFormColumnsType[] => [ + { + title: '用户昵称', + dataIndex: 'nickname', + formItemProps: { + rules: [ + { + required: true, + message: '请输入用户名', + }, + ], + }, + }, + { + title: '归属部门', + dataIndex: 'deptId', + valueType: 'treeSelect', + fieldProps: { + multiple: true, + placeholder: '请选择归属部门', + options: [{ lable: '11', value: 5016 }], + }, + }, + { + title: '手机号码', + dataIndex: 'mobile', + formItemProps: { + rules: [ + { + required: true, + message: '请输入联系手机', + }, + ], + }, + }, + // { prop: "email", label: "邮箱", type: "input", inputType: "email" }, + { + title: '邮箱', + dataIndex: 'email', + valueType: 'text', + fieldProps: { + type: 'email', + placeholder: '请输入邮箱', + }, + formItemProps: { + rules: [ + { + required: true, + message: '请输入邮箱', + }, + ], + }, + }, + { + title: '用户名称', + dataIndex: 'username', + hideInForm: type === 'update', + }, + { + title: '用户密码', + dataIndex: 'password', + valueType: 'password', + hideInForm: type === 'update', + fieldProps: { + placeholder: '请输入用户密码', + autoComplete: 'new-password', + }, + formItemProps: { + rules: [ + { + required: true, + message: '请输入用户密码', + }, + ], + }, + }, + { + title: '用户性别', + dataIndex: 'sex', + valueType: 'select', + fieldProps: { + placeholder: '请选择性别', + options: [ + { + label: '男', + value: 1, + }, + { + label: '女', + value: 2, + }, + ], + }, + }, + { + title: '岗位', + dataIndex: 'postIds', + valueType: 'select', + fieldProps: { + mode: 'multiple', + placeholder: '请选择岗位', + options: [ + { + label: '管理员', + value: 1, + }, + { + label: '普通用户', + value: 2, + }, + ], + }, + formItemProps: {}, + }, + { + title: '备注', + dataIndex: 'remark', + valueType: 'textarea', + }, +]; diff --git a/src/pages/system/user/index.tsx b/src/pages/system/user/index.tsx index 5b0441b..141c26e 100644 --- a/src/pages/system/user/index.tsx +++ b/src/pages/system/user/index.tsx @@ -1,5 +1,130 @@ +import { PlusOutlined } from '@ant-design/icons'; +import type { ActionType, ProColumns } from '@ant-design/pro-components'; +import { Popconfirm } from 'antd'; +import React, { useCallback, useRef, useState } from 'react'; +import ConfigurableDrawerForm, { + type ConfigurableDrawerFormRef, +} from '@/components/DrawerForm'; +import EnhancedProTable from '@/components/EnhancedProTable'; +import type { ToolbarAction } from '@/components/EnhancedProTable/types'; +import { formStatusType } from '@/constants'; +import { + createUser, + deleteUser, + getUserPage, + type UserReqVO, + type UserVO, + updateUser, +} from '@/services/system/user'; +import { baseTenantColumns, formColumns } from './config'; + const SyStemUser = () => { - return <>SyStemUser; + const configurableDrawerRef = useRef(null); + const tableRef = useRef(null); + const [type, setType] = useState<'create' | 'update'>('create'); + const [id, setId] = useState(0); + + const handleEdit = (record: UserVO) => { + setType('update'); + setId(record.id); + + configurableDrawerRef.current?.open(record); + }; + + const onFetch = async ( + params: UserReqVO & { + pageSize?: number; + current?: number; + }, + ) => { + const data = await getUserPage({ + ...params, + pageNo: params.current, + pageSize: params.pageSize, + }); + return { + data: data.list, + success: true, + total: data.total, + }; + }; + + const handleAdd = () => { + setType('create'); + configurableDrawerRef.current?.open({}); + }; + + const handleSubmit = useCallback( + async (values: UserVO) => { + if (type === 'create') { + await createUser(values); + } else { + await updateUser({ + ...values, + id, + }); + } + tableRef.current?.reload(); + return true; + }, + [type, id], + ); + + const toolbarActions: ToolbarAction[] = [ + { + key: 'add', + label: '新建', + type: 'primary', + icon: , + onClick: handleAdd, + }, + ]; + + const actionColumns: ProColumns = { + title: '操作', + dataIndex: 'option', + valueType: 'option', + fixed: 'right', + width: 120, + render: (_text: React.ReactNode, record: UserVO, _: any, action: any) => [ + handleEdit(record)}> + 编辑 + , + { + await deleteUser(record.id); + action?.reload(); + }} + okText="是" + cancelText="否" + > + 删除 + , + ], + }; + const columns = [...baseTenantColumns, actionColumns]; + return ( + <> + + ref={tableRef} + columns={columns} + request={onFetch} + toolbarActions={toolbarActions} + headerTitle="租户列表" + showIndex={false} + showSelection={false} + /> + + + + ); }; export default SyStemUser; diff --git a/src/services/system/dept/index.ts b/src/services/system/dept/index.ts new file mode 100644 index 0000000..ce55ade --- /dev/null +++ b/src/services/system/dept/index.ts @@ -0,0 +1,89 @@ +import { request } from "@umijs/max"; + +export interface DeptVO { + id: number; + name: string; + parentId: number; + status: number; + sort: number; + leaderUserId: number; + phone: string; + email: string; + createTime: Date; +} + +export interface DeptReqVO extends PageParam { + name?: string; + status?: number; +} + +// 查询部门(精简)列表 +// export const getSimpleDeptList = async (): Promise => { +// return await request.get({ url: "/system/dept/simple-list" }); +// }; + +export const getSimpleDeptList = async () => { + return request("/system/dept/simple-list", { + method: "GET", + }); +}; + +// 查询部门列表 +// export const getDeptPage = async (params: DeptReq): Promise => { +// return await request.get({ url: "/system/dept/list", params }); +// }; + +export const getDeptPage = (params: DeptReqVO) => { + return request("/system/dept/list", { + method: "GET", + params, + }); +}; + +// 查询部门详情 +// export const getDept = async (id: number) => { +// return await request.get({ url: "/system/dept/get?id=" + id }); +// }; + +export const getDept = (id: number) => { + return request("/system/dept/get", { + method: "GET", + params: { id }, + }); +}; + +// 新增部门 +// export const createDept = async (data: Dept) => { +// return await request.post({ url: "/system/dept/create", data: data }); +// }; + +export const createDept = (data: DeptVO) => { + return request("/system/dept/create", { + method: "POST", + data, + }); +}; + +// 修改部门 +// export const updateDept = async (params: Dept) => { +// return await request.put({ url: "/system/dept/update", data: params }); +// }; + +export const updateDept = (params: DeptVO) => { + return request("/system/dept/update", { + method: "PUT", + data: params, + }); +}; + +// 删除部门 +// export const deleteDept = async (id: number) => { +// return await request.delete({ url: "/system/dept/delete?id=" + id }); +// }; + +export const deleteDept = (id: number) => { + return request("/system/dept/delete", { + method: "DELETE", + params: { id }, + }); +}; diff --git a/src/services/system/dict/dict.data.ts b/src/services/system/dict/dict.data.ts new file mode 100644 index 0000000..677e17a --- /dev/null +++ b/src/services/system/dict/dict.data.ts @@ -0,0 +1,96 @@ +import { request } from "@umijs/max"; + +export type DictDataVO = { + id: number | undefined; + sort: number | undefined; + label: string; + value: string; + dictType: string; + status: number; + colorType: string; + cssClass: string; + remark: string; + createTime: Date; +}; + +// 查询字典数据(精简)列表 +// export const getSimpleDictDataList = () => { +// return request.get({ url: "/system/dict-data/simple-list" }); +// }; + +export const getSimpleDictDataList = () => { + return request("/system/dict-data/simple-list", { + method: "GET", + }); +}; + +// 查询字典数据列表 +// export const getDictDataPage = (params: PageParam) => { +// return request.get({ url: "/system/dict-data/page", params }); +// }; + +export const getDictDataPage = (params: PageParam) => { + return request("/system/dict-data/page", { + method: "GET", + params, + }); +}; + +// 查询字典数据详情 +// export const getDictData = (id: number) => { +// return request.get({ url: "/system/dict-data/get?id=" + id }); +// }; + +export const getDictData = (id: number) => { + return request("/system/dict-data/get", { + method: "GET", + params: { id }, + }); +}; + +// 新增字典数据 +// export const createDictData = (data: DictData) => { +// return request.post({ url: "/system/dict-data/create", data }); +// }; + +export const createDictData = (data: DictDataVO) => { + return request("/system/dict-data/create", { + method: "POST", + data, + }); +}; + +// 修改字典数据 +// export const updateDictData = (data: DictData) => { +// return request.put({ url: "/system/dict-data/update", data }); +// }; + +export const updateDictData = (data: DictDataVO) => { + return request("/system/dict-data/update", { + method: "PUT", + data, + }); +}; + +// 删除字典数据 +// export const deleteDictData = (id: number) => { +// return request.delete({ url: "/system/dict-data/delete?id=" + id }); +// }; + +export const deleteDictData = (id: number) => { + return request("/system/dict-data/delete", { + method: "DELETE", + params: { id }, + }); +}; + +// 导出字典类型数据 +// export const exportDictData = (params: DictData) => { +// return request.download({ url: "/system/dict-data/export", params }); +// }; +export const exportDictData = (params: DictDataVO) => { + return request("/system/dict-data/export", { + method: "GET", + params, + }); +}; diff --git a/src/services/system/dict/dict.type.ts b/src/services/system/dict/dict.type.ts new file mode 100644 index 0000000..616f1d9 --- /dev/null +++ b/src/services/system/dict/dict.type.ts @@ -0,0 +1,101 @@ +import { request } from "@umijs/max"; + +export interface DictTypeVO extends PageParam { + id: number; + name: string; + type: string; + status: number; + remark: string; + createTime: Date; +} + +export interface DictReqVO extends PageParam { + name?: string; + status?: number; + type?: string; + createTime?: Date; +} + +// 查询字典(精简)列表 +// export const getSimpleDictTypeList = () => { +// return request.get({ url: "/system/dict-type/list-all-simple" }); +// }; + +export const getSimpleDictTypeList = () => { + return request("/system/dict-type/list-all-simple", { + method: "GET", + }); +}; + +// 查询字典列表 +// export const getDictTypePage = ( +// params: DictReq +// ): Promise> => { +// return request.get({ url: "/system/dict-type/page", params }); +// }; + +export const getDictTypePage = (params: PageParam) => { + return request("/system/dict-type/page", { + method: "GET", + params, + }); +}; + +// // 查询字典详情 +// export const getDictType = (id?: number) => { +// return request.get({ url: "/system/dict-type/get?id=" + id }); +// }; +export const getDictType = (id: number) => { + return request("/system/dict-type/get", { + method: "GET", + params: { id }, + }); +}; + +// 新增字典 +// export const createDictType = (data: DictType) => { +// return request.post({ url: "/system/dict-type/create", data }); +// }; + +export const createDictType = (data: DictTypeVO) => { + return request("/system/dict-type/create", { + method: "POST", + data, + }); +}; + +// 修改字典 +// export const updateDictType = (data: DictType) => { +// return request.put({ url: "/system/dict-type/update", data }); +// }; + +export const updateDictType = (data: DictTypeVO) => { + return request("/system/dict-type/update", { + method: "PUT", + data, + }); +}; + +// 删除字典 +// export const deleteDictType = (id: number) => { +// return request.delete({ url: "/system/dict-type/delete?id=" + id }); +// }; + +export const deleteDictType = (id: number) => { + return request("/system/dict-type/delete", { + method: "DELETE", + params: { id }, + }); +}; + +// 导出字典类型 +// export const exportDictType = (params: DictType) => { +// return request.download({ url: "/system/dict-type/export", params }); +// }; + +export const exportDictType = (params: DictTypeVO) => { + return request("/system/dict-type/export", { + method: "POST", + params, + }); +}; diff --git a/src/services/system/menu/index.ts b/src/services/system/menu/index.ts index 8f048ed..0410449 100644 --- a/src/services/system/menu/index.ts +++ b/src/services/system/menu/index.ts @@ -19,32 +19,81 @@ export interface MenuVO { children?: MenuVO[]; } +export interface MenuReqVO { + name?: string; + status?: 0 | 1; +} + // 查询菜单(精简)列表 +export const getSimpleMenusList = () => { + return request("/system/menu/simple-list", { + method: "GET", + }); +}; // export const getSimpleMenusList = () => { // return request.get({ url: '/system/menu/simple-list' }) // } // // 查询菜单列表 + +export const getMenuList = (params: MenuReqVO) => { + return request("/system/menu/list", { + method: "GET", + params, + }); +}; + // export const getMenuList = (params) => { // return request.get({ url: '/system/menu/list', params }) // } // // 获取菜单详情 + +export const getMenu = (id: number) => { + return request("/system/menu/get", { + method: "GET", + params: { id }, + }); +}; + // export const getMenu = (id: number) => { // return request.get({ url: '/system/menu/get?id=' + id }) // } // // 新增菜单 +export const createMenu = (data: MenuVO) => { + return request("/system/menu/create", { + method: "POST", + data, + }); +}; + // export const createMenu = (data: MenuVO) => { // return request.post({ url: '/system/menu/create', data }) // } // // 修改菜单 + +export const updateMenu = (data: MenuVO) => { + return request("/system/menu/update", { + method: "PUT", + data, + }); +}; + // export const updateMenu = (data: MenuVO) => { // return request.put({ url: '/system/menu/update', data }) // } // // 删除菜单 + +export const deleteMenu = (id: number) => { + return request("/system/menu/delete", { + method: "DELETE", + params: { id }, + }); +}; + // export const deleteMenu = (id: number) => { // return request.delete({ url: '/system/menu/delete?id=' + id }) // } diff --git a/src/services/system/post/index.ts b/src/services/system/post/index.ts new file mode 100644 index 0000000..919402a --- /dev/null +++ b/src/services/system/post/index.ts @@ -0,0 +1,87 @@ +import { request } from "@umijs/max"; + +export interface PostVO { + id: number; + name: string; + code: string; + sort: number; + status: number; + remark: string; + createTime?: Date; +} + +export interface PostReqVO extends PageParam { + name?: string; + status?: number; + code?: string; +} + +// 查询岗位列表 +// export const getPostPage = async (params: PostReq) => { +// return await request.get({ url: "/system/post/page", params }); +// }; + +export const getPostPage = (params: PostReqVO) => { + return request("/system/post/page", { + method: "GET", + params, + }); +}; + +// 获取岗位精简信息列表 +// export const getSimplePostList = async (): Promise => { +// return await request.get({ url: "/system/post/simple-list" }); +// }; + +export const getSimplePostList = () => { + return request("/system/post/simple-list", { + method: "GET", + }); +}; + +// 查询岗位详情 +// export const getPost = async (id: number) => { +// return await request.get({ url: "/system/post/get?id=" + id }); +// }; + +export const getPost = (id: number) => { + return request("/system/post/get", { + method: "GET", + params: { id }, + }); +}; +// 新增岗位 +// export const createPost = async (data: Post) => { +// return await request.post({ url: "/system/post/create", data }); +// }; + +export const createPost = async (data: PostVO) => { + return await request("/system/post/create", { + method: "POST", + data, + }); +}; + +// 修改岗位 +// export const updatePost = async (data: Post) => { +// return await request.put({ url: "/system/post/update", data }); +// }; + +export const updatePost = async (data: PostVO) => { + return await request("/system/post/update", { + method: "PUT", + data, + }); +}; + +// 删除岗位 +// export const deletePost = async (id: number) => { +// return await request.delete({ url: "/system/post/delete?id=" + id }); +// }; + +export const deletePost = async (id: number) => { + return await request("/system/post/delete", { + method: "DELETE", + params: { id }, + }); +}; diff --git a/src/services/system/role/index.ts b/src/services/system/role/index.ts new file mode 100644 index 0000000..8448061 --- /dev/null +++ b/src/services/system/role/index.ts @@ -0,0 +1,106 @@ +import { request } from "@umijs/max"; + +export interface RoleVO { + id: number; + name: string; + code: string; + sort: number; + status: number; + type: number; + dataScope: number; + dataScopeDeptIds: number[]; + createTime: Date; + menuIds: number[]; +} + +export interface RolePageReqVO extends PageParam { + name?: string; + code?: string; + status?: string; + createTime?: Date[]; +} + +export interface UpdateStatusReq { + id: number; + status: number; +} + +// 查询角色列表 +export const getRolePage = (params: RolePageReqVO) => { + return request("/system/role/page", { + method: "GET", + params, + }); +}; +// export const getRolePage = async ( +// params: RolePageReq +// ): Promise> => { +// return await request.get({ url: "/system/role/page", params }); +// }; + +// 查询角色(精简)列表 + +export const getSimpleRoleList = () => { + return request("/system/role/simple-list", { + method: "GET", + }); +}; +// export const getSimpleRoleList = async (): Promise => { +// return await request.get({ url: "/system/role/simple-list" }); +// }; + +// 查询角色详情 +export const getRole = (id: number) => { + return request("/system/role/get", { + method: "GET", + params: { id }, + }); +}; +// export const getRole = async (id: number) => { +// return await request.get({ url: "/system/role/get?id=" + id }); +// }; + +// 新增角色 +export const createRole = (data: RoleVO) => { + return request("/system/role/create", { + method: "POST", + data, + }); +}; + +// export const createRole = async (data: Role) => { +// return await request.post({ url: "/system/role/create", data }); +// }; + +// 修改角色 +// export const updateRole = async (data: Role) => { +// return await request.put({ url: "/system/role/update", data }); +// }; +export const updateRole = (data: RoleVO) => { + return request("/system/role/update", { + method: "PUT", + data, + }); +}; + +// 修改角色状态 +export const updateRoleStatus = (data: UpdateStatusReq) => { + return request("/system/role/update-status", { + method: "PUT", + data, + }); +}; +// export const updateRoleStatus = async (data: UpdateStatusReq) => { +// return await request.put({ url: "/system/role/update-status", data }); +// }; + +// 删除角色 +export const deleteRole = (id: number) => { + return request("/system/role/delete", { + method: "DELETE", + params: { id }, + }); +}; +// export const deleteRole = async (id: number) => { +// return await request.delete({ url: "/system/role/delete?id=" + id }); +// }; diff --git a/src/services/system/tenant/package.ts b/src/services/system/tenant/package.ts index 3299d6d..effed7c 100644 --- a/src/services/system/tenant/package.ts +++ b/src/services/system/tenant/package.ts @@ -12,11 +12,27 @@ export interface TenantPackageVO { createTime: Date; } + +export interface TenantPackagePageReqVO extends PageParam { + name?: string; + contactName?: string; + contactMobile?: string; + status?: number; + createTime?: Date[]; +} + // 查询租户套餐列表 // export const getTenantPackagePage = (params: PageParam) => { // return request.get({ url: '/system/tenant-package/page', params }) // } +export async function getTenantPackagePage(params: TenantPackagePageReqVO) { + return request("/system/tenant-package/page", { + method: "GET", + params, + }); +} + // 获得租户 // export const getTenantPackage = (id: number) => { // return request.get({ url: '/system/tenant-package/get?id=' + id }) diff --git a/src/services/system/user/index.ts b/src/services/system/user/index.ts new file mode 100644 index 0000000..01590e2 --- /dev/null +++ b/src/services/system/user/index.ts @@ -0,0 +1,154 @@ +// import request from "@/config/axios"; +import { request } from "@umijs/max"; + +export interface UserVO { + id: number; + username: string; + nickname: string; + deptId: number; + postIds: string[]; + email: string; + mobile: string; + sex: number; + avatar: string; + loginIp: string; + status: number; + remark: string; + loginDate: Date; + createTime: Date; +} + +// export interface User { +// id: number; +// username: string; +// nickname: string; +// deptId: number; +// postIds: string[]; +// email: string; +// mobile: string; +// sex: number; +// avatar: string; +// loginIp: string; +// status: number; +// remark: string; +// loginDate: Date; +// createTime: Date; +// } + +export interface UserReqVO extends PageParam { + username?: string; + status?: number; + mobile?: string; + createTime?: Date; + deptId?: number; +} + +// export interface UserReq extends PageParam { +// username?: string; +// status?: number; +// mobile?: string; +// createTime?: Date; +// deptId?: number; +// } + +// 查询用户管理列表 + +export const getUserPage = (params: UserReqVO) => { + return request("/system/user/page", { + method: "GET", + params, + }); +}; + +// export const getUserPage = (params: UserReq) => { +// return request.get({ url: "/system/user/page", params }); +// }; + +// 查询用户详情 +export const getUser = (id: number) => { + return request("/system/user/get", { + method: "GET", + params: { id }, + }); +}; + +// export const getUser = (id: number) => { +// return request.get({ url: "/system/user/get?id=" + id }); +// }; + +// 新增用户 +export const createUser = (data: UserVO) => { + return request("/system/user/create", { + method: "POST", + data, + }); +}; + +// export const createUser = (data: User) => { +// return request.post({ url: "/system/user/create", data }); +// }; + +// 修改用户 + +export const updateUser = (data: UserVO) => { + return request("/system/user/update", { + method: "PUT", + data, + }); +}; +// export const updateUser = (data: User) => { +// return request.put({ url: "/system/user/update", data }); +// }; + +// 删除用户 + +export const deleteUser = (id: number) => { + return request("/system/user/delete", { + method: "DELETE", + params: { id }, + }); +}; +// export const deleteUser = (id: number) => { +// return request.delete({ url: "/system/user/delete?id=" + id }); +// }; + +// 用户密码重置 + +export const resetUserPassword = (id: number, password: string) => { + return request("/system/user/reset-password", { + method: "PUT", + params: { id, password }, + }); +}; +// export const resetUserPassword = (id: number, password: string) => { +// const data = { +// id, +// password +// }; +// return request.put({ url: "/system/user/update-password", data: data }); +// }; + +// 用户状态修改 +export const updateUserStatus = (id: number, status: number) => { + return request("/system/user/update-status", { + method: "PUT", + data: { id, status }, + }); +}; +// export const updateUserStatus = (id: number, status: number) => { +// const data = { +// id, +// status +// }; +// return request.put({ url: "/system/user/update-status", data: data }); +// }; + +// 获取用户精简信息列表 +export const getSimpleUserList = () => { + return request("/system/user/simple-list", { + method: "GET", + }); +}; +// export const getSimpleUserList = (): Promise => { +// return request.get({ url: "/system/user/simple-list" }); +// }; diff --git a/src/types/constant.ts b/src/types/constant.ts new file mode 100644 index 0000000..d09232a --- /dev/null +++ b/src/types/constant.ts @@ -0,0 +1,5 @@ +export type Operations = { + handleSearch?: (evt: MouseEvent) => void; + handleReset?: (evt: MouseEvent) => void; + handleExport?: (evt: MouseEvent) => void; +}; diff --git a/src/utils/constant.ts b/src/utils/constant.ts new file mode 100644 index 0000000..3827781 --- /dev/null +++ b/src/utils/constant.ts @@ -0,0 +1,22 @@ +// 创建 value 到 label 的映射 +const statusMap = (status: { label: string; value: number }[]) => + new Map(status?.map((item) => [item.value, item.label])); + +const packageMap = (status: { id: number; name: string }[]) => + new Map(status?.map((item) => [item.id, item.name])); + +// 获取 label 的函数 +export const getStatusLabel = ( + status: { label: string; value: number }[], + value: number, +): string => { + return statusMap(status).get(value) || ''; +}; + +// 获取 label 的函数 +export const getpackageLabel = ( + status: { id: number; name: string }[], + id: number, +): string => { + return packageMap(status).get(id) || ''; +}; diff --git a/src/utils/tree.ts b/src/utils/tree.ts new file mode 100644 index 0000000..6dc18aa --- /dev/null +++ b/src/utils/tree.ts @@ -0,0 +1,451 @@ +interface TreeHelperConfig { + id: string; + children: string; + pid: string; +} + +const DEFAULT_CONFIG: TreeHelperConfig = { + id: 'id', + children: 'children', + pid: 'pid', +}; +export const defaultProps = { + children: 'children', + label: 'name', + value: 'id', + isLeaf: 'leaf', + emitPath: false, // 用于 cascader 组件:在选中节点改变时,是否返回由该节点所在的各级菜单的值所组成的数组,若设置 false,则只返回该节点的值 +}; + +const getConfig = (config: Partial) => + Object.assign({}, DEFAULT_CONFIG, config); + +// tree from list +export const listToTree = ( + list: any[], + config: Partial = {}, +): T[] => { + const conf = getConfig(config) as TreeHelperConfig; + const nodeMap = new Map(); + const result: T[] = []; + const { id, children, pid } = conf; + + for (const node of list) { + node[children] = node[children] || []; + nodeMap.set(node[id], node); + } + for (const node of list) { + const parent = nodeMap.get(node[pid]); + (parent ? parent.children : result).push(node); + } + return result; +}; + +export const treeToList = ( + tree: any, + config: Partial = {}, +): T => { + config = getConfig(config); + const { children } = config; + const result: any = [...tree]; + for (let i = 0; i < result.length; i++) { + if (!children) return result; + if (!result[i][children]) continue; + result.splice(i + 1, 0, ...result[i][children]); + } + return result; +}; + +export const findNode = ( + tree: any, + func: Fn, + config: Partial = {}, +): T | null => { + config = getConfig(config); + const { children } = config; + const list = [...tree]; + if (!children) return null; + for (const node of list) { + if (func(node)) return node; + node[children] && list.push(...node[children]); + } + return null; +}; + +export const findNodeAll = ( + tree: any, + func: Fn, + config: Partial = {}, +): T[] => { + config = getConfig(config); + const { children } = config; + const list = [...tree]; + const result: T[] = []; + if (!children) return result; + for (const node of list) { + func(node) && result.push(node); + node[children] && list.push(...node[children]); + } + return result; +}; + +export const findPath = ( + tree: any, + func: Fn, + config: Partial = {}, +): T | T[] | null => { + config = getConfig(config); + const path: T[] = []; + const list = [...tree]; + const visitedSet = new Set(); + const { children } = config; + if (!children) return null; + while (list.length) { + const node = list[0]; + if (visitedSet.has(node)) { + path.pop(); + list.shift(); + } else { + visitedSet.add(node); + node[children] && list.unshift(...node[children]); + path.push(node); + if (func(node)) { + return path; + } + } + } + return null; +}; + +export const findPathAll = ( + tree: any, + func: Fn, + config: Partial = {}, +) => { + config = getConfig(config); + const path: any[] = []; + const list = [...tree]; + const result: any[] = []; + const visitedSet = new Set(), + { children } = config; + + if (!children) return []; + while (list.length) { + const node = list[0]; + if (visitedSet.has(node)) { + path.pop(); + list.shift(); + } else { + visitedSet.add(node); + node[children] && list.unshift(...node[children]); + path.push(node); + func(node) && result.push([...path]); + } + } + return result; +}; + +export const filter = ( + tree: T[], + func: (n: T) => boolean, + config: Partial = {}, +): T[] => { + config = getConfig(config); + const children = config.children as string; + + function listFilter(list: T[]) { + return list + .map((node: any) => ({ ...node })) + .filter((node) => { + node[children] = node[children] && listFilter(node[children]); + return func(node) || node[children]?.length; + }); + } + + return listFilter(tree); +}; + +export const forEach = ( + tree: T[], + func: (n: T) => any, + config: Partial = {}, +): void => { + config = getConfig(config); + const list: any[] = [...tree]; + const { children } = config; + for (let i = 0; i < list.length; i++) { + // func 返回true就终止遍历,避免大量节点场景下无意义循环,引起浏览器卡顿 + if (func(list[i])) { + return; + } + children && + list[i][children] && + list.splice(i + 1, 0, ...list[i][children]); + } +}; + +/** + * @description: Extract tree specified structure + */ +export const treeMap = ( + treeData: T[], + opt: { children?: string; conversion: Fn }, +): T[] => { + return treeData.map((item) => treeMapEach(item, opt)); +}; + +/** + * @description: Extract tree specified structure + */ +export const treeMapEach = ( + data: any, + { children = 'children', conversion }: { children?: string; conversion: Fn }, +) => { + const haveChildren = + Array.isArray(data[children]) && data[children].length > 0; + const conversionData = conversion(data) || {}; + if (haveChildren) { + return { + ...conversionData, + [children]: data[children].map((i: number) => + treeMapEach(i, { + children, + conversion, + }), + ), + }; + } else { + return { + ...conversionData, + }; + } +}; + +/** + * 递归遍历树结构 + * @param treeDatas 树 + * @param callBack 回调 + * @param parentNode 父节点 + */ +export const eachTree = (treeDatas: any[], callBack: Fn, parentNode = {}) => { + treeDatas.forEach((element) => { + const newNode = callBack(element, parentNode) || element; + if (element.children) { + eachTree(element.children, callBack, newNode); + } + }); +}; + +/** + * 构造树型结构数据 + * @param {*} data 数据源 + * @param {*} id id字段 默认 'id' + * @param {*} parentId 父节点字段 默认 'parentId' + * @param {*} children 孩子节点字段 默认 'children' + */ +export const handleTree = ( + data: any[], + id?: string, + parentId?: string, + children?: string, +) => { + if (!Array.isArray(data)) { + console.warn('data must be an array'); + return []; + } + const config = { + id: id || 'id', + parentId: parentId || 'parentId', + childrenList: children || 'children', + }; + + const childrenListMap: any = {}; + const nodeIds: any = {}; + const tree: any[] = []; + + for (const d of data) { + const parentId = d[config.parentId]; + if (childrenListMap[parentId] == null) { + childrenListMap[parentId] = []; + } + nodeIds[d[config.id]] = d; + childrenListMap[parentId].push(d); + } + + for (const d of data) { + const parentId = d[config.parentId]; + if (nodeIds[parentId] == null) { + tree.push(d); + } + } + + for (const t of tree) { + adaptToChildrenList(t); + } + + function adaptToChildrenList(o: { [x: string]: any }) { + if (childrenListMap[o[config.id]] !== null) { + o[config.childrenList] = childrenListMap[o[config.id]]; + } + if (o[config.childrenList]) { + for (const c of o[config.childrenList]) { + adaptToChildrenList(c); + } + } + } + + return tree; +}; + +/** + * 构造树型结构数据 + * @param {*} data 数据源 + * @param {*} id id字段 默认 'id' + * @param {*} parentId 父节点字段 默认 'parentId' + * @param {*} children 孩子节点字段 默认 'children' + * @param {*} rootId 根Id 默认 0 + */ +// @ts-expect-error +export const handleTree2 = (data, id, parentId, _children, rootId) => { + id = id || 'id'; + parentId = parentId || 'parentId'; + // children = children || 'children' + rootId = + rootId || + Math.min( + ...data.map((item: { [x: string]: any }) => { + return item[parentId]; + }), + ) || + 0; + // 对源数据深度克隆 + const cloneData = JSON.parse(JSON.stringify(data)); + // 循环所有项 + const treeData = cloneData.filter( + (father: { [x: string]: any; children: any }) => { + const branchArr = cloneData.filter((child: { [x: string]: any }) => { + // 返回每一项的子级数组 + return father[id] === child[parentId]; + }); + + if (branchArr.length > 0) { + father.children = branchArr; + } + // 返回第一层 + return father[parentId] === rootId; + }, + ); + return treeData !== '' ? treeData : data; +}; + +/** + * 校验选中的节点,是否为指定 level + * + * @param tree 要操作的树结构数据 + * @param nodeId 需要判断在什么层级的数据 + * @param level 检查的级别, 默认检查到二级 + * @return true 是;false 否 + */ +export const checkSelectedNode = ( + tree: any[], + nodeId: any, + level = 2, +): boolean => { + if ( + typeof tree === 'undefined' || + !Array.isArray(tree) || + tree.length === 0 + ) { + console.warn('tree must be an array'); + return false; + } + + // 校验是否是一级节点 + if (tree.some((item) => item.id === nodeId)) { + return false; + } + + // 递归计数 + let count = 1; + + // 深层次校验 + function performAThoroughValidation(arr: any[]): boolean { + count += 1; + for (const item of arr) { + if (item.id === nodeId) { + return true; + } else if ( + typeof item.children !== 'undefined' && + item.children.length !== 0 + ) { + if (performAThoroughValidation(item.children)) { + return true; + } + } + } + return false; + } + + for (const item of tree) { + count = 1; + if (performAThoroughValidation(item.children)) { + // 找到后对比是否是期望的层级 + if (count >= level) { + return true; + } + } + } + + return false; +}; + +/** + * 获取节点的完整结构 + * @param tree 树数据 + * @param nodeId 节点 id + */ +export const treeToString = (tree: any[], nodeId: number) => { + if ( + typeof tree === 'undefined' || + !Array.isArray(tree) || + tree.length === 0 + ) { + console.warn('tree must be an array'); + return ''; + } + // 校验是否是一级节点 + const node = tree.find((item) => item.id === nodeId); + if (typeof node !== 'undefined') { + return node.name; + } + let str = ''; + + function performAThoroughValidation(arr: string | any[]) { + if (typeof arr === 'undefined' || !Array.isArray(arr) || arr.length === 0) { + return false; + } + for (const item of arr) { + if (item.id === nodeId) { + str += ` / ${item.name}`; + return true; + } else if ( + typeof item.children !== 'undefined' && + item.children.length !== 0 + ) { + str += ` / ${item.name}`; + if (performAThoroughValidation(item.children)) { + return true; + } + } + } + return false; + } + + for (const item of tree) { + str = `${item.name}`; + if (performAThoroughValidation(item.children)) { + break; + } + } + return str; +};