From e88b4c75669d662d3976bd9a0e754e0a768f083f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=87=91?= Date: Mon, 22 Apr 2024 17:57:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E8=8F=9C=E5=8D=95=E6=A8=A1?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/glass/index.tsx | 38 +++++ src/components/glass/style.ts | 29 ++++ src/i18n.ts | 2 +- src/locales/lang/en-US.ts | 124 ++++++++------ src/locales/lang/pages/system/menus/en-US.ts | 31 +++- src/locales/lang/pages/system/menus/zh-CN.ts | 50 +++--- src/locales/lang/zh-CN.ts | 109 +++++++----- src/pages/system/menus/components/MenuTree.tsx | 143 ++++++++-------- src/pages/system/menus/index.tsx | 220 +++++++++++++------------ src/pages/system/menus/store.ts | 103 +++++++----- src/pages/system/menus/style.ts | 141 ++++++++-------- src/theme/themes/token.ts | 3 + 12 files changed, 588 insertions(+), 405 deletions(-) create mode 100644 src/components/glass/index.tsx create mode 100644 src/components/glass/style.ts diff --git a/src/components/glass/index.tsx b/src/components/glass/index.tsx new file mode 100644 index 0000000..317a702 --- /dev/null +++ b/src/components/glass/index.tsx @@ -0,0 +1,38 @@ +import { Flex } from 'antd' +import { useStyle } from './style.ts' +import React from 'react' + +export interface IClassProps { + enabled: boolean + className?: string + description?: JSX.Element | React.ReactNode + children?: JSX.Element | React.ReactNode +} + +const Glass = (props: IClassProps) => { + const { styles } = useStyle(props) + + if (!props.enabled) { + return props.children + } + + return ( +
+ + {props.description} + +
+ {props.children} +
+ ) +} + +export const withGlass = (Component: React.Component | React.FC | JSX.Element | React.ReactNode) => (props) => { + return ( + + + + ) +} + +export default Glass \ No newline at end of file diff --git a/src/components/glass/style.ts b/src/components/glass/style.ts new file mode 100644 index 0000000..946014c --- /dev/null +++ b/src/components/glass/style.ts @@ -0,0 +1,29 @@ +import { createStyles } from '@/theme' + +export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => { + const prefix = `${prefixCls}-${token?.proPrefix}-glass-wrap` + + const container = css` + position: relative; + ` + + const glass = css` + background-color: transparent; + backdrop-filter: blur(6px); + z-index: 100; + position: absolute; + width: 100%; + height: 100%; + ` + const description = css` + position: absolute; + top: 20%; + width: 100%; + z-index: 101; + ` + return { + container: cx(prefix, props.className, container), + glass, + description, + } +}) \ No newline at end of file diff --git a/src/i18n.ts b/src/i18n.ts index 641af60..d7ca59d 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -35,7 +35,7 @@ export const initI18n = (options?: InitOptions) => { }, }, fallbackLng: 'zh', - debug: false, + debug: true, detection: detectionOptions, interpolation: { escapeValue: false, diff --git a/src/locales/lang/en-US.ts b/src/locales/lang/en-US.ts index 94286ee..fe3de35 100644 --- a/src/locales/lang/en-US.ts +++ b/src/locales/lang/en-US.ts @@ -3,61 +3,85 @@ import menus from './pages/system/menus/en-US' export default { - ...antdEN, + ...antdEN, - error: { - '404': { - title: 'not fund', - message: 'Sorry, not found this page.' - }, - '403': { - title: 'not authorized', - message: 'Sorry, you are not authorized to access this page.' - }, - 'error': { - title: 'error info', - }, + error: { + '404': { + title: 'not fund', + message: 'Sorry, not found this page.' }, - route: { - goBack: 'Go Back', + '403': { + title: 'not authorized', + message: 'Sorry, you are not authorized to access this page.' }, - app: { - header: { - logout: 'logout', - } + 'error': { + title: 'error info', }, - login: { - title: 'Account Password Login', - username: 'Username', - usernameMsg: 'Please enter your username', - password: 'Password', - passwordMsg: 'Please enter your password', - code: 'Verification Code', - codeMsg: 'Please enter the verification code', - submit: 'Login', - success: 'Login success' - }, - home: { - welcome: 'Welcome to' - }, - system: { - menus, - }, - + }, + route: { + goBack: 'Go Back', + }, + app: { + header: { + logout: 'logout', + } + }, + login: { + title: 'Account Password Login', + username: 'Username', + usernameMsg: 'Please enter your username', + password: 'Password', + passwordMsg: 'Please enter your password', + code: 'Verification Code', + codeMsg: 'Please enter the verification code', + submit: 'Login', + success: 'Login success' + }, + home: { + welcome: 'Welcome to' + }, + system: { + menus, + }, + actions: { + news: 'Add newly', + add: 'Add', + cancel: 'Cancel', + delete: 'Delete', + batchDel: 'Batch Delete', + reset: 'Reset', + clear: 'Clear', + close: 'Close', + }, + message: { + infoTitle: 'Hint', errorTitle: 'Error', successTitle: 'Success', - success: 'Submit Success', - fail: 'Submit Fail', - saveSuccess: 'Save Success', - saveFail: 'Save Fail', + warningTitle: 'Warning', + batchDelete: 'Are you sure to delete the selected data?', + deleteConfirm: 'Are you sure to delete it?', + success: 'Submission successful', + fail: 'Submission failed', + saveSuccess: 'Save successfully', + saveFail: 'Save failed', + emptyData: 'No Data', + emptyDataAdd: 'No data at present, click to add', + required: 'This item is a required field', + }, + errorTitle: 'Error', + successTitle: 'Success', + success: 'Submit Success', + fail: 'Submit Fail', + saveSuccess: 'Save Success', + saveFail: 'Save Fail', - tabs: { - refresh: 'Refresh', - maximize: 'Maximize', - closeCurrent: 'Close current', - closeLeft: 'Close Left', - closeRight: 'Close Right', - closeOther: 'Close other', - closeAll: 'Close All' - } + tabs: { + refresh: 'Refresh', + maximize: 'Maximize', + closeCurrent: 'Close current', + closeLeft: 'Close Left', + closeRight: 'Close Right', + closeOther: 'Close other', + closeAll: 'Close All' + } } diff --git a/src/locales/lang/pages/system/menus/en-US.ts b/src/locales/lang/pages/system/menus/en-US.ts index 5c933b1..30f93e2 100644 --- a/src/locales/lang/pages/system/menus/en-US.ts +++ b/src/locales/lang/pages/system/menus/en-US.ts @@ -1,3 +1,32 @@ export default { - + title: 'Menu', + setting: 'Configuration', + saveSuccess: 'Save successfully', + form: { + title: 'Menu name', + parent: 'Upper-level menu', + type: 'Type', + typeOptions: { + menu: 'Menu', + iframe: 'Iframe', + link: 'External link', + button: 'Button', + }, + name: 'Alias', + icon: 'Icon', + sort: 'Sorting', + path: 'Route', + component: 'View', + componentHelp: 'View path, relative to src/pages, menu groups can be left blank', + save: 'Save', + empty: 'Please select a row of data from the left for operation', + table: { + columns: { + name: 'Name', + code: 'Code', + option: 'Option', + } + } + }, + button: 'Buttons' } \ No newline at end of file diff --git a/src/locales/lang/pages/system/menus/zh-CN.ts b/src/locales/lang/pages/system/menus/zh-CN.ts index e1b5538..5100cde 100644 --- a/src/locales/lang/pages/system/menus/zh-CN.ts +++ b/src/locales/lang/pages/system/menus/zh-CN.ts @@ -1,24 +1,32 @@ export default { - title: '菜单', - setting: '配置', - saveSuccess: '保存成功', - form:{ - title: '菜单名称', - parent: '上级菜单', - type:'类型', - typeOptions: { - menu: '菜单', - iframe: 'iframe', - link: '外链', - button: '按钮', - }, - name: '别名', - icon: '图标', - sort: '排序', - path: '路由', - component: '视图', - componentHelp: '视图路径,相对于src/pages,菜单组可以不填', - save: '保存', + title: '菜单', + setting: '配置', + saveSuccess: '保存成功', + form: { + title: '菜单名称', + parent: '上级菜单', + type: '类型', + typeOptions: { + menu: '菜单', + iframe: 'iframe', + link: '外链', + button: '按钮', }, - button: '按钮' + name: '别名', + icon: '图标', + sort: '排序', + path: '路由', + component: '视图', + componentHelp: '视图路径,相对于src/pages,菜单组可以不填', + save: '保存', + empty: '请从左侧选择一行数据操作', + table: { + columns: { + name: '名称', + code: '标识', + option: '操作', + } + } + }, + button: '按钮' } \ No newline at end of file diff --git a/src/locales/lang/zh-CN.ts b/src/locales/lang/zh-CN.ts index 2e0d13c..1d4f455 100644 --- a/src/locales/lang/zh-CN.ts +++ b/src/locales/lang/zh-CN.ts @@ -3,59 +3,78 @@ import menus from './pages/system/menus/zh-CN.ts' export default { - ...antdZh, - error: { - '404': { - title: '无法找到', - message: '找不到此页面' - }, - '403': { - title: '没有权限', - message: '对不起,您没有权限查看此页面。' - }, - 'error': { - title: '错误信息', - }, + ...antdZh, + error: { + '404': { + title: '无法找到', + message: '找不到此页面' }, - route: { - goBack: '返回', + '403': { + title: '没有权限', + message: '对不起,您没有权限查看此页面。' }, - app: { - header: { - logout: '退出登录', - } - }, - login: { - title: '账户密码登录', - username: '用户名', - usernameMsg: '请输入用户名', - password: '密码', - passwordMsg: '请输入密码', - code: '验证码', - codeMsg: '请输入验证码', - submit: '登录', - success: '登录成功' - }, - home: { - welcome: '欢迎使用' + 'error': { + title: '错误信息', }, + }, + route: { + goBack: '返回', + }, + app: { + header: { + logout: '退出登录', + } + }, + login: { + title: '账户密码登录', + username: '用户名', + usernameMsg: '请输入用户名', + password: '密码', + passwordMsg: '请输入密码', + code: '验证码', + codeMsg: '请输入验证码', + submit: '登录', + success: '登录成功' + }, + home: { + welcome: '欢迎使用' + }, - system: { - menus, - }, + system: { + menus, + }, + actions: { + news: '新增加', + add: '添加', + cancel: '取消', + delete: '删除', + batchDel: '批量删除', + reset: '重置', + clear: '清空', + close: '关闭', + }, + message: { + infoTitle: '提示', errorTitle: '错误', successTitle: '成功', + warningTitle: '警告', + batchDelete: '确定要删除所选数据吗?', + deleteConfirm: '确定要删除吗?', success: '提交成功', fail: '提交失败', saveSuccess: '保存成功', saveFail: '保存失败', - tabs: { - refresh: '刷新', - maximize: '最大化', - closeCurrent: '关闭当前', - closeLeft: '关闭左侧', - closeRight: '关闭右侧', - closeOther: '关闭其它', - closeAll: '关闭所有' - } + emptyData: '暂无数据', + emptyDataAdd: '暂无数据,点击添加', + required: '此项为必填项', + }, + tabs: { + refresh: '刷新', + maximize: '最大化', + closeCurrent: '关闭当前', + closeLeft: '关闭左侧', + closeRight: '关闭右侧', + closeOther: '关闭其它', + closeAll: '关闭所有' + } } diff --git a/src/pages/system/menus/components/MenuTree.tsx b/src/pages/system/menus/components/MenuTree.tsx index ff0679b..d5bb025 100644 --- a/src/pages/system/menus/components/MenuTree.tsx +++ b/src/pages/system/menus/components/MenuTree.tsx @@ -4,7 +4,7 @@ import { MenuItem } from '@/types' import { useStyle } from '../style.ts' import { useTranslation } from '@/i18n.ts' import { useSetAtom } from 'jotai' -import { batchIdsAtom, menuDataAtom, selectedMenuAtom } from '../store.ts' +import { batchIdsAtom, defaultMenu, menuDataAtom, selectedMenuAtom } from '../store.ts' import { FormInstance } from 'antd/lib' import { useAtomValue } from 'jotai/index' import { TreeNodeRender } from '../components/TreeNodeRender.tsx' @@ -15,89 +15,78 @@ import { useDeepCompareEffect } from 'react-use' const MenuTree = ({ form }: { form: FormInstance }) => { - const { styles } = useStyle() - const { t } = useTranslation() - const setCurrentMenu = useSetAtom(selectedMenuAtom) - const setIds = useSetAtom(batchIdsAtom) - const { data = [], isLoading } = useAtomValue(menuDataAtom) - const flattenMenusRef = useRef([]) + const { styles } = useStyle() + const { t } = useTranslation() + const setCurrentMenu = useSetAtom(selectedMenuAtom) + const setIds = useSetAtom(batchIdsAtom) + const { data = [], isLoading } = useAtomValue(menuDataAtom) + const flattenMenusRef = useRef([]) - useDeepCompareEffect(() => { + useDeepCompareEffect(() => { - if (isLoading) return + 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) - } + // 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 () => { - setCurrentMenu({} as MenuItem) - } + return () => { + setCurrentMenu({} as MenuItem) + } - }, [ data, isLoading ]) + }, [ data, isLoading ]) - const renderEmpty = () => { - if ((data as any).length > 0 || isLoading) return null - return - - - } + const renderEmpty = () => { + if ((data as any).length > 0 || isLoading) return null + return + + + } - return ( - <> - - { - renderEmpty() - } - { - return () - }} - fieldNames={{ - title: 'title', - key: 'id' - }} - onSelect={(item) => { - const current = flattenMenusRef.current?.find((menu) => menu.id === item[0]) - setCurrentMenu(current as MenuItem) - form.setFieldsValue({ ...current }) - }} - onCheck={(item) => { - setIds(item as number[]) - }} - checkable={true} - showIcon={false} - /> - - - ) + return ( + <> + + { + renderEmpty() + } + { + return () + }} + fieldNames={{ + title: 'title', + key: 'id' + }} + onSelect={(item) => { + const current = flattenMenusRef.current?.find((menu) => menu.id === item[0]) + setCurrentMenu(current as MenuItem) + form.setFieldsValue({ ...current }) + }} + onCheck={(item) => { + setIds(item as number[]) + }} + checkable={true} + showIcon={false} + /> + + + ) } export default MenuTree \ No newline at end of file diff --git a/src/pages/system/menus/index.tsx b/src/pages/system/menus/index.tsx index b0a1338..5478a56 100644 --- a/src/pages/system/menus/index.tsx +++ b/src/pages/system/menus/index.tsx @@ -1,6 +1,7 @@ +import Glass from '@/components/glass' import { useTranslation } from '@/i18n.ts' import { PageContainer, ProCard } from '@ant-design/pro-components' -import { Button, Form, Input, message, Radio, TreeSelect, InputNumber, notification } from 'antd' +import { Button, Form, Input, message, Radio, TreeSelect, InputNumber, notification, Alert } from 'antd' import { useAtomValue } from 'jotai' import { menuDataAtom, saveOrUpdateMenuAtom, selectedMenuAtom } from './store.ts' import IconPicker from '@/components/icon/picker' @@ -17,7 +18,7 @@ import { createLazyFileRoute } from '@tanstack/react-router' const Menus = () => { - const { styles } = useStyle() + const { styles, cx } = useStyle() const { t } = useTranslation() const [ form ] = Form.useForm() const { mutate, isPending, isSuccess, error, isError } = useAtomValue(saveOrUpdateMenuAtom) @@ -26,13 +27,13 @@ const Menus = () => { useEffect(() => { if (isSuccess) { - message.success(t('saveSuccess', '保存成功')) + message.success(t('message.saveSuccess', '保存成功')) } if (isError) { notification.error({ - message: t('errorTitle', '错误'), - description: (error as any).message ?? t('saveFail', '保存失败'), + message: t('message.error', '错误'), + description: (error as any).message ?? t('message.saveFail', '保存失败'), }) } @@ -57,110 +58,121 @@ const Menus = () => { - -
+ + } > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { - return prevValues.id !== curValues.id - }}> - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + return prevValues.id !== curValues.id + }}> + + + + + +
diff --git a/src/pages/system/menus/store.ts b/src/pages/system/menus/store.ts index d932ee7..e1582d8 100644 --- a/src/pages/system/menus/store.ts +++ b/src/pages/system/menus/store.ts @@ -1,67 +1,92 @@ import systemServ from '@/service/system.ts' -import { IPage, IPageResult, MenuItem } from '@/types' +import { IApiResult, IPage, IPageResult, MenuItem } from '@/types' import { IMenu } from '@/types/menus' +import { AxiosResponse } from 'axios' import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query' -import { atom } from 'jotai/index' +import { atom, createStore } from 'jotai' + + +export const defaultMenu = { + parent_id: 0, + type: 'menu', + name: '', + title: '', + icon: '', + path: '', + component: '', + sort: 0, + id: 0, + button: [], +} as MenuItem export const menuPageAtom = atom({}) +const store = createStore() export const menuDataAtom = atomWithQuery>((get) => { - return { - queryKey: [ 'menus', get(menuPageAtom) ], - queryFn: async ({ queryKey: [ , page ] }) => { - return await systemServ.menus.list(page) - }, - select: (data) => { - return data.rows ?? [] - } + return { + queryKey: [ 'menus', get(menuPageAtom) ], + queryFn: async ({ queryKey: [ , page ] }) => { + return await systemServ.menus.list(page) + }, + select: (data) => { + return data.rows ?? [] } + } }) export const selectedMenuIdAtom = atom(0) export const selectedMenuAtom = atom({} as MenuItem) export const byIdMenuAtom = atomWithQuery((get) => ({ - queryKey: [ 'selectedMenu', get(selectedMenuIdAtom) ], - queryFn: async ({ queryKey: [ , id ] }) => { - return await systemServ.menus.info(id as number) - }, - select: data => data.data, + queryKey: [ 'selectedMenu', get(selectedMenuIdAtom) ], + queryFn: async ({ queryKey: [ , id ] }) => { + return await systemServ.menus.info(id as number) + }, + select: data => data.data, })) -export const saveOrUpdateMenuAtom = atomWithMutation((get) => { +export const saveOrUpdateMenuAtom = atomWithMutation>((get) => { - return { - mutationKey: [ 'updateMenu', get(selectedMenuIdAtom) ], - mutationFn: async (data: IMenu) => { - if (data.id === 0) { - return await systemServ.menus.add(data) - } - return await systemServ.menus.update(data) - }, - onSuccess: () => { - // console.log(data) - //更新列表 - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore fix - get(queryClientAtom).refetchQueries([ 'menus', get(menuPageAtom) ]).then() - } + return { + mutationKey: [ 'updateMenu', get(selectedMenuIdAtom) ], + mutationFn: async (data: IMenu) => { + if (data.id === 0) { + return await systemServ.menus.add(data) + } + return await systemServ.menus.update(data) + }, + onSuccess: (res) => { + const menu = get(selectedMenuAtom) + console.log({ + ...menu, + id: res.data?.id + }) + store.set(selectedMenuAtom, { + ...menu, + id: res.data?.id + }) + //更新列表 + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore fix + get(queryClientAtom).refetchQueries([ 'menus', get(menuPageAtom) ]).then() } + } }) export const batchIdsAtom = atom([]) export const deleteMenuAtom = atomWithMutation((get) => { - return { - mutationKey: [ 'deleteMenu', get(batchIdsAtom) ], - mutationFn: async (ids?: number[]) => { - return await systemServ.menus.batchDelete(ids ?? get(batchIdsAtom)) - }, - onSuccess: (data) => { - console.log(data) - } + return { + mutationKey: [ 'deleteMenu', get(batchIdsAtom) ], + mutationFn: async (ids?: number[]) => { + return await systemServ.menus.batchDelete(ids ?? get(batchIdsAtom)) + }, + onSuccess: () => { + store.set(batchIdsAtom, []) + get(queryClientAtom).refetchQueries([ 'menus', get(menuPageAtom) ]).then() } + } }) \ No newline at end of file diff --git a/src/pages/system/menus/style.ts b/src/pages/system/menus/style.ts index 77582f2..92550af 100644 --- a/src/pages/system/menus/style.ts +++ b/src/pages/system/menus/style.ts @@ -1,72 +1,79 @@ 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}; - ` - - const form = css` - display: flex; - flex-wrap: wrap; - min-width: 500px; - ` - - const formSetting = css` - flex: 1; - - ` - - const formButtons = css` - width: 500px; - - ` - - 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` - - ` - - return { - container: cx(prefix), - box, - tree, - form, - treeNode, - treeActions, - formSetting, - formButtons, + 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}; + ` + const emptyForm = css` + backdrop-filter: ${token.backdropFilter}; + color: red; + ` + + const form = css` + display: flex; + flex-wrap: wrap; + min-width: 500px; + ` + + const formSetting = css` + flex: 1; + + ` + + const formButtons = css` + width: 500px; + + ` + + 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` + + ` + + return { + container: cx(prefix), + box, + emptyForm, + tree, + form, + treeNode, + treeActions, + formSetting, + formButtons, + } }) \ No newline at end of file diff --git a/src/theme/themes/token.ts b/src/theme/themes/token.ts index 80a6d96..0250e31 100644 --- a/src/theme/themes/token.ts +++ b/src/theme/themes/token.ts @@ -8,6 +8,7 @@ export interface ProThemeToken { colorTypeBoolArray: string; colorTypeNumberArray: string; colorTypeStringArray: string; + filterBackdrop: string; } export const getProToken: GetCustomToken = () => ({ @@ -19,6 +20,8 @@ export const getProToken: GetCustomToken = () => ({ colorTypeNumberArray: '#239BEF', colorTypeStringArray: '#62AE8D', + filterBackdrop: 'blur(6px)' + }) export const themeToken = getProToken({} as any) \ No newline at end of file