李金
7 months ago
29 changed files with 1022 additions and 1033 deletions
-
1package.json
-
2src/_authenticatedRoute.tsx
-
38src/components/loading/index.tsx
-
4src/components/loading/style.ts
-
22src/components/user-picker/Item.tsx
-
61src/components/user-picker/UserPicker.tsx
-
1src/components/user-picker/index.ts
-
5src/components/user-picker/store.ts
-
25src/components/user-picker/style.ts
-
15src/pages/list/list.tsx
-
146src/pages/list/tree.tsx
-
11src/pages/system/departments/components/DepartmentTree.tsx
-
9src/pages/system/departments/components/TreeNodeRender.tsx
-
9src/pages/system/departments/index.tsx
-
84src/pages/system/departments/store.ts
-
7src/pages/system/menus/components/BatchButton.tsx
-
11src/pages/system/menus/components/MenuTree.tsx
-
9src/pages/system/menus/components/TreeNodeRender.tsx
-
9src/pages/system/menus/index.tsx
-
19src/pages/system/roles/index.tsx
-
23src/routes.tsx
-
106src/store/department.ts
-
36src/store/index.ts
-
7src/store/menu.ts
-
0src/store/role.ts
-
5vite.config.ts
@ -1,7 +1,7 @@ |
|||
import { isAuthenticated } from '@/utils/auth.ts' |
|||
import { createFileRoute, redirect } from '@tanstack/react-router' |
|||
|
|||
export const Route = createFileRoute('/_authenticated')({ |
|||
export const AuthenticatedRoute = createFileRoute('/_authenticated')({ |
|||
beforeLoad: async ({ location }) => { |
|||
if (!isAuthenticated()) { |
|||
throw redirect({ |
@ -0,0 +1,22 @@ |
|||
import { IUser } from '@/types/user' |
|||
import { useAtom } from 'jotai' |
|||
import { useStyle } from './style.ts' |
|||
import { Checkbox } from 'antd' |
|||
|
|||
export const Item = ( props: { |
|||
value: IUser, |
|||
onChange:( value: IUser)=>void |
|||
} )=>{ |
|||
|
|||
const { styles } = useStyle() |
|||
|
|||
|
|||
return ( |
|||
<div className={styles.listItem}> |
|||
<Checkbox value={props.value} onChange={e=>}> |
|||
<span>{props.value.name}</span> |
|||
</Checkbox> |
|||
</div> |
|||
) |
|||
|
|||
} |
@ -0,0 +1,61 @@ |
|||
import { Modal, ModalProps, Select, SelectProps } from 'antd' |
|||
import { memo } from 'react' |
|||
import { Flexbox } from 'react-layout-kit' |
|||
import { useStyle } from './style.ts' |
|||
|
|||
export interface UserSelectProps extends SelectProps { |
|||
|
|||
} |
|||
|
|||
export interface UserModelProps extends ModalProps { |
|||
|
|||
} |
|||
|
|||
export type UserPickerProps = |
|||
| { |
|||
type?: 'modal'; |
|||
/** Props for the modal component */ |
|||
} & UserModelProps |
|||
| { |
|||
type: 'select'; |
|||
/** Props for the select component */ |
|||
} & UserSelectProps |
|||
|
|||
const UserSelect = memo((props: UserSelectProps) => { |
|||
console.log(props) |
|||
return ( |
|||
<Select {...props}> |
|||
|
|||
</Select> |
|||
) |
|||
}) |
|||
|
|||
const UserModel = memo(({ open, ...props }: UserModelProps) => { |
|||
const { styles } = useStyle() |
|||
|
|||
|
|||
return ( |
|||
<Modal |
|||
open={open} |
|||
{...props} |
|||
> |
|||
<Flexbox horizontal={true} className={styles.container}> |
|||
<Flexbox> |
|||
|
|||
</Flexbox> |
|||
<Flexbox> |
|||
|
|||
</Flexbox> |
|||
</Flexbox> |
|||
|
|||
</Modal> |
|||
) |
|||
}) |
|||
|
|||
|
|||
const UserPicker = memo(({ type, ...props }: UserPickerProps) => { |
|||
|
|||
return type === 'modal' ? <UserModel {...props} /> : <UserSelect {...props as UserSelectProps} /> |
|||
}) |
|||
|
|||
export default UserPicker |
@ -0,0 +1 @@ |
|||
export * from './UserPicker.tsx' |
@ -0,0 +1,5 @@ |
|||
import { IUser } from '@/types/user' |
|||
import { atom } from 'jotai' |
|||
|
|||
export const userSelectedAtom = atom<IUser[]>([]) |
|||
|
@ -0,0 +1,25 @@ |
|||
import { createStyles } from '@/theme' |
|||
|
|||
export const useStyle = createStyles(({ token, css, cx, prefixCls }) => { |
|||
const prefix = `${prefixCls}-${token?.proPrefix}-user-picker` |
|||
|
|||
const list = css`` |
|||
|
|||
const listItem = css`
|
|||
|
|||
padding: 5px; |
|||
height: 40px; |
|||
background-color: ${token.colorBgContainer}; |
|||
|
|||
:hover { |
|||
background-color: ${token.controlItemBgActiveHover}; |
|||
} |
|||
|
|||
`
|
|||
|
|||
return { |
|||
container: cx(prefix), |
|||
list, |
|||
listItem, |
|||
} |
|||
}) |
@ -1,15 +0,0 @@ |
|||
import { createLazyRoute } from '@tanstack/react-router' |
|||
import { ProCard } from '@ant-design/pro-components' |
|||
const List = () => { |
|||
return ( |
|||
<ProCard> |
|||
列表页面 |
|||
</ProCard> |
|||
) |
|||
} |
|||
export const Route = createLazyRoute('/list/index')({ |
|||
component: List, |
|||
}) |
|||
|
|||
|
|||
export default List |
@ -1,146 +0,0 @@ |
|||
import { LightFilter, PageContainer, ProCard, ProColumns, ProTable } from '@ant-design/pro-components' |
|||
import { Tree, Input, Space, Button } from 'antd' |
|||
import { createLazyRoute } from '@tanstack/react-router' |
|||
import { departmentAtom } from '../../store/department.ts' |
|||
import { useAtomValue } from 'jotai' |
|||
import { getIcon } from '../../components/icon' |
|||
import dayjs from 'dayjs' |
|||
|
|||
//递归渲染树形结构,将name->title, id->key
|
|||
const renderTree = (data: any[]) => { |
|||
return data?.map((item) => { |
|||
if (item.children) { |
|||
return { |
|||
title: item.name, |
|||
key: item.id, |
|||
children: renderTree(item.children), |
|||
} |
|||
} |
|||
return { |
|||
title: item.name, |
|||
key: item.id, |
|||
} |
|||
}) |
|||
} |
|||
|
|||
|
|||
const columns: ProColumns[] = [ |
|||
{ |
|||
title: '姓名', |
|||
dataIndex: 'name', |
|||
render: (_) => <a>{_}</a>, |
|||
formItemProps: { |
|||
lightProps: { |
|||
labelFormatter: (value) => `app-${value}`, |
|||
}, |
|||
}, |
|||
}, |
|||
{ |
|||
title: '帐号', |
|||
dataIndex: 'account', |
|||
}, |
|||
{ |
|||
title: '创建者', |
|||
dataIndex: 'creator', |
|||
valueType: 'select', |
|||
search: false, |
|||
valueEnum: { |
|||
all: { text: '全部' }, |
|||
付小小: { text: '付小小' }, |
|||
曲丽丽: { text: '曲丽丽' }, |
|||
林东东: { text: '林东东' }, |
|||
陈帅帅: { text: '陈帅帅' }, |
|||
兼某某: { text: '兼某某' }, |
|||
}, |
|||
}, |
|||
//操作
|
|||
{ |
|||
title: '操作', |
|||
valueType: 'option', |
|||
render: (_, record) => { |
|||
return [ |
|||
<a key="editable" onClick={() => { |
|||
alert('edit') |
|||
}}>编辑</a>, |
|||
<a key="delete" onClick={() => { |
|||
alert('delete') |
|||
}}>删除</a>, |
|||
] |
|||
} |
|||
}, |
|||
] |
|||
|
|||
const TreePage = () => { |
|||
|
|||
const { data, isError, isPending } = useAtomValue(departmentAtom) |
|||
|
|||
if (isError) { |
|||
return <div>Error</div> |
|||
} |
|||
// if (isPending){
|
|||
// return <div>Loading</div>
|
|||
// }
|
|||
|
|||
return ( |
|||
<PageContainer breadcrumbRender={false}> |
|||
<ProCard split="vertical"> |
|||
<ProCard title="部门" |
|||
colSpan="25%" |
|||
loading={isPending} |
|||
extra={<> |
|||
<Button size={'small'} icon={getIcon('Plus')} shape={'circle'}/> |
|||
</>} |
|||
> |
|||
<Tree showLine={true} treeData={renderTree(data)}/> |
|||
</ProCard> |
|||
<ProCard headerBordered> |
|||
<div style={{ height: 360 }}> |
|||
<ProTable |
|||
rowKey="account" |
|||
headerTitle={'帐号列表'} |
|||
columns={columns} |
|||
dataSource={[ |
|||
{ |
|||
name: '张三', |
|||
account: 'zhangsan', |
|||
}, |
|||
{ |
|||
name: '李四', |
|||
account: 'lisi', |
|||
}, |
|||
]} |
|||
// pagination={false}
|
|||
options={{ |
|||
search: true, |
|||
}} |
|||
search={false} |
|||
toolbar={{ |
|||
search: { |
|||
onSearch: (value: string) => { |
|||
alert(value) |
|||
}, |
|||
}, |
|||
actions: [ |
|||
<Button |
|||
key="primary" |
|||
type="primary" |
|||
onClick={() => { |
|||
alert('add') |
|||
}} |
|||
> |
|||
添加 |
|||
</Button>, |
|||
], |
|||
}} |
|||
|
|||
/> |
|||
|
|||
</div> |
|||
</ProCard> |
|||
</ProCard> |
|||
</PageContainer> |
|||
) |
|||
} |
|||
|
|||
|
|||
export default TreePage |
@ -1,84 +0,0 @@ |
|||
import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query' |
|||
import systemServ from '@/service/system.ts' |
|||
import { IApiResult, IPage } from '@/types' |
|||
import { IDepartment } from '@/types/department' |
|||
import { atom, createStore } from 'jotai' |
|||
import { t } from 'i18next' |
|||
import { message } from 'antd' |
|||
|
|||
|
|||
const store = createStore() |
|||
|
|||
export const departPageAtom = atom<IPage>({}) |
|||
|
|||
export const defaultDepart = { |
|||
id: 0, |
|||
parent_id: 0, |
|||
name: '', |
|||
manager_user_id: 0, |
|||
phone: '', |
|||
sort: 0, |
|||
} as IDepartment |
|||
|
|||
export const batchIdsAtom = atom<number[]>([]) |
|||
|
|||
export const selectedDepartAtom = atom<IDepartment>({} as IDepartment) |
|||
|
|||
export const departTreeAtom = atomWithQuery(() => { |
|||
|
|||
return { |
|||
queryKey: [ 'departTree' ], |
|||
queryFn: async () => { |
|||
return await systemServ.dept.tree() |
|||
}, |
|||
select: (res) => { |
|||
return res.data.tree ?? [] |
|||
} |
|||
} |
|||
}) |
|||
|
|||
export const saveOrUpdateDepartAtom = atomWithMutation<IApiResult, IDepartment>((get) => { |
|||
|
|||
return { |
|||
mutationKey: [ 'saveOrUpdateDepart' ], |
|||
mutationFn: async (data: IDepartment) => { |
|||
if (data.id) { |
|||
return await systemServ.dept.update(data) |
|||
} |
|||
return await systemServ.dept.add(data) |
|||
}, |
|||
onSuccess: (res) => { |
|||
const isAdd = !!res.data?.id |
|||
message.success(t(isAdd ? 'message.saveSuccess' : 'message.editSuccess', '保存成功')) |
|||
|
|||
if (isAdd) { |
|||
store.set(selectedDepartAtom, prev => ({ |
|||
...prev, |
|||
id: res.data.id |
|||
})) |
|||
} |
|||
|
|||
get(queryClientAtom).invalidateQueries({ queryKey: [ 'departTree' ] }).then() |
|||
} |
|||
} |
|||
|
|||
}) |
|||
|
|||
|
|||
export const deleteDepartAtom = atomWithMutation<IApiResult, number[]>((get) => { |
|||
|
|||
return { |
|||
mutationKey: [ 'deleteDepart', get(batchIdsAtom) ], |
|||
mutationFn: async (ids: number[]) => { |
|||
return await systemServ.dept.batchDelete(ids) |
|||
}, |
|||
onSuccess: () => { |
|||
message.success(t('message.deleteSuccess', '删除成功')) |
|||
store.set(batchIdsAtom, []) |
|||
get(queryClientAtom).invalidateQueries({ queryKey: [ 'departTree' ] }).then() |
|||
|
|||
} |
|||
} |
|||
|
|||
}) |
|||
|
@ -1,44 +1,84 @@ |
|||
import { atom } from 'jotai' |
|||
import { IDepartment } from '../types/department' |
|||
import { QueryClient } from '@tanstack/query-core' |
|||
import { atomWithMutation, atomWithQuery } from 'jotai-tanstack-query' |
|||
import { IApiResult } from '../types' |
|||
import request from '../request.ts' |
|||
import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query' |
|||
import systemServ from '@/service/system.ts' |
|||
import { IApiResult, IPage } from '@/types' |
|||
import { IDepartment } from '@/types/department' |
|||
import { atom, createStore } from 'jotai' |
|||
import { t } from 'i18next' |
|||
import { message } from 'antd' |
|||
|
|||
|
|||
export const departmentSearchAtom = atom<Partial<IDepartment>>({}) |
|||
const store = createStore() |
|||
|
|||
export const departmentAtom = atomWithQuery<IApiResult<IDepartment[]>, any, IDepartment[]>((get) => ({ |
|||
queryKey: [ 'departments', get(departmentSearchAtom) ], |
|||
queryFn: async ({ queryKey: [ , departSearch ] }) => { |
|||
await new Promise(resolve => setTimeout(resolve, 5000)) |
|||
export const departPageAtom = atom<IPage>({}) |
|||
|
|||
return await request('/departments', { |
|||
data: departSearch, |
|||
}) |
|||
export const defaultDepart = { |
|||
id: 0, |
|||
parent_id: 0, |
|||
name: '', |
|||
manager_user_id: 0, |
|||
phone: '', |
|||
sort: 0, |
|||
} as IDepartment |
|||
|
|||
export const batchIdsAtom = atom<number[]>([]) |
|||
|
|||
export const selectedDepartAtom = atom<IDepartment>({} as IDepartment) |
|||
|
|||
export const departTreeAtom = atomWithQuery(() => { |
|||
|
|||
return { |
|||
queryKey: [ 'departTree' ], |
|||
queryFn: async () => { |
|||
return await systemServ.dept.tree() |
|||
}, |
|||
select: data => data.data, |
|||
})) |
|||
select: (res) => { |
|||
return res.data.tree ?? [] |
|||
} |
|||
} |
|||
}) |
|||
|
|||
export const departmentDetailAtom = atomWithQuery<IApiResult<IDepartment>>((get) => ({ |
|||
queryKey: [ 'department', get(departmentSearchAtom) ], |
|||
queryFn: async ({ queryKey: [ , departSearch ] }) => { |
|||
return await request(`/departments/${(departSearch as IDepartment).id}`) |
|||
export const saveOrUpdateDepartAtom = atomWithMutation<IApiResult, IDepartment>((get) => { |
|||
|
|||
return { |
|||
mutationKey: [ 'saveOrUpdateDepart' ], |
|||
mutationFn: async (data: IDepartment) => { |
|||
if (data.id) { |
|||
return await systemServ.dept.update(data) |
|||
} |
|||
return await systemServ.dept.add(data) |
|||
}, |
|||
select: data => data, |
|||
onSuccess: (res) => { |
|||
const isAdd = !!res.data?.id |
|||
message.success(t(isAdd ? 'message.saveSuccess' : 'message.editSuccess', '保存成功')) |
|||
|
|||
if (isAdd) { |
|||
store.set(selectedDepartAtom, prev => ({ |
|||
...prev, |
|||
id: res.data.id |
|||
})) |
|||
} |
|||
|
|||
//add use atomWithMutation
|
|||
export const departmentAddAtom = (client: QueryClient) => atomWithMutation<IApiResult<IDepartment>, any, IDepartment>(() => ({ |
|||
mutationKey: [ 'addDepartment', ], |
|||
mutationFn: async (depart: IDepartment) => { |
|||
return await request.post('/departments', depart) |
|||
}, |
|||
onSuccess: (result) => { |
|||
console.log(result) |
|||
get(queryClientAtom).invalidateQueries({ queryKey: [ 'departTree' ] }).then() |
|||
} |
|||
} |
|||
|
|||
}) |
|||
|
|||
|
|||
export const deleteDepartAtom = atomWithMutation<IApiResult, number[]>((get) => { |
|||
|
|||
return { |
|||
mutationKey: [ 'deleteDepart', get(batchIdsAtom) ], |
|||
mutationFn: async (ids: number[]) => { |
|||
return await systemServ.dept.batchDelete(ids) |
|||
}, |
|||
onSettled: () => { |
|||
//清空列表的缓存
|
|||
void client.invalidateQueries({ queryKey: [ 'departments' ] }) |
|||
onSuccess: () => { |
|||
message.success(t('message.deleteSuccess', '删除成功')) |
|||
store.set(batchIdsAtom, []) |
|||
get(queryClientAtom).invalidateQueries({ queryKey: [ 'departTree' ] }).then() |
|||
|
|||
} |
|||
})) |
|||
} |
|||
|
|||
}) |
|||
|
@ -0,0 +1,36 @@ |
|||
import { createStore } from 'jotai' |
|||
import React, { useContext, createContext } from 'react' |
|||
|
|||
export type Store = ReturnType<typeof createStore> |
|||
|
|||
export const InternalPageContext = createContext<Store | undefined>( |
|||
undefined, |
|||
) |
|||
|
|||
export const useInternalStore = (): Store => { |
|||
const store = useContext(InternalPageContext) |
|||
if (!store) { |
|||
throw new Error( |
|||
`Unable to find internal Page store, Did you wrap the component within PageStoreProvider?`, |
|||
) |
|||
} |
|||
return store |
|||
} |
|||
|
|||
export const usePageStoreOptions = () => ({ |
|||
store: useInternalStore(), |
|||
}) |
|||
|
|||
export const pageStore = createStore() |
|||
|
|||
export const PageStoreProvider = ({ children }: React.PropsWithChildren) => { |
|||
const internalStoreRef = React.useRef<Store>() |
|||
|
|||
if (!internalStoreRef.current) { |
|||
internalStoreRef.current = pageStore |
|||
} |
|||
|
|||
return React.createElement(InternalPageContext.Provider, { |
|||
value: internalStoreRef.current |
|||
}, children) |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue