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]) {