Browse Source

完善菜单页面,增加多语言

添加角色页面
main
dark 5 months ago
parent
commit
2d75091535
  1. 15
      src/locales/lang/en-US.ts
  2. 3
      src/locales/lang/pages/system/menus/en-US.ts
  3. 24
      src/locales/lang/pages/system/menus/zh-CN.ts
  4. 14
      src/locales/lang/zh-CN.ts
  5. 45
      src/pages/system/menus/index.tsx
  6. 13
      src/pages/system/menus/store.ts
  7. 100
      src/pages/system/roles/index.tsx
  8. 25
      src/pages/system/roles/store.ts
  9. 22
      src/pages/system/roles/style.ts
  10. 4
      src/service/base.ts
  11. 4
      src/service/system.ts
  12. 2
      vite.config.ts

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

@ -1,4 +1,6 @@
import antdEN from 'antd/locale/en_US' import antdEN from 'antd/locale/en_US'
import menus from './pages/system/menus/en-US'
export default { export default {
...antdEN, ...antdEN,
@ -24,7 +26,7 @@ export default {
logout: 'logout', logout: 'logout',
} }
}, },
login:{
login: {
title: 'Account Password Login', title: 'Account Password Login',
username: 'Username', username: 'Username',
usernameMsg: 'Please enter your username', usernameMsg: 'Please enter your username',
@ -38,6 +40,17 @@ export default {
home: { home: {
welcome: 'Welcome to' welcome: 'Welcome to'
}, },
system: {
menus,
},
errorTitle: 'Error',
successTitle: 'Success',
success: 'Submit Success',
fail: 'Submit Fail',
saveSuccess: 'Save Success',
saveFail: 'Save Fail',
tabs: { tabs: {
refresh: 'Refresh', refresh: 'Refresh',
maximize: 'Maximize', maximize: 'Maximize',

3
src/locales/lang/pages/system/menus/en-US.ts

@ -0,0 +1,3 @@
export default {
}

24
src/locales/lang/pages/system/menus/zh-CN.ts

@ -0,0 +1,24 @@
export default {
title: '菜单',
setting: '配置',
saveSuccess: '保存成功',
form:{
title: '菜单名称',
parent: '上级菜单',
type:'类型',
typeOptions: {
menu: '菜单',
iframe: 'iframe',
link: '外链',
button: '按钮',
},
name: '别名',
icon: '图标',
sort: '排序',
path: '路由',
component: '视图',
componentHelp: '视图路径,相对于src/pages,菜单组可以不填',
save: '保存',
},
button: '按钮'
}

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

@ -1,4 +1,6 @@
import antdZh from 'antd/locale/zh_CN' import antdZh from 'antd/locale/zh_CN'
import menus from './pages/system/menus/zh-CN.ts'
export default { export default {
...antdZh, ...antdZh,
@ -23,7 +25,7 @@ export default {
logout: '退出登录', logout: '退出登录',
} }
}, },
login:{
login: {
title: '账户密码登录', title: '账户密码登录',
username: '用户名', username: '用户名',
usernameMsg: '请输入用户名', usernameMsg: '请输入用户名',
@ -37,6 +39,16 @@ export default {
home: { home: {
welcome: '欢迎使用' welcome: '欢迎使用'
}, },
system: {
menus,
},
errorTitle: '错误',
successTitle: '成功',
success: '提交成功',
fail: '提交失败',
saveSuccess: '保存成功',
saveFail: '保存失败',
tabs: { tabs: {
refresh: '刷新', refresh: '刷新',
maximize: '最大化', maximize: '最大化',

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

@ -1,10 +1,8 @@
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 } from 'antd'
import { Button, Form, Input, message, Radio, TreeSelect, InputNumber, notification } 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 { createLazyFileRoute } from '@tanstack/react-router'
import { NumberPicker } from '@formily/antd-v5'
import IconPicker from '@/components/icon/picker' import IconPicker from '@/components/icon/picker'
import ButtonTable from './components/ButtonTable.tsx' import ButtonTable from './components/ButtonTable.tsx'
import { Flexbox } from 'react-layout-kit' import { Flexbox } from 'react-layout-kit'
@ -14,6 +12,7 @@ import { MenuItem } from '@/types'
import MenuTree from './components/MenuTree.tsx' import MenuTree from './components/MenuTree.tsx'
import BatchButton from '@/pages/system/menus/components/BatchButton.tsx' import BatchButton from '@/pages/system/menus/components/BatchButton.tsx'
import { useEffect } from 'react' import { useEffect } from 'react'
import { createLazyFileRoute } from '@tanstack/react-router'
const Menus = () => { const Menus = () => {
@ -21,19 +20,26 @@ const Menus = () => {
const { styles } = useStyle() const { styles } = useStyle()
const { t } = useTranslation() const { t } = useTranslation()
const [ form ] = Form.useForm() const [ form ] = Form.useForm()
const { mutate, isPending, isSuccess, isError } = useAtomValue(saveOrUpdateMenuAtom)
const { mutate, isPending, isSuccess, error, isError } = useAtomValue(saveOrUpdateMenuAtom)
const { data = [] } = useAtomValue(menuDataAtom) const { data = [] } = useAtomValue(menuDataAtom)
const currentMenu = useAtomValue<MenuItem>(selectedMenuAtom) ?? {} const currentMenu = useAtomValue<MenuItem>(selectedMenuAtom) ?? {}
useEffect(() => { useEffect(() => {
if (isSuccess) { if (isSuccess) {
message.success(t('system.menus.saveSuccess', '保存成功'))
message.success(t('saveSuccess', '保存成功'))
}
if (isError) {
notification.error({
message: t('errorTitle', '错误'),
description: (error as any).message ?? t('saveFail', '保存失败'),
})
} }
}, [ isError, isSuccess ]) }, [ isError, isSuccess ])
return ( return (
<PageContainer
breadcrumbRender={false} title={false} className={styles.container}>
<PageContainer breadcrumbRender={false} title={false} className={styles.container}>
<Flexbox horizontal> <Flexbox horizontal>
<DraggablePanel expandable={false} placement="left" maxWidth={800} style={{ width: '100%', }}> <DraggablePanel expandable={false} placement="left" maxWidth={800} style={{ width: '100%', }}>
@ -62,7 +68,7 @@ const Menus = () => {
className={styles.formSetting} className={styles.formSetting}
> >
<Form.Item hidden={true} label={t('system.menus.form.id', 'ID')} name={'id'}>
<Form.Item hidden={true} label={'id'} name={'id'}>
<Input disabled={true}/> <Input disabled={true}/>
</Form.Item> </Form.Item>
<Form.Item label={t('system.menus.form.title', '菜单名称')} name={'title'}> <Form.Item label={t('system.menus.form.title', '菜单名称')} name={'title'}>
@ -79,16 +85,25 @@ const Menus = () => {
value: 'id' value: 'id'
}}/> }}/>
</Form.Item> </Form.Item>
<Form.Item label={t('system.menus.form.parent', '类型')} name={'type'}>
<Form.Item label={t('system.menus.form.type', '类型')} name={'type'}>
<Radio.Group <Radio.Group
options={[ options={[
{ label: t('system.menus.form.type.menu', '菜单'), value: 'menu' },
{ {
label: t('system.menus.form.type.iframe', 'iframe'),
label: t('system.menus.form.typeOptions.menu', '菜单'),
value: 'menu'
},
{
label: t('system.menus.form.typeOptions.iframe', 'iframe'),
value: 'iframe' value: 'iframe'
}, },
{ label: t('system.menus.form.type.link', '外链'), value: 'link' },
{ label: t('system.menus.form.type.button', '按钮'), value: 'button' },
{
label: t('system.menus.form.typeOptions.link', '外链'),
value: 'link'
},
{
label: t('system.menus.form.typeOptions.button', '按钮'),
value: 'button'
},
]} ]}
optionType="button" optionType="button"
buttonStyle="solid" buttonStyle="solid"
@ -101,7 +116,7 @@ const Menus = () => {
<IconPicker placement={'left'}/> <IconPicker placement={'left'}/>
</Form.Item> </Form.Item>
<Form.Item label={t('system.menus.form.sort', '排序')} name={'sort'}> <Form.Item label={t('system.menus.form.sort', '排序')} name={'sort'}>
<NumberPicker/>
<InputNumber/>
</Form.Item> </Form.Item>
<Form.Item label={t('system.menus.form.path', '路由')} name={'path'}> <Form.Item label={t('system.menus.form.path', '路由')} name={'path'}>
<Input/> <Input/>
@ -109,7 +124,7 @@ const Menus = () => {
<Form.Item label={t('system.menus.form.component', '视图')} <Form.Item label={t('system.menus.form.component', '视图')}
name={'component'} name={'component'}
help={t('system.menus.form.component.help', '视图路径,相对于src/pages')}
help={t('system.menus.form.component.componentHelp', '视图路径,相对于src/pages')}
> >
<Input addonBefore={'pages/'}/> <Input addonBefore={'pages/'}/>
</Form.Item> </Form.Item>

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

@ -6,9 +6,8 @@ import { atom } from 'jotai/index'
export const menuPageAtom = atom<IPage>({}) export const menuPageAtom = atom<IPage>({})
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore fix get
export const menuDataAtom = atomWithQuery<IPageResult<IMenu>>((get) => {
export const menuDataAtom = atomWithQuery<any, IPageResult<IMenu>>((get) => {
return { return {
queryKey: [ 'menus', get(menuPageAtom) ], queryKey: [ 'menus', get(menuPageAtom) ],
@ -43,10 +42,12 @@ export const saveOrUpdateMenuAtom = atomWithMutation((get) => {
} }
return await systemServ.menus.update(data) return await systemServ.menus.update(data)
}, },
onSuccess: (data) => {
console.log(data)
onSuccess: () => {
// console.log(data)
//更新列表 //更新列表
get(queryClientAtom).refetchQueries([ 'menus', get(menuPageAtom) ])
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore fix
get(queryClientAtom).refetchQueries([ 'menus', get(menuPageAtom) ]).then()
} }
} }
}) })

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

@ -1,15 +1,109 @@
import { PageContainer } from '@ant-design/pro-components'
import { ActionType, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components'
import { createLazyFileRoute } from '@tanstack/react-router' import { createLazyFileRoute } from '@tanstack/react-router'
import { useStyle } from './style.ts'
import { useMemo, useRef } from 'react'
import { useAtomValue } from 'jotai'
import { rolesAtom } from './store.ts'
import { useTranslation } from '@/i18n.ts'
import { Button, Space, Table } from 'antd'
import { PlusOutlined } from '@ant-design/icons'
const Roles = () => { const Roles = () => {
const { t } = useTranslation()
const { styles } = useStyle()
const actionRef = useRef<ActionType>()
const { data, isLoading } = useAtomValue(rolesAtom)
const columns = useMemo(() => {
return [
{ title: 'id', dataIndex: 'id', hideInTable: true, hideInSearch: true, },
{ title: '名称', dataIndex: 'name' },
{ title: '别名', dataIndex: 'code' },
{ title: '状态', dataIndex: 'status' },
{ title: '排序', dataIndex: 'sort', valueType: 'indexBorder', },
{ title: '备注', dataIndex: 'remark' },
{
title: '操作', valueType: 'option',
key: 'option',
render: (text, record, _, action) => [
<a
key="editable"
onClick={() => {
action?.startEditable?.(record.id)
}}
>
</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>,
],
},
] as ProColumns[]
}, [])
return (
<PageContainer breadcrumbRender={false} title={false} className={styles.container}>
<ProTable
rowKey={'id'}
actionRef={actionRef}
headerTitle={t('system.roles.title', '角色管理')}
columns={columns}
loading={isLoading}
dataSource={data?.rows}
search={false}
rowSelection={{
selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
}}
tableAlertOptionRender={() => {
return ( return (
<PageContainer breadcrumbRender={false}>
<Space size={16}>
<a></a>
</Space>
);
}}
toolbar={{
search: {
onSearch: (value: string) => {
alert(value)
},
},
onSearch: (value) => {
console.log('value', value)
},
actions: [
<Button
key="button"
icon={<PlusOutlined/>}
onClick={() => {
actionRef.current?.reload()
}}
type="primary"
>
</Button>,
]
}}
pagination={{
total: data?.total,
current: data?.page,
pageSize: data?.pageSize,
onChange: (page) => {
console.log('page', page)
}
}}
/>
</PageContainer> </PageContainer>
) )
} }
export const Route = createLazyFileRoute("/system/roles")({
export const Route = createLazyFileRoute('/system/roles')({
component: Roles component: Roles
}) })

25
src/pages/system/roles/store.ts

@ -0,0 +1,25 @@
import { atom } from 'jotai/index'
import { IRole } from '@/types/roles'
import { atomWithQuery } from 'jotai-tanstack-query'
import { IPage, IPageResult } from '@/types'
import systemServ from '@/service/system.ts'
type SearchParams = IPage & IRole
export const idAtom = atom(0)
export const IdsAtom = atom([])
export const roleAtom = atom<IRole>(undefined as unknown as IRole)
export const searchAtom = atom<SearchParams>({} as SearchParams)
export const rolesAtom = atomWithQuery<any, IPageResult<IRole>>((get) => {
return {
queryKey: [ 'roles', get(searchAtom) ],
queryFn: async ({ queryKey: [ , params ] }) => {
return await systemServ.role.list(params as SearchParams)
},
}
})

22
src/pages/system/roles/style.ts

@ -0,0 +1,22 @@
import { createStyles } from '@/theme'
export const useStyle = createStyles(({ token, css, cx, prefixCls }) => {
const prefix = `${prefixCls}-${token?.proPrefix}-role-page`;
const box = css`
flex: 1;
background: ${token.colorBgContainer};
`
const form = css`
display: flex;
flex-wrap: wrap;
min-width: 500px;
`
return {
container: cx(prefix),
box,
form,
}
})

4
src/service/base.ts

@ -1,12 +1,12 @@
import { request, AxiosRequestConfig } from '@/request.ts' import { request, AxiosRequestConfig } from '@/request.ts'
import { IPage } from '@/types'
import { IPage, IPageResult } from '@/types'
export const createCURD = <TParams, TResult>(api: string, options?: AxiosRequestConfig) => { export const createCURD = <TParams, TResult>(api: string, options?: AxiosRequestConfig) => {
return { return {
list: (params?: TParams & IPage) => { list: (params?: TParams & IPage) => {
return request.post<TResult[]>(`${api}/list`, { ...options, ...params }).then(data => data.data)
return request.post<IPageResult<TResult>>(`${api}/list`, { ...options, ...params }).then(data => data.data)
}, },
add: (data: TParams) => { add: (data: TParams) => {
return request.post<TResult>(`${api}/add`, data, options) return request.post<TResult>(`${api}/add`, data, options)

4
src/service/system.ts

@ -2,6 +2,7 @@ import request from '../request.ts'
import { LoginRequest, LoginResponse } from '@/types/login' import { LoginRequest, LoginResponse } from '@/types/login'
import { createCURD } from '@/service/base.ts' import { createCURD } from '@/service/base.ts'
import { IMenu } from '@/types/menus' import { IMenu } from '@/types/menus'
import { IRole } from '@/types/roles'
const systemServ = { const systemServ = {
dept: { dept: {
@ -18,6 +19,9 @@ const systemServ = {
return request.get<IMenu[]>('/sys/user/menus') return request.get<IMenu[]>('/sys/user/menus')
} }
},
role: {
...createCURD<any, IRole>('/sys/role')
} }

2
vite.config.ts

@ -15,7 +15,7 @@ export default defineConfig({
server: { server: {
proxy: { proxy: {
'/api': { '/api': {
target: 'https://proapi.devwork.top',
target: 'http://47.113.117.106:8000',
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path rewrite: (path) => path
} }

Loading…
Cancel
Save