From 2b470f64e2583f7cf9e4b2458b97e37789d45481 Mon Sep 17 00:00:00 2001 From: dark Date: Sun, 30 Jun 2024 21:31:44 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E9=A1=B5=E9=9D=A2=E5=B8=83?= =?UTF-8?q?=E5=B1=80=EF=BC=8C=E5=A2=9E=E5=8A=A0PageTitle=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.css | 4 + src/components/avatar/index.tsx | 14 ++- src/components/loading/FetchLoading.tsx | 2 +- src/components/table/style.ts | 2 +- src/global.d.ts | 2 + src/hooks/useInlineStyle.ts | 65 ++++++++++ src/hooks/useResizeObserver.ts | 63 ++++++++++ src/layout/ListPageLayout.tsx | 20 +++- src/layout/RootLayout.tsx | 27 ++++- src/layout/TwoColPageLayout.tsx | 21 +++- src/layout/style.ts | 21 +++- src/pages/system/menus/index.tsx | 8 +- src/pages/websites/domain/index.tsx | 5 +- src/store/system.ts | 5 + src/store/system/user.ts | 203 +++++++++++++++++--------------- src/utils/index.ts | 9 +- 16 files changed, 349 insertions(+), 122 deletions(-) create mode 100644 src/hooks/useInlineStyle.ts create mode 100644 src/hooks/useResizeObserver.ts diff --git a/src/App.css b/src/App.css index 5f9d2d3..6261550 100644 --- a/src/App.css +++ b/src/App.css @@ -17,4 +17,8 @@ justify-content: center; gap: 5px; } +} + +.ant-drawer .ant-drawer-footer{ + background-color: #fcfcfc; } \ No newline at end of file diff --git a/src/components/avatar/index.tsx b/src/components/avatar/index.tsx index be5ec9c..96c9516 100644 --- a/src/components/avatar/index.tsx +++ b/src/components/avatar/index.tsx @@ -1,19 +1,27 @@ import Icon from '@/components/icon' import { useTranslation } from '@/i18n.ts' -import { currentUserAtom, logoutAtom } from '@/store/system/user.ts' +import { currentStaticUserAtom, currentUserAtom, logoutAtom } from '@/store/system/user.ts' import { Avatar as AntAvatar, Dropdown, Spin } from 'antd' -import { useAtomValue } from 'jotai' +import { useAtomValue, useSetAtom } from 'jotai' import { useNavigate } from '@tanstack/react-router' import { useStyle } from './style' +import { useEffect } from 'react' const Avatar = () => { const { styles } = useStyle() const { t } = useTranslation() - const { data, isLoading } = useAtomValue(currentUserAtom) + const { data, isLoading, isSuccess } = useAtomValue(currentUserAtom) + const updateUser = useSetAtom(currentStaticUserAtom) const { mutate: logout } = useAtomValue(logoutAtom) const navigate = useNavigate() + useEffect(() => { + if (isSuccess) { + updateUser(data) + } + }, [ isSuccess ]) + return (
{ const isFetching = useIsFetching() - return ( + return ( <> 0}/> diff --git a/src/components/table/style.ts b/src/components/table/style.ts index 96f6ddf..e6d24de 100644 --- a/src/components/table/style.ts +++ b/src/components/table/style.ts @@ -10,7 +10,7 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) --toolbar-height: 65px; --alter-height: 0px; --padding: 33px; - --table-body-height: calc(var(--toolbar-height, 65px) + var(--alter-height, 0px) + var(--header-height, 56px) + var(--padding, 20px) * 4); + --table-body-height: calc( var(--pageHeader, 0px) + var(--toolbar-height, 65px) + var(--alter-height, 0px) + var(--header-height, 56px) + var(--padding, 20px) * 4); .ant-table-body { overflow: auto scroll; diff --git a/src/global.d.ts b/src/global.d.ts index e15e348..26ae21c 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -3,6 +3,7 @@ import { QueryClient } from '@tanstack/react-query' import { Router } from '@tanstack/react-router' import { RouteOptions } from '@tanstack/react-router/src/route.ts' import { Attributes, ReactNode } from 'react' +import { System } from '@/types' export type LayoutType = 'list' | 'form' | 'tree' | 'normal' @@ -13,6 +14,7 @@ export type IAppData = { baseUrl: string; token: string; device: string; + currentUser: System.IUser } export type TRouter = { diff --git a/src/hooks/useInlineStyle.ts b/src/hooks/useInlineStyle.ts new file mode 100644 index 0000000..01f865b --- /dev/null +++ b/src/hooks/useInlineStyle.ts @@ -0,0 +1,65 @@ +import React, { useEffect, useRef } from 'react' + +interface UseStyleOptions { + selector?: string; + styles: React.CSSProperties & { [key: `--${string}`]: string }; +} + +const applyStyles = (element: HTMLElement, styles: React.CSSProperties) => { + Object.entries(styles).forEach(([ key, value ]) => { + element.style.setProperty(key, value as string) + }) +} + +const removeStyles = (element: HTMLElement, styles: React.CSSProperties) => { + Object.keys(styles).forEach(key => { + element.style.removeProperty(key) + }) +} + +function useInlineStyle({ selector, styles }: UseStyleOptions) { + const elementRef = useRef(null) + + useEffect(() => { + + + let targetElement: HTMLElement | null = null + + const manageStyles = () => { + targetElement = selector ? document.querySelector(selector) : elementRef.current + + if (!targetElement) return + + applyStyles(targetElement, styles) + + return () => { + if (targetElement) { + removeStyles(targetElement, styles) + } + } + } + + if (selector) { + if (document.readyState === 'complete' || document.readyState === 'interactive') { + manageStyles() + } else { + document.addEventListener('DOMContentLoaded', manageStyles) + return () => { + document.removeEventListener('DOMContentLoaded', manageStyles) + } + } + } else { + manageStyles() + } + + return () => { + if (targetElement) { + removeStyles(targetElement, styles) + } + } + }, [ selector, styles ]) + + return elementRef +} + +export default useInlineStyle diff --git a/src/hooks/useResizeObserver.ts b/src/hooks/useResizeObserver.ts new file mode 100644 index 0000000..df18b73 --- /dev/null +++ b/src/hooks/useResizeObserver.ts @@ -0,0 +1,63 @@ +import { useState, useEffect, useRef } from 'react' + +interface UseResizeObserverOptions { + selector?: string; +} + +interface Size { + width: number; + height: number; + inlineSize: number; + blockSize: number; +} + +function useResizeObserver({ selector }: UseResizeObserverOptions = {}) { + const [ size, setSize ] = useState({ width: 0, height: 0, inlineSize: 0, blockSize: 0 }) + const elementRef = useRef(null) + + useEffect(() => { + const handleResize = (entries: ResizeObserverEntry[]) => { + if (entries[0]) { + const [ { inlineSize, blockSize } ] = entries[0].borderBoxSize + const { width, height } = entries[0].contentRect + setSize({ width, height, inlineSize, blockSize }) + } + } + + let resizeObserver: ResizeObserver | null = null + let targetElement: HTMLElement | null = null + + const observeElement = () => { + targetElement = selector ? document.querySelector(selector) : elementRef.current + + if (!targetElement) return + + resizeObserver = new ResizeObserver(handleResize) + resizeObserver.observe(targetElement) + } + + if (selector) { + if (document.readyState === 'complete' || document.readyState === 'interactive') { + observeElement() + } else { + document.addEventListener('DOMContentLoaded', observeElement) + return () => { + document.removeEventListener('DOMContentLoaded', observeElement) + } + } + } else { + observeElement() + } + + return () => { + if (resizeObserver && targetElement) { + resizeObserver.unobserve(targetElement) + resizeObserver.disconnect() + } + } + }, [ selector, elementRef.current ]) + + return [ elementRef, size ] as const +} + +export default useResizeObserver diff --git a/src/layout/ListPageLayout.tsx b/src/layout/ListPageLayout.tsx index 0caa39d..d1d1afd 100644 --- a/src/layout/ListPageLayout.tsx +++ b/src/layout/ListPageLayout.tsx @@ -1,6 +1,10 @@ import React from 'react' import { useStyle } from '@/layout/style.ts' import { PageContainer, PageContainerProps } from '@ant-design/pro-components' +import { useAtomValue } from 'jotai' +import { currentMenuAtom } from '@/store/system.ts' +import useResizeObserver from '@/hooks/useResizeObserver.ts' +import useInlineStyle from '@/hooks/useInlineStyle.ts' interface IListPageLayoutProps extends PageContainerProps { children: React.ReactNode @@ -11,13 +15,25 @@ const ListPageLayout: React.FC = ( { className, children, authHeight = true, ...props }) => { - const { styles, cx } = useStyle({ className: 'two-col' }) + const { styles, cx } = useStyle({ className: 'one-col' }) + const currentMenu = useAtomValue(currentMenuAtom) + const [ , headerSize ] = useResizeObserver({ + selector: '.ant-page-header', + }) + useInlineStyle({ + styles: { + '--pageHeader': `${headerSize.blockSize}px`, + }, + selector: `.one-col`, + }) return ( <> { const navigate = useNavigate() const { styles } = useStyle() + const currentUser = useAtomValue(currentStaticUserAtom) const { data: menuData = [], isLoading } = useAtomValue(userMenuDataAtom) const { language } = useAtomValue(appAtom) + const setCurrentMenu = useSetAtom(currentMenuAtom) const items = getBreadcrumbData(menuData, location.pathname) const [ pathname, setPathname ] = useState(location.pathname) const [ openMenuKeys, setOpenKeys ] = useState([]) @@ -80,6 +82,10 @@ export default () => { } }, [ location.pathname ]) + useEffect(() => { + + }, [location.pathname]) + return (
{ return document.getElementById('crazy-pro-layout') || document.body }} > + { collapsedButtonRender={false} // collapsed={false} postMenuData={() => { - return menuData.filter(item=>!item.hidden).map(item => ({ + return menuData.filter(item => !item.hidden).map(item => ({ ...item, children: [], })) as any @@ -209,6 +224,7 @@ export default () => { onClick={(menu) => { const info = menusFlatten.current?.find(item => item.path === menu.key) if (info) { + setCurrentMenu(info) // setOpenKeys([ info.path as string ]) navigate({ to: info.path, @@ -304,6 +320,7 @@ export default () => { > */} + diff --git a/src/layout/TwoColPageLayout.tsx b/src/layout/TwoColPageLayout.tsx index 512d225..f602e59 100644 --- a/src/layout/TwoColPageLayout.tsx +++ b/src/layout/TwoColPageLayout.tsx @@ -3,6 +3,10 @@ import { PageContainer, PageContainerProps } from '@ant-design/pro-components' import { Flexbox } from 'react-layout-kit' import { DraggablePanel, DraggablePanelProps } from '@/components/draggable-panel' import { useStyle } from './style' +import { useAtomValue } from 'jotai/index' +import { currentMenuAtom } from '@/store/system.ts' +import useResizeObserver from '@/hooks/useResizeObserver.ts' +import useInlineStyle from '@/hooks/useInlineStyle.ts' interface ITreePageLayoutProps { children?: React.ReactNode @@ -14,10 +18,23 @@ interface ITreePageLayoutProps { export const TwoColPageLayout: React.FC = ({ className, ...props }) => { const { styles, cx } = useStyle({ className: 'two-col' }) + const currentMenu = useAtomValue(currentMenuAtom) + const [ , headerSize ] = useResizeObserver({ + selector: '.ant-page-header', + }) + + useInlineStyle({ + styles: { + '--pageHeader': `${headerSize.blockSize}px`, + }, + selector: `.two-col`, + }) + return ( diff --git a/src/layout/style.ts b/src/layout/style.ts index 826f3b8..6cb0c94 100644 --- a/src/layout/style.ts +++ b/src/layout/style.ts @@ -40,6 +40,9 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) padding-block: 20px; } + .ant-page-header{ + background-color: white; + } .ant-page-header-no-children { height: 0px; } @@ -52,8 +55,8 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) .ant-pro-card-body{ padding-block-start: 0; overflow: auto; - height: calc(100vh - 160px); - min-height: calc(100vh - 160px); + height: calc(100vh - 160px - var(--pageHeader, 0px)); + min-height: calc(100vh - 160px - var(--pageHeader, 0px)); ${scrollbarBackground} } @@ -77,6 +80,10 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) height: 100%; } + .layoutkit-flexbox{ + height: calc(100% - var(--pageHeader, 0px)); + } + ` const pageContext = css` @@ -156,7 +163,9 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) } .ant-menu-inline-collapsed >.ant-menu-item{ - padding-inline: calc(50% - 8px); + padding-inline: calc(50% - 8px); + height: 52px; + line-height: 52px; } .ant-menu-inline >.ant-menu-submenu>.ant-menu-submenu-title, @@ -182,6 +191,7 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) .ant-menu-light>.ant-menu .ant-menu-item-selected{ background-color: #3f9eff; color: white; + } ` @@ -281,13 +291,14 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) const body = css` overflow: hidden; - --bodyHeader: 50px; + --bodyHeader: 50px; + --pageHeader: 0px; .ant-pro-page-container { width: 100%; flex: 1; overflow: hidden; - height: calc(100vh - var(--bodyHeader, 50px)); + height: calc(100vh - var(--bodyHeader, 50px) - var(--pageHeader, 0px)); } ` diff --git a/src/pages/system/menus/index.tsx b/src/pages/system/menus/index.tsx index 62c83b6..1230445 100644 --- a/src/pages/system/menus/index.tsx +++ b/src/pages/system/menus/index.tsx @@ -179,6 +179,12 @@ const Menus = () => { + + + { loading={isPending} onClick={() => { form.validateFields().then((values) => { - console.log(values) + // console.log(values) mutate(values) }) }} diff --git a/src/pages/websites/domain/index.tsx b/src/pages/websites/domain/index.tsx index 8a4da42..6be4440 100644 --- a/src/pages/websites/domain/index.tsx +++ b/src/pages/websites/domain/index.tsx @@ -17,6 +17,7 @@ import { useStyle } from './style' import { FilterOutlined } from '@ant-design/icons' import { getValueCount } from '@/utils' import { Table as ProTable } from '@/components/table' +import { Link } from '@tanstack/react-router' const i18nPrefix = 'websiteDomains.list' @@ -48,8 +49,10 @@ const WebsiteDomain = () => { { title: t(`${i18nPrefix}.columns.name`, '域名'), dataIndex: 'name', + render(_text, record){ + return {record.name} + } }, - { title: t(`${i18nPrefix}.columns.dns_account_id`, 'DNS账号'), dataIndex: 'dns_account_id', diff --git a/src/store/system.ts b/src/store/system.ts index c2df969..e451325 100644 --- a/src/store/system.ts +++ b/src/store/system.ts @@ -2,6 +2,8 @@ import { IAppData } from '@/global' import { createStore } from 'jotai' import { atomWithStorage } from 'jotai/utils' import { changeLanguage as setLang } from 'i18next' +import { atom } from 'jotai/index' +import { System } from '@/types' /** * app全局状态 @@ -24,6 +26,9 @@ export const getAppData = () => { return appStore.get(appAtom) } +export const currentMenuAtom = atom(null) + + export const changeLanguage = (lang: string, reload?: boolean) => { setLang(lang) updateAppData({ language: lang }) diff --git a/src/store/system/user.ts b/src/store/system/user.ts index 993a259..02e4836 100644 --- a/src/store/system/user.ts +++ b/src/store/system/user.ts @@ -1,5 +1,5 @@ import { appAtom, setToken } from '@/store/system.ts' -import { atom } from 'jotai/index' +import { atom } from 'jotai' import { IApiResult, IAuth, IPage, IPageResult, MenuItem } from '@/global' import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query' import systemServ from '@/service/system.ts' @@ -7,70 +7,77 @@ import { formatMenuData, isDev } from '@/utils' import { message } from 'antd' import { t } from 'i18next' import { System } from '@/types' +import { atomWithStorage } from 'jotai/utils' +import { IUserInfo } from '@/types/system/user' export const authAtom = atom({ - isLogin: false, - authKey: [] + isLogin: false, + authKey: [] }) const devLogin = { - username: 'SupperAdmin', - password: 'kk123456', - code: '123456' + username: 'SupperAdmin', + password: 'kk123456', + code: '123456' } export const loginFormAtom = atom({ - ...(isDev ? devLogin : {}) + ...(isDev ? devLogin : {}) } as System.LoginRequest) export const loginAtom = atomWithMutation((get) => ({ - mutationKey: [ 'login' ], - mutationFn: async (params) => { - return await systemServ.login(params) - }, - onSuccess: (res) => { - message.success(t('login.success')) - // console.log('login success', res) - get(userMenuDataAtom).refetch().then() - return res.data - }, - retry: false, + mutationKey: [ 'login' ], + mutationFn: async (params) => { + return await systemServ.login(params) + }, + onSuccess: (res) => { + message.success(t('login.success')) + // console.log('login success', res) + get(userMenuDataAtom).refetch().then() + return res.data + }, + retry: false, })) export const logoutAtom = atomWithMutation(() => ({ - mutationKey: [ 'logout' ], - mutationFn: async () => { - setToken('') - return true - }, + mutationKey: [ 'logout' ], + mutationFn: async () => { + setToken('') + return true + }, })) -export const currentUserAtom = atomWithQuery, any, System.IUserInfo>((get) => { - return { - queryKey: [ 'user_info', get(appAtom).token ], - queryFn: async () => { - return await systemServ.user.current() - }, - select: (data) => { - return data.data - } - } -}) +export const currentStaticUserAtom = atomWithStorage('user', null) -export const userMenuDataAtom = atomWithQuery>, any, MenuItem[]>((get) => ({ - enabled: false, - queryKey: [ 'user_menus', get(appAtom).token ], + +export const currentUserAtom = atomWithQuery, any, System.IUserInfo>((get) => { + return { + queryKey: [ 'user_info', get(appAtom).token ], queryFn: async () => { - return await systemServ.user.menus() + return await systemServ.user.current() }, select: (data) => { - return formatMenuData(data.data.rows as any ?? [], []) + // store.set(currentStaticUserAtom, data.data) + return data.data }, - retry: false, + } +}) + + +export const userMenuDataAtom = atomWithQuery>, any, MenuItem[]>((get) => ({ + enabled: false, + queryKey: [ 'user_menus', get(appAtom).token ], + queryFn: async () => { + return await systemServ.user.menus() + }, + select: (data) => { + return formatMenuData(data.data.rows as any ?? [], []) + }, + retry: false, })) export type UserSearch = { - dept_id?: any, - key?: string + dept_id?: any, + key?: string } export const userSearchAtom = atom({} as UserSearch) @@ -78,87 +85,87 @@ export const userSearchAtom = atom({} as UserSearch) //=======user page store====== export const userPageAtom = atom({ - pageSize: 10, - page: 1 + pageSize: 10, + page: 1 }) // user list export const userListAtom = atomWithQuery((get) => { - return { - queryKey: [ 'user_list', get(userSearchAtom), get(userPageAtom) ], - queryFn: async ({ queryKey: [ , params, page ] }) => { - return await systemServ.user.list({ - ...params as any, - ...page as any, - }) - }, - select: (data) => { - return data.data - }, + return { + queryKey: [ 'user_list', get(userSearchAtom), get(userPageAtom) ], + queryFn: async ({ queryKey: [ , params, page ] }) => { + return await systemServ.user.list({ + ...params as any, + ...page as any, + }) + }, + select: (data) => { + return data.data + }, - } + } }) // user selected export const userSelectedAtom = atom({} as System.IUser) export const defaultUserData = { - id: 0, - dept_id: 0, - role_id: 0, + id: 0, + dept_id: 0, + role_id: 0, } as System.IUser //save or update user export const saveOrUpdateUserAtom = atomWithMutation((get) => ({ - mutationKey: [ 'save_user' ], - mutationFn: async (params) => { - params.status = params.status ? '1' : '0' - const isAdd = 0 === params.id - if (isAdd) { - return await systemServ.user.add(params) - } - return await systemServ.user.update(params) - }, - onSuccess: (res) => { - const isAdd = !!res.data?.id - message.success(t(isAdd ? 'message.saveSuccess' : 'message.editSuccess', '保存成功')) + mutationKey: [ 'save_user' ], + mutationFn: async (params) => { + params.status = params.status ? '1' : '0' + const isAdd = 0 === params.id + if (isAdd) { + return await systemServ.user.add(params) + } + return await systemServ.user.update(params) + }, + onSuccess: (res) => { + const isAdd = !!res.data?.id + message.success(t(isAdd ? 'message.saveSuccess' : 'message.editSuccess', '保存成功')) - //刷新userList - get(queryClientAtom).invalidateQueries({ queryKey: [ 'user_list' ] }) + //刷新userList + get(queryClientAtom).invalidateQueries({ queryKey: [ 'user_list' ] }) - return res - }, + return res + }, })) //delete user export const batchUserIdsAtom = atom([]) export const deleteUserAtom = atomWithMutation((get) => ({ - mutationKey: [ 'delete_user' ], - mutationFn: async (params) => { - return await systemServ.user.batchDelete(params) - }, - onSuccess: () => { - message.success(t('message.deleteSuccess', '删除成功')) - //刷新userList - get(queryClientAtom).invalidateQueries({ queryKey: [ 'user_list' ] }) - return true - }, + mutationKey: [ 'delete_user' ], + mutationFn: async (params) => { + return await systemServ.user.batchDelete(params) + }, + onSuccess: () => { + message.success(t('message.deleteSuccess', '删除成功')) + //刷新userList + get(queryClientAtom).invalidateQueries({ queryKey: [ 'user_list' ] }) + return true + }, })) //reset password export const resetPasswordAtom = atomWithMutation(() => ({ - mutationKey: [ 'reset_password' ], - mutationFn: async (id) => { - return await systemServ.user.resetPassword(id) - }, - onSuccess: () => { - message.success(t('message.resetSuccess', '重置成功')) - //刷新userList - // get(queryClientAtom).invalidateQueries({ queryKey: [ 'user_list' ] }) - return true - }, - onError: () => { - message.error(t('message.resetError', '重置失败')) - }, + mutationKey: [ 'reset_password' ], + mutationFn: async (id) => { + return await systemServ.user.resetPassword(id) + }, + onSuccess: () => { + message.success(t('message.resetSuccess', '重置成功')) + //刷新userList + // get(queryClientAtom).invalidateQueries({ queryKey: [ 'user_list' ] }) + return true + }, + onError: () => { + message.error(t('message.resetError', '重置失败')) + }, })) diff --git a/src/utils/index.ts b/src/utils/index.ts index 99014c3..586df62 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -64,13 +64,16 @@ export const convertToMenu = (data: any[], format?: (item: any) => any) => { const result: MenuItemProps[] = [] format = format ?? ((item: any) => item) for (const item of data) { + if (item.hidden) { + continue + } const _item = format(item) if (_item.children && _item.children.length) { - result.push( { + result.push({ ..._item, children: convertToMenu(_item.children, format), }) - }else { + } else { result.push(_item) } } @@ -155,7 +158,7 @@ export const getValueCount = (obj: any, filterObj: any = {}) => { // 获取对象中所有值的数量 let count = 0 for (const key in obj) { - if (['page', 'pageSize', 'pageIndex'].includes(key)){ + if ([ 'page', 'pageSize', 'pageIndex' ].includes(key)) { continue } if (Object.prototype.hasOwnProperty.call(obj, key) && obj[key]) {