From bce3c5a86a8d4661e92604e3e9b42617538b6a33 Mon Sep 17 00:00:00 2001 From: dark Date: Sun, 28 Apr 2024 23:53:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E9=83=A8=E9=97=A8=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../departments/components/DepartmentTree.tsx | 80 +++++++++++ .../departments/components/TreeNodeRender.tsx | 59 ++++++++ src/pages/system/departments/index.tsx | 152 ++++++++++++++++++++- src/pages/system/departments/store.ts | 89 ++++++++++++ src/pages/system/departments/style.ts | 72 ++++++++++ src/pages/system/menus/components/MenuTree.tsx | 2 +- src/pages/system/menus/index.tsx | 15 +- src/pages/system/menus/style.ts | 36 ++--- src/pages/system/roles/index.tsx | 5 - src/pages/system/users/index.tsx | 4 - src/service/system.ts | 6 +- src/types/department.d.ts | 2 + 12 files changed, 476 insertions(+), 46 deletions(-) create mode 100644 src/pages/system/departments/components/DepartmentTree.tsx create mode 100644 src/pages/system/departments/components/TreeNodeRender.tsx create mode 100644 src/pages/system/departments/store.ts create mode 100644 src/pages/system/departments/style.ts diff --git a/src/pages/system/departments/components/DepartmentTree.tsx b/src/pages/system/departments/components/DepartmentTree.tsx new file mode 100644 index 0000000..4931a56 --- /dev/null +++ b/src/pages/system/departments/components/DepartmentTree.tsx @@ -0,0 +1,80 @@ +import { Empty, Spin, Tree } from 'antd' +import { useStyle } from '../style.ts' +import { useTranslation } from '@/i18n.ts' +import { useSetAtom } from 'jotai' +import { useDepartStore } from '../store.ts' +import { FormInstance } from 'antd/lib' +import { useAtomValue } from 'jotai/index' +import { TreeNodeRender } from './TreeNodeRender.tsx' +import { useRef } from 'react' +import { flattenTree } from '@/utils' +import { useDeepCompareEffect } from 'react-use' +import { IDepartment } from '@/types/department' + +export const DepartmentTree = ({ form }: { form: FormInstance }) => { + + const { selectedDepartAtom, departTreeAtom, batchIdsAtom } = useDepartStore() + const { styles } = useStyle() + const { t } = useTranslation() + const setIds = useSetAtom(batchIdsAtom) + const setCurrent = useSetAtom(selectedDepartAtom) + const { data = [], isLoading } = useAtomValue(departTreeAtom) + const flattenMenusRef = useRef([]) + + useDeepCompareEffect(() => { + + if (isLoading) return + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore array + if (data.length) { + // @ts-ignore flattenTree + flattenMenusRef.current = flattenTree(data as any) + // console.log(flattenMenusRef.current) + } + + return () => { + setCurrent({} as IDepartment) + } + + }, [ data, isLoading ]) + + const renderEmpty = () => { + if ((data as any).length > 0 || isLoading) return null + return + } + + return (<> + + { + renderEmpty() + } + { + return () + }} + fieldNames={{ + title: 'name', + key: 'id' + }} + onSelect={(item) => { + const current = flattenMenusRef.current?.find((menu) => menu.id === item[0]) + setCurrent(current as IDepartment) + form.setFieldsValue({ ...current }) + }} + onCheck={(item) => { + setIds(item as number[]) + }} + // checkable={true} + showIcon={false} + /> + + + ) +} + +export default DepartmentTree \ No newline at end of file diff --git a/src/pages/system/departments/components/TreeNodeRender.tsx b/src/pages/system/departments/components/TreeNodeRender.tsx new file mode 100644 index 0000000..31eec28 --- /dev/null +++ b/src/pages/system/departments/components/TreeNodeRender.tsx @@ -0,0 +1,59 @@ +import { memo } from 'react' +import { MenuItem } from '@/types' +import { Popconfirm, Space, TreeDataNode } from 'antd' +import { FormInstance } from 'antd/lib' +import { useTranslation } from '@/i18n.ts' +import { useStyle } from '../style.ts' +import { useAtomValue, useSetAtom } from 'jotai/index' +import { useDepartStore } from '../store.ts' +import { PlusOutlined } from '@ant-design/icons' +import ActionIcon, { DeleteAction } from '@/components/icon/action' + +export const TreeNodeRender = memo(({ node, form }: { node: MenuItem & TreeDataNode, form: FormInstance }) => { + const { name } = node + const { t } = useTranslation() + const { styles } = useStyle() + const { selectedDepartAtom, deleteDepartAtom, defaultDepart } = useDepartStore() + const { mutate } = useAtomValue(deleteDepartAtom) + + const setCurrent = useSetAtom(selectedDepartAtom) + + return ( +
+ {name as any} + + + } + title={t('actions.add', '添加')} + onClick={(e) => { + // console.log('add') + e.stopPropagation() + e.preventDefault() + const data = { + ...defaultDepart, + parent_id: node.id, + } + setCurrent(data) + form.setFieldsValue(data) + + }}/> + { + mutate([ (node as any).id ]) + }} + > + { + e.stopPropagation() + e.stopPropagation() + }}/> + + + +
+ ) +}) \ No newline at end of file diff --git a/src/pages/system/departments/index.tsx b/src/pages/system/departments/index.tsx index a5b0031..2291df5 100644 --- a/src/pages/system/departments/index.tsx +++ b/src/pages/system/departments/index.tsx @@ -1,16 +1,156 @@ -import { PageContainer } from '@ant-design/pro-components' -import { createLazyFileRoute } from '@tanstack/react-router' +import { PageContainer, ProCard } from '@ant-design/pro-components' +import { Flexbox } from 'react-layout-kit' +import { DraggablePanel } from '@/components/draggable-panel' +import { useTranslation } from '@/i18n.ts' +import { useStyle } from './style.ts' +import DepartmentTree from './components/DepartmentTree.tsx' +import { Alert, Button, Divider, Form, Input, InputNumber, InputRef, notification, TreeSelect } from 'antd' +import { PlusOutlined } from '@ant-design/icons' +import { useDepartStore } from './store.ts' +import { useAtom, useAtomValue, } from 'jotai' +import Glass from '@/components/glass' +import { useEffect, useRef } from 'react' const Departments = () => { + + const { t } = useTranslation() + const { styles, cx } = useStyle() + const [ form ] = Form.useForm() + const inputRef = useRef() + const { defaultDepart, selectedDepartAtom, departTreeAtom, saveOrUpdateDepartAtom } = useDepartStore() + const { data } = useAtomValue(departTreeAtom) + const { mutate, isPending, isError, error } = useAtomValue(saveOrUpdateDepartAtom) + const [ current, setCurrent ] = useAtom(selectedDepartAtom) + + useEffect(() => { + + if (isError) { + notification.error({ + message: t('message.error', '错误'), + description: (error as any).message ?? t('message.saveFail', '保存失败'), + }) + } + }, [ isError ]) + + useEffect(() => { + if (current.id === 0 && inputRef.current) { + inputRef.current.focus() + } + }, [ current ]) + return ( - + + + + + + +
+ + +
+
+ + + + } + > + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
) } -export const Route = createLazyFileRoute("/system/departments")({ - component: Departments -}) export default Departments \ No newline at end of file diff --git a/src/pages/system/departments/store.ts b/src/pages/system/departments/store.ts new file mode 100644 index 0000000..a130b34 --- /dev/null +++ b/src/pages/system/departments/store.ts @@ -0,0 +1,89 @@ +import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query' +import systemServ from '@/service/system.ts' +import { IApiResult, IPage } from '@/types' +import { IDepartment } from '@/types/department' +import { atom, createStore } from 'jotai' +import { t } from 'i18next' +import { message } from 'antd' + + +const store = createStore() + +const departPageAtom = atom({}) + +const defaultDepart = { + id: 0, + parent_id: 0, + name: '', + manager_user_id: 0, + phone: '', + sort: 0, +} as IDepartment + +const batchIdsAtom = atom([]) + +const selectedDepartAtom = atom({} as IDepartment) + +const departTreeAtom = atomWithQuery(() => { + + return { + queryKey: [ 'departTree' ], + queryFn: async () => { + return await systemServ.dept.tree() + }, + select: (res) => { + return res.data.tree ?? [] + } + } +}) + +const saveOrUpdateDepartAtom = atomWithMutation((get) => { + + return { + mutationKey: [ 'saveOrUpdateDepart', get(selectedDepartAtom) ], + mutationFn: async (data: IDepartment) => { + if (data.id) { + return await systemServ.dept.update(data) + } + return await systemServ.dept.add(data) + }, + onSuccess: (res) => { + const isAdd = !!res.data?.id + message.success(t(isAdd ? 'message.saveSuccess' : 'message.editSuccess', '保存成功')) + store.set(selectedDepartAtom, prev => ({ + ...prev, + ...(isAdd ? res.data : {}) + })) + get(queryClientAtom).invalidateQueries({ queryKey: [ 'departTree', get(departPageAtom) ] }).then() + } + } + +}) + + +const deleteDepartAtom = atomWithMutation((get) => { + + return { + mutationKey: [ 'deleteDepart', get(batchIdsAtom) ], + mutationFn: async (ids: number[]) => { + return await systemServ.dept.batchDelete(ids) + }, + onSuccess: () => { + message.success(t('message.deleteSuccess', '删除成功')) + store.set(batchIdsAtom, []) + get(queryClientAtom).invalidateQueries({ queryKey: [ 'departTree', get(departPageAtom) ] }).then() + + } + } + +}) + +export const useDepartStore = () => ({ + defaultDepart, + departPageAtom, + selectedDepartAtom, + departTreeAtom, + deleteDepartAtom, + batchIdsAtom, + saveOrUpdateDepartAtom, +}) diff --git a/src/pages/system/departments/style.ts b/src/pages/system/departments/style.ts new file mode 100644 index 0000000..a6d561b --- /dev/null +++ b/src/pages/system/departments/style.ts @@ -0,0 +1,72 @@ +import { createStyles } from '@/theme' + +export const useStyle = createStyles(({ token, css, cx, prefixCls }) => { + const prefix = `${prefixCls}-${token?.proPrefix}-department-page` + + + const tree = css` + .ant-tree { + overflow: auto; + height: 100%; + border-right: 1px solid ${token.colorBorder}; + background: ${token.colorBgContainer}; + + } + + .ant-tree-directory .ant-tree-treenode-selected::before { + background: ${token.colorBgTextHover}; + } + + .ant-tree-treenode:before { + border-radius: ${token.borderRadius}px; + } + ` + + const treeNode = css` + display: flex; + justify-content: space-between; + align-items: center; + + .actions { + display: none; + padding: 0 10px; + } + + &:hover .actions { { + display: flex; + } + + ` + const treeActions = css` + padding: 0 24px 16px; + display: flex; + flex-direction: column; + position: sticky; + bottom: 0; + z-index: 10; + background: ${token.colorBgContainer}; + ` + + const box = css` + flex: 1; + background: ${token.colorBgContainer}; + ` + const form = css` + //display: flex; + //flex-wrap: wrap; + min-width: 300px; + max-width: 800px; + ` + const emptyForm = css` + ` + + return { + container: cx(prefix), + box, + form, + emptyForm, + tree, + treeNode, + treeActions + } +}) \ No newline at end of file diff --git a/src/pages/system/menus/components/MenuTree.tsx b/src/pages/system/menus/components/MenuTree.tsx index 71dd022..dc234c0 100644 --- a/src/pages/system/menus/components/MenuTree.tsx +++ b/src/pages/system/menus/components/MenuTree.tsx @@ -6,7 +6,7 @@ import { useSetAtom } from 'jotai' import { batchIdsAtom, menuDataAtom, selectedMenuAtom } from '../store.ts' import { FormInstance } from 'antd/lib' import { useAtomValue } from 'jotai/index' -import { TreeNodeRender } from '../components/TreeNodeRender.tsx' +import { TreeNodeRender } from './TreeNodeRender.tsx' import { useRef } from 'react' import { flattenTree } from '@/utils' import { useDeepCompareEffect } from 'react-use' diff --git a/src/pages/system/menus/index.tsx b/src/pages/system/menus/index.tsx index a1b7be9..17e3694 100644 --- a/src/pages/system/menus/index.tsx +++ b/src/pages/system/menus/index.tsx @@ -14,15 +14,13 @@ import { MenuItem } from '@/types' import MenuTree from './components/MenuTree.tsx' import BatchButton from '@/pages/system/menus/components/BatchButton.tsx' import { useEffect, useRef } from 'react' -import { createLazyFileRoute } from '@tanstack/react-router' - const Menus = () => { const { styles, cx } = useStyle() const { t } = useTranslation() const [ form ] = Form.useForm() - const { mutate, isPending, isSuccess, error, isError } = useAtomValue(saveOrUpdateMenuAtom) + const { mutate, isPending, error, isError } = useAtomValue(saveOrUpdateMenuAtom) const { data = [] } = useAtomValue(menuDataAtom) const [ currentMenu, setMenuData ] = useAtom(selectedMenuAtom) ?? {} const menuInputRef = useRef(undefined) @@ -35,7 +33,7 @@ const Menus = () => { description: (error as any).message ?? t('message.saveFail', '保存失败'), }) } - }, [ isError, isSuccess ]) + }, [ isError ]) useEffect(() => { if (currentMenu.id === 0 && menuInputRef.current) { @@ -71,7 +69,7 @@ const Menus = () => { onClick={() => { const menu = { ...defaultMenu, - parent_id: 0, + parent_id: currentMenu.id ?? 0, } setMenuData(menu) form.setFieldsValue(menu) @@ -97,7 +95,7 @@ const Menus = () => { labelWrap wrapperCol={{ flex: 1 }} colon={false} - className={cx(styles.form, styles.emptyForm, { + className={cx(styles.form, { [styles.emptyForm]: currentMenu.id === undefined })} > @@ -209,9 +207,4 @@ const Menus = () => { ) } -export const Route = createLazyFileRoute('/system/menus')({ - component: Menus -}) - - export default Menus \ No newline at end of file diff --git a/src/pages/system/menus/style.ts b/src/pages/system/menus/style.ts index e0afef4..a5834b1 100644 --- a/src/pages/system/menus/style.ts +++ b/src/pages/system/menus/style.ts @@ -3,24 +3,6 @@ import { createStyles } from '@/theme' export const useStyle = createStyles(({ token, css, cx, prefixCls }) => { const prefix = `${prefixCls}-${token?.proPrefix}-menu-page` - const tree = css` - .ant-tree { - overflow: auto; - height: 100%; - border-right: 1px solid ${token.colorBorder}; - background: ${token.colorBgContainer}; - - } - - .ant-tree-directory .ant-tree-treenode-selected::before { - background: ${token.colorBgTextHover}; - } - - .ant-tree-treenode:before { - border-radius: ${token.borderRadius}px; - } - ` - const box = css` flex: 1; background: ${token.colorBgContainer}; @@ -44,6 +26,24 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }) => { ` + const tree = css` + .ant-tree { + overflow: auto; + height: 100%; + border-right: 1px solid ${token.colorBorder}; + background: ${token.colorBgContainer}; + + } + + .ant-tree-directory .ant-tree-treenode-selected::before { + background: ${token.colorBgTextHover}; + } + + .ant-tree-treenode:before { + border-radius: ${token.borderRadius}px; + } + ` + const treeNode = css` display: flex; justify-content: space-between; diff --git a/src/pages/system/roles/index.tsx b/src/pages/system/roles/index.tsx index 5bc17ce..683552b 100644 --- a/src/pages/system/roles/index.tsx +++ b/src/pages/system/roles/index.tsx @@ -7,7 +7,6 @@ import { ProTable, BetaSchemaForm, ProFormColumnsType, } from '@ant-design/pro-components' -import { createLazyFileRoute } from '@tanstack/react-router' import { useStyle } from './style.ts' import { memo, useEffect, useMemo, useRef, useState } from 'react' import { useAtom, useAtomValue } from 'jotai' @@ -241,8 +240,4 @@ const Roles = memo(() => { ) }) -export const Route = createLazyFileRoute('/system/roles')({ - component: Roles -}) - export default Roles \ No newline at end of file diff --git a/src/pages/system/users/index.tsx b/src/pages/system/users/index.tsx index baa64e4..e70019c 100644 --- a/src/pages/system/users/index.tsx +++ b/src/pages/system/users/index.tsx @@ -1,5 +1,4 @@ import { PageContainer } from '@ant-design/pro-components' -import { createLazyFileRoute } from '@tanstack/react-router' const Users = () => { return ( @@ -9,8 +8,5 @@ const Users = () => { ) } -export const Route = createLazyFileRoute("/system/users")({ - component: Users -}) export default Users \ No newline at end of file diff --git a/src/service/system.ts b/src/service/system.ts index 49061dc..04118e7 100644 --- a/src/service/system.ts +++ b/src/service/system.ts @@ -5,10 +5,14 @@ import { LoginRequest, LoginResponse } from '@/types/login' import { createCURD } from '@/service/base.ts' import { IMenu } from '@/types/menus' import { IRole } from '@/types/roles' +import { IDepartment } from '@/types/department' const systemServ = { dept: { - ...createCURD('/sys/dept') + ...createCURD('/sys/dept'), + tree: () => { + return request.get<{ tree: IDepartment }>('/sys/dept/tree') + } }, menus: { ...createCURD('/sys/menu') diff --git a/src/types/department.d.ts b/src/types/department.d.ts index 96fdf33..835c1ba 100644 --- a/src/types/department.d.ts +++ b/src/types/department.d.ts @@ -8,6 +8,8 @@ export interface IDepartment { sort: number, status: string, remark: string + + children?: IDepartment[] } export interface DepartmentRequest extends IDepartment {