dark
3 months ago
10 changed files with 828 additions and 84 deletions
-
322src/components/r-form/index.tsx
-
13src/components/r-form/style.ts
-
90src/components/r-form/utils/index.tsx
-
15src/components/x-form/utils/index.tsx
-
26src/pages/r-form/index.tsx
-
29src/pages/r-form/style.ts
-
22src/service/r-form/model.ts
-
189src/store/r-form/model.ts
-
25src/types/r-form/model.d.ts
-
25src/utils/tree.ts
@ -0,0 +1,322 @@ |
|||||
|
import { useStyle } from './style.ts' |
||||
|
import { Badge, Button, Divider, Form, Popconfirm, Space, Tooltip } from 'antd' |
||||
|
import { useAtom, useAtomValue, useSetAtom } from 'jotai' |
||||
|
import { ModelContext, useSpanModel } from '@/store/r-form/model.ts' |
||||
|
import { ReactNode, useEffect, useState } from 'react' |
||||
|
import { transformAntdTableProColumns } from './utils' |
||||
|
import Action from '@/components/action/Action.tsx' |
||||
|
import { FilterOutlined } from '@ant-design/icons' |
||||
|
import ListPageLayout from '@/layout/ListPageLayout.tsx' |
||||
|
import { Table as ProTable } from '@/components/table' |
||||
|
import { getValueCount, unSetColumnRules } from '@/utils' |
||||
|
import { BetaSchemaForm, ProColumns, ProFormColumnsType } from '@ant-design/pro-components' |
||||
|
import { useApiContext } from '@/context.ts' |
||||
|
import { useDeepCompareEffect } from 'react-use' |
||||
|
import { RFormTypes } from '@/types/r-form/model' |
||||
|
|
||||
|
|
||||
|
export interface RFormProps { |
||||
|
title?: ReactNode |
||||
|
namespace?: string |
||||
|
columns?: ProColumns[] //重写columns
|
||||
|
} |
||||
|
|
||||
|
const RForm = ({ namespace, columns: propColumns = [], title }: RFormProps) => { |
||||
|
|
||||
|
const { styles, cx } = useStyle() |
||||
|
const apiCtx = useApiContext() |
||||
|
const { |
||||
|
apiAtom, |
||||
|
deleteModelAtom, |
||||
|
modelAtom, |
||||
|
modelCURDAtom, |
||||
|
modelsAtom, |
||||
|
modelSearchAtom, |
||||
|
saveOrUpdateModelAtom |
||||
|
} = useSpanModel(namespace || apiCtx?.menu?.meta?.name || 'default') as ModelContext |
||||
|
const [ form ] = Form.useForm() |
||||
|
const [ filterForm ] = Form.useForm() |
||||
|
const setApi = useSetAtom(apiAtom) |
||||
|
const [ model, setModel ] = useAtom<RFormTypes.IModel>(modelAtom) |
||||
|
const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateModelAtom) |
||||
|
const [ search, setSearch ] = useAtom(modelSearchAtom) |
||||
|
const { data, isFetching, isLoading, refetch } = useAtomValue(modelsAtom) |
||||
|
const { mutate: deleteModel, isPending: isDeleting } = useAtomValue(deleteModelAtom) |
||||
|
const { data: curdModal, isLoading: curdLoading, refetch: reloadCURDModal } = useAtomValue(modelCURDAtom) |
||||
|
const [ open, setOpen ] = useState(false) |
||||
|
const [ openFilter, setFilterOpen ] = useState(false) |
||||
|
const [ searchKey, setSearchKey ] = useState(search?.key) |
||||
|
const [ columns, setColumns ] = useState<ProColumns[]>([]) |
||||
|
|
||||
|
useDeepCompareEffect(() => { |
||||
|
|
||||
|
const res = transformAntdTableProColumns(curdModal?.columns || [], propColumns) |
||||
|
const _columns = [ { |
||||
|
title: 'ID', |
||||
|
dataIndex: 'id', |
||||
|
hideInTable: true, |
||||
|
hideInSearch: true, |
||||
|
formItemProps: { hidden: true } |
||||
|
} ].concat(res as any).concat([ |
||||
|
{ |
||||
|
title: '操作', |
||||
|
dataIndex: 'option', |
||||
|
valueType: 'option', |
||||
|
fixed: 'right', |
||||
|
render: (_, record) => [ |
||||
|
<Action key="edit" |
||||
|
as={'a'} |
||||
|
onClick={() => { |
||||
|
form.setFieldsValue(record) |
||||
|
setOpen(true) |
||||
|
}}>{'编辑'}</Action>, |
||||
|
<Popconfirm |
||||
|
key={'del_confirm'} |
||||
|
disabled={isDeleting} |
||||
|
onConfirm={() => { |
||||
|
deleteModel([ record.id ]) |
||||
|
}} |
||||
|
title={'确定要删除吗?'}> |
||||
|
<a key="del"> |
||||
|
删除 |
||||
|
</a> |
||||
|
</Popconfirm> |
||||
|
] |
||||
|
} as any |
||||
|
]) |
||||
|
setColumns(_columns) |
||||
|
}, [ curdModal?.columns, propColumns, deleteModel, form, isDeleting, setOpen, ]) |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (apiCtx.isApi && apiCtx.api) { |
||||
|
setApi(apiCtx.api) |
||||
|
reloadCURDModal() |
||||
|
} |
||||
|
}, [ apiCtx.isApi, apiCtx.api ]) |
||||
|
|
||||
|
useDeepCompareEffect(() => { |
||||
|
|
||||
|
setSearchKey(search?.key) |
||||
|
|
||||
|
filterForm.setFieldsValue(search) |
||||
|
|
||||
|
}, [ search ]) |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (isSuccess) { |
||||
|
setOpen(false) |
||||
|
} |
||||
|
}, [ isSuccess ]) |
||||
|
|
||||
|
const formProps = curdModal?.form.layoutType === 'DrawerForm' ? { |
||||
|
layoutType: 'DrawerForm', |
||||
|
drawerProps: { |
||||
|
maskClosable: false, |
||||
|
} |
||||
|
} : { |
||||
|
layoutType: 'ModalForm', |
||||
|
modalProps: { |
||||
|
maskClosable: false, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const renderTitle = () => { |
||||
|
if (title) { |
||||
|
return title |
||||
|
} |
||||
|
|
||||
|
if (apiCtx.menu) { |
||||
|
const { menu } = apiCtx |
||||
|
return menu.title |
||||
|
} |
||||
|
return null |
||||
|
} |
||||
|
|
||||
|
const tableTitle = <> |
||||
|
<Button key={'add'} |
||||
|
onClick={() => { |
||||
|
form.resetFields() |
||||
|
form.setFieldsValue({ |
||||
|
id: 0, |
||||
|
}) |
||||
|
setOpen(true) |
||||
|
}} |
||||
|
type={'primary'}>{'添加'}</Button> |
||||
|
</> |
||||
|
|
||||
|
return ( |
||||
|
<> |
||||
|
<ListPageLayout |
||||
|
className={styles.container} |
||||
|
title={renderTitle()}> |
||||
|
|
||||
|
<ProTable |
||||
|
{...curdModal?.table} |
||||
|
rowKey="id" |
||||
|
headerTitle={tableTitle} |
||||
|
toolbar={{ |
||||
|
/*search: { |
||||
|
loading: isFetching && !!search?.key, |
||||
|
onSearch: (value: string) => { |
||||
|
setSearch(prev => ({ |
||||
|
...prev, |
||||
|
title: value |
||||
|
})) |
||||
|
}, |
||||
|
allowClear: true, |
||||
|
onChange: (e) => { |
||||
|
setSearchKey(e.target?.value) |
||||
|
}, |
||||
|
value: searchKey, |
||||
|
placeholder: '输入关键字搜索', |
||||
|
},*/ |
||||
|
actions: [ |
||||
|
<Tooltip key={'filter'} title={'高级查询'}> |
||||
|
<Badge count={getValueCount(search)}> |
||||
|
<Button |
||||
|
onClick={() => { |
||||
|
setFilterOpen(true) |
||||
|
}} |
||||
|
icon={<FilterOutlined/>} shape={'circle'} size={'small'}/> |
||||
|
</Badge> |
||||
|
</Tooltip>, |
||||
|
<Divider type={'vertical'} key={'divider'}/>, |
||||
|
|
||||
|
] |
||||
|
}} |
||||
|
scroll={{ |
||||
|
x: (columns?.length || 1) * 100, |
||||
|
y: 'calc(100vh - 290px)' |
||||
|
}} |
||||
|
search={false} |
||||
|
onRow={(record) => { |
||||
|
return { |
||||
|
className: cx({ |
||||
|
// 'ant-table-row-selected': currentMovie?.id === record.id
|
||||
|
}), |
||||
|
onClick: () => { |
||||
|
setModel(record) |
||||
|
} |
||||
|
} |
||||
|
}} |
||||
|
dateFormatter="string" |
||||
|
loading={isLoading || isFetching || curdLoading} |
||||
|
dataSource={data?.rows ?? []} |
||||
|
columns={columns} |
||||
|
options={{ |
||||
|
reload: () => { |
||||
|
refetch() |
||||
|
}, |
||||
|
}} |
||||
|
pagination={{ |
||||
|
total: data?.total, |
||||
|
pageSize: search.pageSize, |
||||
|
current: search.page, |
||||
|
onShowSizeChange: (current: number, size: number) => { |
||||
|
setSearch({ |
||||
|
...search, |
||||
|
pageSize: size, |
||||
|
page: current |
||||
|
}) |
||||
|
}, |
||||
|
onChange: (current, pageSize) => { |
||||
|
setSearch(prev => { |
||||
|
return { |
||||
|
...prev, |
||||
|
page: current, |
||||
|
pageSize: pageSize, |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
}} |
||||
|
/> |
||||
|
|
||||
|
<BetaSchemaForm |
||||
|
{...curdModal?.form} |
||||
|
grid={true} |
||||
|
shouldUpdate={false} |
||||
|
width={1000} |
||||
|
form={form} |
||||
|
layout={'vertical'} |
||||
|
scrollToFirstError={true} |
||||
|
title={model?.id !== 0 ? '编辑' : '添加'} |
||||
|
{...formProps as any} |
||||
|
open={open} |
||||
|
onOpenChange={(open) => { |
||||
|
setOpen(open) |
||||
|
}} |
||||
|
loading={isSubmitting} |
||||
|
onFinish={async (values) => { |
||||
|
saveOrUpdate(values as any) |
||||
|
}} |
||||
|
columns={columns as ProFormColumnsType[]}/> |
||||
|
<BetaSchemaForm |
||||
|
{...curdModal?.form} |
||||
|
title={'高级查询'} |
||||
|
grid={true} |
||||
|
shouldUpdate={false} |
||||
|
width={500} |
||||
|
form={filterForm} |
||||
|
open={openFilter} |
||||
|
onOpenChange={open => { |
||||
|
setFilterOpen(open) |
||||
|
}} |
||||
|
layout={'vertical'} |
||||
|
scrollToFirstError={true} |
||||
|
layoutType={formProps.layoutType as any} |
||||
|
drawerProps={{ |
||||
|
...formProps.drawerProps, |
||||
|
mask: false, |
||||
|
}} |
||||
|
modalProps={{ |
||||
|
...formProps.modalProps, |
||||
|
mask: false, |
||||
|
}} |
||||
|
submitter={{ |
||||
|
searchConfig: { |
||||
|
resetText: '清空', |
||||
|
submitText: '查询', |
||||
|
}, |
||||
|
onReset: () => { |
||||
|
filterForm.resetFields() |
||||
|
}, |
||||
|
render: (props,) => { |
||||
|
return ( |
||||
|
<div style={{ textAlign: 'right' }}> |
||||
|
<Space> |
||||
|
<Button onClick={() => { |
||||
|
props.reset() |
||||
|
|
||||
|
}}>{props.searchConfig?.resetText}</Button> |
||||
|
<Button type="primary" |
||||
|
onClick={() => { |
||||
|
props.submit() |
||||
|
}} |
||||
|
>{props.searchConfig?.submitText}</Button> |
||||
|
</Space> |
||||
|
</div> |
||||
|
) |
||||
|
}, |
||||
|
|
||||
|
}} |
||||
|
|
||||
|
|
||||
|
onFinish={async (values) => { |
||||
|
//处理,变成数组
|
||||
|
Object.keys(values).forEach(key => { |
||||
|
if (typeof values[key] === 'string' && values[key].includes(',')) { |
||||
|
values[key] = values[key].split(',') |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
setSearch(values) |
||||
|
|
||||
|
}} |
||||
|
columns={unSetColumnRules(columns.filter(item => !item.hideInSearch) as ProFormColumnsType[])}/> |
||||
|
|
||||
|
</ListPageLayout> |
||||
|
</> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
export default RForm |
@ -0,0 +1,13 @@ |
|||||
|
import { createStyles } from '@/theme' |
||||
|
|
||||
|
export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => { |
||||
|
const prefix = `${prefixCls}-${token?.proPrefix}-x-form-component` |
||||
|
|
||||
|
const container = css`
|
||||
|
|
||||
|
`
|
||||
|
|
||||
|
return { |
||||
|
container: cx(prefix, props?.className, container), |
||||
|
} |
||||
|
}) |
@ -0,0 +1,90 @@ |
|||||
|
import { RFormTypes } from '@/types/r-form/model' |
||||
|
import { ProColumns } from '@ant-design/pro-components' |
||||
|
import Switch from '@/components/switch' |
||||
|
import { Checkbox, DatePicker, Input, Radio, Select, TreeSelect } from 'antd' |
||||
|
import request from '@/request' |
||||
|
import { convertToBool } from '@/utils' |
||||
|
import { has, get } from 'lodash' |
||||
|
import { mapTree } from '@/utils/tree.ts' |
||||
|
|
||||
|
|
||||
|
const getValueType = (column: RFormTypes.IColumn) => { |
||||
|
|
||||
|
return column.valueType |
||||
|
} |
||||
|
|
||||
|
//根据type返回对应的组件
|
||||
|
const getComponent = (column: RFormTypes.IColumn) => { |
||||
|
const type = getValueType(column) as any |
||||
|
switch (type) { |
||||
|
case 'input': |
||||
|
return Input |
||||
|
case 'select': |
||||
|
return Select |
||||
|
case 'date': |
||||
|
return DatePicker |
||||
|
case 'switch': |
||||
|
return Switch |
||||
|
case 'radio': |
||||
|
return Radio |
||||
|
case 'checkbox': |
||||
|
return Checkbox |
||||
|
case 'textarea': |
||||
|
return Input.TextArea |
||||
|
case 'tree': |
||||
|
case 'treeSelect': |
||||
|
return TreeSelect |
||||
|
default: |
||||
|
return Input |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
export const transformAntdTableProColumns = (columns: RFormTypes.IColumn[], overwriteColumns?: ProColumns[]) => { |
||||
|
|
||||
|
const overwriteKeys = [] as string[] |
||||
|
|
||||
|
return (columns || []).map(item => { |
||||
|
|
||||
|
|
||||
|
const type = getValueType(item) |
||||
|
|
||||
|
const overwrite = overwriteColumns?.find(i => i.dataIndex === item.dataIndex || item.name) |
||||
|
if (overwrite) { |
||||
|
overwriteKeys.push(item.prop) |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
...item, |
||||
|
request: item.dicUrl ? async (params, props) => { |
||||
|
const { fieldProps: { dataFiledNames } } = props |
||||
|
const { value, res: resKey, label, disabled: disabledKey, children } = dataFiledNames || {} |
||||
|
const url = `/${item.dicUrl.replace(/^:/, '/')}` |
||||
|
return request[item.dicMethod || 'get'](url, params).then(res => { |
||||
|
const data = has(res.data, resKey) ? get(res.data, resKey) : res.data |
||||
|
|
||||
|
return mapTree(data || [], (i: any) => { |
||||
|
const disabled = has(i, disabledKey) ? get(i, disabledKey) : ('status' in i ? !convertToBool(i.status) : false) |
||||
|
return { |
||||
|
title: i[label || 'label'], |
||||
|
label: i[label || 'label'], |
||||
|
value: i[value || 'id'], |
||||
|
disabled, |
||||
|
data: i |
||||
|
} |
||||
|
|
||||
|
}, { children }) |
||||
|
}) |
||||
|
} : undefined, |
||||
|
// renderFormItem: (_scheam, config, dom) => {
|
||||
|
//
|
||||
|
// },
|
||||
|
render: (text: any, _record: any) => { |
||||
|
|
||||
|
return text |
||||
|
}, |
||||
|
...overwrite |
||||
|
} as ProColumns |
||||
|
}).concat(overwriteColumns?.filter(i => !overwriteKeys.includes(i.dataIndex)) || []) |
||||
|
|
||||
|
} |
@ -0,0 +1,26 @@ |
|||||
|
import { createFileRoute } from '@tanstack/react-router' |
||||
|
import RForm from '@/components/r-form' |
||||
|
|
||||
|
const RFormRender = () => { |
||||
|
|
||||
|
return <> |
||||
|
<RForm/> |
||||
|
</> |
||||
|
} |
||||
|
|
||||
|
type RFormRouteSearch = { |
||||
|
api: string |
||||
|
} |
||||
|
|
||||
|
// @ts-ignore fix route id
|
||||
|
export const Route = createFileRoute('x-form')({ |
||||
|
validateSearch: (search: Record<string, unknown>): RFormRouteSearch => { |
||||
|
// validate and parse the search params into a typed state
|
||||
|
// console.log(search.id)
|
||||
|
return { |
||||
|
api: (search.api ?? '') as string |
||||
|
} as RFormRouteSearch |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
export default RFormRender |
@ -0,0 +1,29 @@ |
|||||
|
|
||||
|
import { createStyles } from '@/theme' |
||||
|
|
||||
|
export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => { |
||||
|
const prefix = `${prefixCls}-${token?.proPrefix}-r-form-page` |
||||
|
|
||||
|
const container = css`
|
||||
|
.ant-table-cell { |
||||
|
.ant-tag { |
||||
|
padding-inline: 3px; |
||||
|
margin-inline-end: 3px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.ant-table-empty { |
||||
|
.ant-table-body { |
||||
|
height: calc(100vh - 350px) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.ant-pro-table-highlight { |
||||
|
|
||||
|
} |
||||
|
`
|
||||
|
|
||||
|
return { |
||||
|
container: cx(prefix, props?.className, container), |
||||
|
} |
||||
|
}) |
@ -0,0 +1,22 @@ |
|||||
|
import { createCURD } from '@/service/base.ts' |
||||
|
import { RFormTypes } from '@/types/r-form/model' |
||||
|
import request from '@/request.ts' |
||||
|
|
||||
|
const model = (api: string) => { |
||||
|
return { |
||||
|
...createCURD<any, RFormTypes.IModel>(api), |
||||
|
proxy: async <T = RFormTypes.IModelCURD>(params?: { |
||||
|
path: string, |
||||
|
body: any, |
||||
|
method?: string |
||||
|
}) => { |
||||
|
return request.post<T>(`/x_form/proxy`, { |
||||
|
api: `${api}/ui/curd`, |
||||
|
method: 'post', |
||||
|
...params |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export { model } |
@ -0,0 +1,189 @@ |
|||||
|
import { Atom, atom } from 'jotai' |
||||
|
import { IApiResult, IPage, IPageResult } from '@/global' |
||||
|
import { |
||||
|
atomWithMutation, |
||||
|
atomWithQuery, |
||||
|
AtomWithQueryResult, |
||||
|
AtomWithMutationResult, |
||||
|
queryClientAtom |
||||
|
} from 'jotai-tanstack-query' |
||||
|
import { message } from 'antd' |
||||
|
import { t } from 'i18next' |
||||
|
import { RFormTypes } from '@/types/r-form/model' |
||||
|
import * as modelServ from '@/service/r-form/model' |
||||
|
import { PrimitiveAtom } from 'jotai/vanilla/atom' |
||||
|
|
||||
|
type SearchParams = IPage & { |
||||
|
key?: string |
||||
|
api: string |
||||
|
} |
||||
|
|
||||
|
|
||||
|
export type ModelContext = { |
||||
|
apiAtom: PrimitiveAtom<string> |
||||
|
modelIdAtom: PrimitiveAtom<number> |
||||
|
modelIdsAtom: PrimitiveAtom<number[]> |
||||
|
modelAtom: PrimitiveAtom<RFormTypes.IModel> |
||||
|
modelSearchAtom: PrimitiveAtom<SearchParams> |
||||
|
modelPageAtom: PrimitiveAtom<IPage> |
||||
|
modelCURDAtom: Atom<AtomWithQueryResult<RFormTypes.IModelCURD['page']>> |
||||
|
modelsAtom: Atom<AtomWithQueryResult<IPageResult<any>>> |
||||
|
saveOrUpdateModelAtom: Atom<AtomWithMutationResult<any, any, any, any>> |
||||
|
deleteModelAtom: Atom<AtomWithMutationResult<any, any, any, any>> |
||||
|
} |
||||
|
|
||||
|
export const modelContext = new Map<string, ModelContext>() |
||||
|
|
||||
|
// @ts-ignore fix debug modelContext
|
||||
|
window.__MODELCONTEXT__ = modelContext |
||||
|
|
||||
|
export const useSpanModel = (name: string) => { |
||||
|
|
||||
|
if (modelContext.has(name)) { |
||||
|
return modelContext.get(name) |
||||
|
} |
||||
|
|
||||
|
const apiAtom = atom('') |
||||
|
|
||||
|
const modelIdAtom = atom(0) |
||||
|
|
||||
|
const modelIdsAtom = atom<number[]>([]) |
||||
|
|
||||
|
const modelAtom = atom<RFormTypes.IModel>(undefined as unknown as RFormTypes.IModel) |
||||
|
|
||||
|
const modelSearchAtom = atom<SearchParams>({ |
||||
|
key: '', |
||||
|
pageSize: 10, |
||||
|
page: 1, |
||||
|
} as SearchParams) |
||||
|
|
||||
|
const modelPageAtom = atom<IPage>({ |
||||
|
pageSize: 10, |
||||
|
page: 1, |
||||
|
}) |
||||
|
|
||||
|
const modelCURDAtom = atomWithQuery<IApiResult<RFormTypes.IModelCURD>, any, any, any>((get) => { |
||||
|
|
||||
|
return { |
||||
|
enabled: !!get(apiAtom), |
||||
|
queryKey: [ 'modelCURD', get(apiAtom) ], |
||||
|
queryFn: async ({ queryKey: [ , api ] }) => { |
||||
|
return await modelServ.model(api).proxy() |
||||
|
}, |
||||
|
select: (res) => { |
||||
|
return res.data.page |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
const modelsAtom = atomWithQuery<IApiResult<IPageResult<any>>, any, any, any>((get) => { |
||||
|
|
||||
|
const curd = get(modelCURDAtom) |
||||
|
return { |
||||
|
enabled: curd.isSuccess && !!get(apiAtom), |
||||
|
queryKey: [ 'models', get(modelSearchAtom), get(apiAtom) ], |
||||
|
queryFn: async ({ queryKey: [ , params, api ] }) => { |
||||
|
|
||||
|
if (api.startsWith('http')) { |
||||
|
return await modelServ.model(api).proxy<RFormTypes.IModel>({ |
||||
|
path: '/list', |
||||
|
body: params, |
||||
|
}) |
||||
|
} |
||||
|
return await modelServ.model(api).list(params as SearchParams) |
||||
|
}, |
||||
|
select: res => { |
||||
|
const data = res.data |
||||
|
data.rows = data.rows?.map(row => { |
||||
|
return { |
||||
|
...row, |
||||
|
//status: convertToBool(row.status)
|
||||
|
} |
||||
|
}) |
||||
|
return data |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
//saveOrUpdateAtom
|
||||
|
const saveOrUpdateModelAtom = atomWithMutation<IApiResult, RFormTypes.IModel>((get) => { |
||||
|
|
||||
|
return { |
||||
|
mutationKey: [ 'updateModel', get(apiAtom) ], |
||||
|
mutationFn: async (data) => { |
||||
|
const api = get(apiAtom) |
||||
|
if (!api) { |
||||
|
return Promise.reject('api 不能为空') |
||||
|
} |
||||
|
if (data.id === 0) { |
||||
|
if (api.startsWith('http')) { |
||||
|
return await modelServ.model(api).proxy<RFormTypes.IModel>({ |
||||
|
body: data, |
||||
|
path: '/add', |
||||
|
}) |
||||
|
} |
||||
|
return await modelServ.model(api).add(data) |
||||
|
} |
||||
|
if (api.startsWith('http')) { |
||||
|
return await modelServ.model(api).proxy<RFormTypes.IModel>({ |
||||
|
body: data, |
||||
|
path: '/edit', |
||||
|
}) |
||||
|
} |
||||
|
return await modelServ.model(api).update(data) |
||||
|
}, |
||||
|
onSuccess: (res) => { |
||||
|
const isAdd = !!res.data?.id |
||||
|
message.success(t(isAdd ? 'message.saveSuccess' : 'message.editSuccess', '保存成功')) |
||||
|
|
||||
|
//更新列表
|
||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
|
// @ts-ignore fix
|
||||
|
get(queryClientAtom).invalidateQueries({ queryKey: [ 'models', get(modelSearchAtom) ] }) |
||||
|
|
||||
|
return res |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
const deleteModelAtom = atomWithMutation((get) => { |
||||
|
|
||||
|
return { |
||||
|
mutationKey: [ 'deleteModel', get(apiAtom) ], |
||||
|
mutationFn: async (ids: number[]) => { |
||||
|
const api = get(apiAtom) |
||||
|
if (api.startsWith('http')) { |
||||
|
return await modelServ.model(api).proxy<RFormTypes.IModel>({ |
||||
|
body: ids ?? get(modelIdsAtom), |
||||
|
path: '/deletes', |
||||
|
}) |
||||
|
} |
||||
|
return await modelServ.model(api).batchDelete(ids ?? get(modelIdsAtom)) |
||||
|
}, |
||||
|
onSuccess: (res) => { |
||||
|
message.success('message.deleteSuccess') |
||||
|
//更新列表
|
||||
|
get(queryClientAtom).invalidateQueries({ queryKey: [ 'models', get(modelSearchAtom) ] }) |
||||
|
return res |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
|
||||
|
const value = { |
||||
|
apiAtom, |
||||
|
modelIdAtom, |
||||
|
modelIdsAtom, |
||||
|
modelAtom, |
||||
|
modelSearchAtom, |
||||
|
modelPageAtom, |
||||
|
modelCURDAtom, |
||||
|
modelsAtom, |
||||
|
saveOrUpdateModelAtom, |
||||
|
deleteModelAtom, |
||||
|
} as ModelContext |
||||
|
|
||||
|
modelContext.set(name, value) |
||||
|
|
||||
|
return value |
||||
|
} |
@ -0,0 +1,25 @@ |
|||||
|
import { ProColumns, ProTableProps, } from '@ant-design/pro-components' |
||||
|
import { FormSchema } from '@ant-design/pro-form/es/components/SchemaForm/typing' |
||||
|
|
||||
|
export namespace RFormTypes { |
||||
|
export interface IModel { |
||||
|
id: number; |
||||
|
created_at: string; |
||||
|
updated_at: string; |
||||
|
|
||||
|
[key: string]: any; |
||||
|
} |
||||
|
|
||||
|
export interface IModelCURD { |
||||
|
|
||||
|
page: { |
||||
|
columns: ProColumns[]; |
||||
|
config: any; |
||||
|
table: ProTableProps<any, any> |
||||
|
form: FormSchema |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue