Browse Source

完善角色页

main
dark 7 months ago
parent
commit
de211b9924
  1. 7
      src/i18n.ts
  2. 3
      src/locales/lang/en-US.ts
  3. 1
      src/locales/lang/zh-CN.ts
  4. 44
      src/pages/system/menus/components/BatchButton.tsx
  5. 68
      src/pages/system/menus/components/TreeNodeRender.tsx
  6. 5
      src/pages/system/menus/index.tsx
  7. 113
      src/pages/system/menus/store.ts
  8. 50
      src/pages/system/roles/index.tsx
  9. 4
      src/service/base.ts
  10. 6
      src/store/user.ts

7
src/i18n.ts

@ -1,7 +1,7 @@
import { changeLanguage } from '@/store/system.ts' import { changeLanguage } from '@/store/system.ts'
import i18n, { InitOptions } from 'i18next'
import i18n, { InitOptions, t } from 'i18next'
import LanguageDetector from 'i18next-browser-languagedetector' import LanguageDetector from 'i18next-browser-languagedetector'
import { initReactI18next, useTranslation } from 'react-i18next'
import { initReactI18next, useTranslation, } from 'react-i18next'
import { zh, en } from './locales' import { zh, en } from './locales'
const detectionOptions = { const detectionOptions = {
@ -43,10 +43,9 @@ export const initI18n = (options?: InitOptions) => {
...options, ...options,
}) })
} }
export { export {
useTranslation
useTranslation, t
} }
export default i18n export default i18n

3
src/locales/lang/en-US.ts

@ -60,7 +60,8 @@ export default {
warningTitle: 'Warning', warningTitle: 'Warning',
batchDelete: 'Are you sure to delete the selected data?', batchDelete: 'Are you sure to delete the selected data?',
deleteConfirm: 'Are you sure to delete it?', deleteConfirm: 'Are you sure to delete it?',
success: 'Submission successful',
success: 'Submission successfully',
deleteSuccess: 'Delete successfully',
fail: 'Submission failed', fail: 'Submission failed',
saveSuccess: 'Save successfully', saveSuccess: 'Save successfully',
saveFail: 'Save failed', saveFail: 'Save failed',

1
src/locales/lang/zh-CN.ts

@ -63,6 +63,7 @@ export default {
success: '提交成功', success: '提交成功',
fail: '提交失败', fail: '提交失败',
saveSuccess: '保存成功', saveSuccess: '保存成功',
deleteSuccess: '删除成功',
saveFail: '保存失败', saveFail: '保存失败',
emptyData: '暂无数据', emptyData: '暂无数据',
emptyDataAdd: '暂无数据,点击添加', emptyDataAdd: '暂无数据,点击添加',

44
src/pages/system/menus/components/BatchButton.tsx

@ -5,28 +5,30 @@ import { useTranslation } from '@/i18n.ts'
const BatchButton = () => { const BatchButton = () => {
const { t } = useTranslation()
const { isPending, mutate, } = useAtomValue(deleteMenuAtom)
const ids = useAtomValue(batchIdsAtom)
if (ids.length === 0) {
return null
}
const { t } = useTranslation()
const { isPending, mutate, } = useAtomValue(deleteMenuAtom)
const ids = useAtomValue(batchIdsAtom)
return (
<Popconfirm
onConfirm={() => {
mutate(ids as number[])
}}
title={t('message.batchDelete', '确定要删除所选数据吗?')}>
<Button
type="primary"
danger={true}
size={'small'}
disabled={ids.length === 0}
loading={isPending}
>{t('actions.batchDel', '批量删除')}</Button>
</Popconfirm>
)
if (ids.length === 0) {
return null
}
return (
<Popconfirm
onConfirm={() => {
mutate(ids as number[])
}}
title={t('message.batchDelete', '确定要删除所选数据吗?')}>
<Button
type="primary"
danger={true}
size={'small'}
disabled={ids.length === 0}
loading={isPending}
>{t('actions.batchDel', '批量删除')}</Button>
</Popconfirm>
)
} }
export default BatchButton export default BatchButton

68
src/pages/system/menus/components/TreeNodeRender.tsx

@ -10,49 +10,49 @@ import { PlusOutlined } from '@ant-design/icons'
import ActionIcon, { DeleteAction } from '@/components/icon/action' import ActionIcon, { DeleteAction } from '@/components/icon/action'
export const TreeNodeRender = memo(({ node, form }: { node: MenuItem & TreeDataNode, form: FormInstance }) => { export const TreeNodeRender = memo(({ node, form }: { node: MenuItem & TreeDataNode, form: FormInstance }) => {
const { title } = node
const { t } = useTranslation()
const { styles } = useStyle()
const { mutate, } = useAtomValue(deleteMenuAtom)
const { title } = node
const { t } = useTranslation()
const { styles } = useStyle()
const { mutate } = useAtomValue(deleteMenuAtom)
const setMenuData = useSetAtom(selectedMenuAtom)
const setMenuData = useSetAtom(selectedMenuAtom)
return (
<div className={styles.treeNode}>
<span>{title as any}</span>
<span className={'actions'}>
return (
<div className={styles.treeNode}>
<span>{title as any}</span>
<span className={'actions'}>
<Space size={'middle'}> <Space size={'middle'}>
<ActionIcon <ActionIcon
size={12}
icon={<PlusOutlined/>}
title={t('actions.add', '添加')}
onClick={(e) => {
// console.log('add')
e.stopPropagation()
e.preventDefault()
const menu = {
...defaultMenu,
parent_id: node.id,
}
setMenuData(menu)
form.setFieldsValue(menu)
size={12}
icon={<PlusOutlined/>}
title={t('actions.add', '添加')}
onClick={(e) => {
// console.log('add')
e.stopPropagation()
e.preventDefault()
const menu = {
...defaultMenu,
parent_id: node.id,
}
setMenuData(menu)
form.setFieldsValue(menu)
}}/>
}}/>
<Popconfirm <Popconfirm
title={t('message.deleteConfirm', '确定要删除吗?')}
onConfirm={() => {
mutate([ (node as any).id ])
}}
title={t('message.deleteConfirm', '确定要删除吗?')}
onConfirm={() => {
mutate([ (node as any).id ])
}}
> >
<DeleteAction <DeleteAction
size={12}
onClick={(e) => {
e.stopPropagation()
e.stopPropagation()
}}/>
size={12}
onClick={(e) => {
e.stopPropagation()
e.stopPropagation()
}}/>
</Popconfirm> </Popconfirm>
</Space> </Space>
</span> </span>
</div>
)
</div>
)
}) })

5
src/pages/system/menus/index.tsx

@ -1,7 +1,7 @@
import Glass from '@/components/glass' import Glass from '@/components/glass'
import { useTranslation } from '@/i18n.ts' import { useTranslation } from '@/i18n.ts'
import { PageContainer, ProCard } from '@ant-design/pro-components' import { PageContainer, ProCard } from '@ant-design/pro-components'
import { Button, Form, Input, message, Radio, TreeSelect, InputNumber, notification, Alert } from 'antd'
import { Button, Form, Input, Radio, TreeSelect, InputNumber, notification, Alert } from 'antd'
import { useAtomValue } from 'jotai' import { useAtomValue } from 'jotai'
import { menuDataAtom, saveOrUpdateMenuAtom, selectedMenuAtom } from './store.ts' import { menuDataAtom, saveOrUpdateMenuAtom, selectedMenuAtom } from './store.ts'
import IconPicker from '@/components/icon/picker' import IconPicker from '@/components/icon/picker'
@ -26,9 +26,6 @@ const Menus = () => {
const currentMenu = useAtomValue<MenuItem>(selectedMenuAtom) ?? {} const currentMenu = useAtomValue<MenuItem>(selectedMenuAtom) ?? {}
useEffect(() => { useEffect(() => {
if (isSuccess) {
message.success(t('message.saveSuccess', '保存成功'))
}
if (isError) { if (isError) {
notification.error({ notification.error({

113
src/pages/system/menus/store.ts

@ -1,23 +1,24 @@
import systemServ from '@/service/system.ts' import systemServ from '@/service/system.ts'
import { IApiResult, IPage, IPageResult, MenuItem } from '@/types' import { IApiResult, IPage, IPageResult, MenuItem } from '@/types'
import { IMenu } from '@/types/menus' import { IMenu } from '@/types/menus'
import { AxiosResponse } from 'axios'
import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query' import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query'
import { atom, createStore } from 'jotai' import { atom, createStore } from 'jotai'
import { message } from 'antd'
import { t } from '@/i18n.ts'
export const defaultMenu = { export const defaultMenu = {
parent_id: 0,
type: 'menu',
name: '',
title: '',
icon: '',
path: '',
component: '',
sort: 0,
id: 0,
button: [],
} as MenuItem
parent_id: 0,
type: 'menu',
name: '',
title: '',
icon: '',
path: '',
component: '',
sort: 0,
id: 0,
button: [],
} as unknown as MenuItem
export const menuPageAtom = atom<IPage>({}) export const menuPageAtom = atom<IPage>({})
@ -25,68 +26,66 @@ const store = createStore()
export const menuDataAtom = atomWithQuery<any, IPageResult<IMenu>>((get) => { export const menuDataAtom = atomWithQuery<any, IPageResult<IMenu>>((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<number>(0) export const selectedMenuIdAtom = atom<number>(0)
export const selectedMenuAtom = atom<MenuItem>({} as MenuItem) export const selectedMenuAtom = atom<MenuItem>({} as MenuItem)
export const byIdMenuAtom = atomWithQuery((get) => ({ 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<IApiResult<IMenu>>((get) => { export const saveOrUpdateMenuAtom = atomWithMutation<IApiResult<IMenu>>((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: (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()
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) => {
message.success(t('message.saveSuccess', '保存成功'))
const menu = get(selectedMenuAtom)
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<number[]>([]) export const batchIdsAtom = atom<number[]>([])
export const deleteMenuAtom = atomWithMutation((get) => { export const deleteMenuAtom = atomWithMutation((get) => {
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()
return {
mutationKey: [ 'deleteMenu', get(batchIdsAtom) ],
mutationFn: async (ids?: number[]) => {
return await systemServ.menus.batchDelete(ids ?? get(batchIdsAtom))
},
onSuccess: () => {
message.success(t('message.deleteSuccess', '删除成功'))
store.set(batchIdsAtom, [])
get(queryClientAtom).refetchQueries([ 'menus', get(menuPageAtom) ]).then()
}
} }
}
}) })

50
src/pages/system/roles/index.tsx

@ -1,29 +1,33 @@
import { ActionType, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components'
import { ActionType, PageContainer, ProColumns, ProTable,BetaSchemaForm } from '@ant-design/pro-components'
import { createLazyFileRoute } from '@tanstack/react-router' import { createLazyFileRoute } from '@tanstack/react-router'
import { useStyle } from './style.ts' import { useStyle } from './style.ts'
import { useMemo, useRef } from 'react'
import { useAtomValue } from 'jotai'
import { rolesAtom } from './store.ts'
import { useMemo, useRef, useState } from 'react'
import { useAtom, useAtomValue } from 'jotai'
import { roleAtom, rolesAtom } from './store.ts'
import { useTranslation } from '@/i18n.ts' import { useTranslation } from '@/i18n.ts'
import { Button, Space, Table } from 'antd'
import { Button, Form, Space, Table } from 'antd'
import { PlusOutlined } from '@ant-design/icons' import { PlusOutlined } from '@ant-design/icons'
const Roles = () => { const Roles = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { styles } = useStyle() const { styles } = useStyle()
const [form] = Form.useForm()
const actionRef = useRef<ActionType>() const actionRef = useRef<ActionType>()
const { data, isLoading } = useAtomValue(rolesAtom) const { data, isLoading } = useAtomValue(rolesAtom)
const [ role, setRole ] = useAtom(roleAtom)
const [ open, setOpen] = useState(false)
const columns = useMemo(() => { const columns = useMemo(() => {
return [ return [
{ title: 'id', dataIndex: 'id', hideInTable: true, hideInSearch: true, },
{ title: '名称', dataIndex: 'name' },
{ title: '别名', dataIndex: 'code' },
{ title: '状态', dataIndex: 'status' },
{ title: 'id', dataIndex: 'id', hideInTable: true, hideInSearch: true, hideInForm: true,},
{ title: '名称', dataIndex: 'name', valueType: 'text',},
{ title: '别名', dataIndex: 'code', valueType: 'text', },
{ title: '状态', dataIndex: 'status', valueType: 'switch', },
{ title: '排序', dataIndex: 'sort', valueType: 'indexBorder', }, { title: '排序', dataIndex: 'sort', valueType: 'indexBorder', },
{ title: '备注', dataIndex: 'remark' },
{ title: '备注', dataIndex: 'remark' ,valueType: 'textarea' },
{ title: '权限', dataIndex: 'permissions', valueType: 'treeSelect', },
{ {
title: '操作', valueType: 'option', title: '操作', valueType: 'option',
key: 'option', key: 'option',
@ -31,14 +35,14 @@ const Roles = () => {
<a <a
key="editable" key="editable"
onClick={() => { onClick={() => {
action?.startEditable?.(record.id)
// action?.startEditable?.(record.id)
setRole(record)
setOpen(true)
form.setFieldsValue(record)
}} }}
> >
</a>, </a>,
<a href={record.url} target="_blank" rel="noopener noreferrer" key="view">
</a>,
<a href={record.url} target="_blank" rel="noopener noreferrer" key="del"> <a href={record.url} target="_blank" rel="noopener noreferrer" key="del">
</a>, </a>,
@ -99,6 +103,24 @@ const Roles = () => {
} }
}} }}
/> />
<BetaSchemaForm
width={600}
form={form}
layout={'horizontal'}
title={t('system.roles.edit.title', '角色编辑')}
colProps={{ span: 24 }}
labelCol={{ span: 6 }}
wrapperCol={{ span: 16 }}
layoutType={'ModalForm'}
open={open}
modalProps={{
maskClosable: false,
}}
onOpenChange={(open) => {
console.log('open', open)
setOpen(open)
}}
columns={columns} />
</PageContainer> </PageContainer>
) )
} }

4
src/service/base.ts

@ -15,10 +15,10 @@ export const createCURD = <TParams, TResult>(api: string, options?: AxiosRequest
return request.post(`${api}/edit`, data, options) return request.post(`${api}/edit`, data, options)
}, },
delete: (id: number) => { delete: (id: number) => {
return request.delete(`${api}/delete`, { ...options, params: { id } })
return request.post(`${api}/delete`, { id }, options)
}, },
batchDelete: (ids: number[]) => { batchDelete: (ids: number[]) => {
return request.delete(`${api}/deletes`, { ...options, params: { ids } })
return request.post(`${api}/deletes`, { ids }, options )
}, },
info: (id: number) => { info: (id: number) => {
return request.get<TResult>(`${api}/${id}`, options) return request.get<TResult>(`${api}/${id}`, options)

6
src/store/user.ts

@ -4,7 +4,7 @@ import { AxiosResponse } from 'axios'
import { atom } from 'jotai/index' import { atom } from 'jotai/index'
import { IApiResult, IAuth, MenuItem } from '@/types' import { IApiResult, IAuth, MenuItem } from '@/types'
import { LoginRequest } from '@/types/login' import { LoginRequest } from '@/types/login'
import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query'
import { atomWithMutation, atomWithQuery } from 'jotai-tanstack-query'
import systemServ from '@/service/system.ts' import systemServ from '@/service/system.ts'
import { formatMenuData, isDev } from '@/utils' import { formatMenuData, isDev } from '@/utils'
@ -54,9 +54,5 @@ export const userMenuDataAtom = atomWithQuery<any, MenuItem[]>((get) => ({
select: (data: AxiosResponse) => { select: (data: AxiosResponse) => {
return formatMenuData(data.data.rows as any ?? []) return formatMenuData(data.data.rows as any ?? [])
}, },
initialData: () => {
const queryClient = get(queryClientAtom)
return queryClient.getQueryData([ 'user_menus', get(appAtom).token ])
},
retry: false, retry: false,
})) }))
Loading…
Cancel
Save