dark
3 months ago
8 changed files with 625 additions and 2 deletions
-
1package.json
-
441src/pages/app/package/index.tsx
-
26src/pages/app/package/style.ts
-
25src/service/app/package.ts
-
90src/store/app/package.ts
-
25src/types/app/package.d.ts
-
8src/utils/index.ts
-
11vite.config.ts
@ -0,0 +1,441 @@ |
|||||
|
import { useTranslation } from '@/i18n.ts' |
||||
|
import { Button, Form, Popconfirm, Divider, Space, Tooltip, Badge } from 'antd' |
||||
|
import { useAtom, useAtomValue } from 'jotai' |
||||
|
import { |
||||
|
deleteAppPackageAtom, |
||||
|
saveOrUpdateAppPackageAtom, appPackageAtom, appPackagesAtom, appPackageSearchAtom, |
||||
|
} from '@/store/app/package' |
||||
|
import { useEffect, useMemo, useState } from 'react' |
||||
|
import Action from '@/components/action/Action.tsx' |
||||
|
import { |
||||
|
BetaSchemaForm, |
||||
|
ProColumns, |
||||
|
ProFormColumnsType, |
||||
|
} from '@ant-design/pro-components' |
||||
|
import ListPageLayout from '@/layout/ListPageLayout.tsx' |
||||
|
import { useStyle } from './style' |
||||
|
import { FilterOutlined } from '@ant-design/icons' |
||||
|
import { getValueCount, unSetColumnRules } from '@/utils' |
||||
|
import { Table as ProTable } from '@/components/table' |
||||
|
import Switch from '@/components/switch' |
||||
|
|
||||
|
const i18nPrefix = 'appPackages.list' |
||||
|
|
||||
|
const AppPackage = () => { |
||||
|
|
||||
|
const { styles, cx } = useStyle() |
||||
|
const { t } = useTranslation() |
||||
|
const [ form ] = Form.useForm() |
||||
|
const [ filterForm ] = Form.useForm() |
||||
|
const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateAppPackageAtom) |
||||
|
const [ search, setSearch ] = useAtom(appPackageSearchAtom) |
||||
|
const [ currentAppPackage, setAppPackage ] = useAtom(appPackageAtom) |
||||
|
const { data, isFetching, isLoading, refetch } = useAtomValue(appPackagesAtom) |
||||
|
const { mutate: deleteAppPackage, isPending: isDeleting } = useAtomValue(deleteAppPackageAtom) |
||||
|
|
||||
|
const [ open, setOpen ] = useState(false) |
||||
|
const [ openFilter, setFilterOpen ] = useState(false) |
||||
|
const [ searchKey, setSearchKey ] = useState(search?.title) |
||||
|
|
||||
|
const columns = useMemo(() => { |
||||
|
return [ |
||||
|
{ |
||||
|
title: 'ID', |
||||
|
dataIndex: 'id', |
||||
|
hideInTable: true, |
||||
|
hideInSearch: true, |
||||
|
formItemProps: { hidden: true } |
||||
|
}, |
||||
|
{ |
||||
|
title: t(`${i18nPrefix}.columns.package_name`, '包名'), |
||||
|
dataIndex: 'package_name', |
||||
|
formItemProps: { |
||||
|
rules: [ |
||||
|
{ |
||||
|
required: true, |
||||
|
message: t('message.required', '包名必填') |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
title: t(`${i18nPrefix}.columns.app_name`, '应用名'), |
||||
|
dataIndex: 'app_name', |
||||
|
formItemProps: { |
||||
|
rules: [ |
||||
|
{ |
||||
|
required: true, |
||||
|
message: t('message.required', '应用名必填') |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
title: t(`${i18nPrefix}.columns.app_icon`, '应用图标'), |
||||
|
dataIndex: 'app_icon', |
||||
|
formItemProps: { |
||||
|
rules: [ |
||||
|
{ |
||||
|
required: true, |
||||
|
message: t('message.required', '应用图标必填') |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
title: t(`${i18nPrefix}.columns.web_url`, '主页地址'), |
||||
|
dataIndex: 'web_url', |
||||
|
formItemProps: { |
||||
|
rules: [ |
||||
|
{ |
||||
|
required: true, |
||||
|
message: t('message.required', '主页地址必填') |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
title: t(`${i18nPrefix}.columns.splash_url`, '启动图'), |
||||
|
dataIndex: 'splash_url', |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
title: t(`${i18nPrefix}.columns.conf`, '配置地址'), |
||||
|
dataIndex: 'conf', |
||||
|
formItemProps: { |
||||
|
rules: [ |
||||
|
{ |
||||
|
required: true, |
||||
|
message: t('message.required', '配置地址必填') |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
title: t(`${i18nPrefix}.columns.jks_url`, '签名文件地址'), |
||||
|
dataIndex: 'jks_url', |
||||
|
formItemProps: { |
||||
|
rules: [ |
||||
|
{ |
||||
|
required: true, |
||||
|
message: t('message.required', '签名文件地址必填') |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
title: t(`${i18nPrefix}.columns.app_url`, '应用地址'), |
||||
|
dataIndex: 'app_url', |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
title: t(`${i18nPrefix}.columns.splash_color`, 'App背景色'), |
||||
|
dataIndex: 'splash_color', |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
title: t(`${i18nPrefix}.columns.key_alias`, 'key别名'), |
||||
|
dataIndex: 'key_alias', |
||||
|
formItemProps: { |
||||
|
rules: [ |
||||
|
{ |
||||
|
required: true, |
||||
|
message: t('message.required', 'key别名必填') |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
title: t(`${i18nPrefix}.columns.store_pwd`, 'jks密码'), |
||||
|
dataIndex: 'store_pwd', |
||||
|
formItemProps: { |
||||
|
rules: [ |
||||
|
{ |
||||
|
required: true, |
||||
|
message: t('message.required', 'jks密码必填') |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
title: t(`${i18nPrefix}.columns.key_pwd`, 'key密码'), |
||||
|
dataIndex: 'key_pwd', |
||||
|
formItemProps: { |
||||
|
rules: [ |
||||
|
{ |
||||
|
required: true, |
||||
|
message: t('message.required', 'key密码必填') |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
title: t(`${i18nPrefix}.columns.status`, '状态'), |
||||
|
dataIndex: 'status', |
||||
|
valueType: 'select', |
||||
|
fieldProps: { |
||||
|
options: [ |
||||
|
{ label: '未处理', value: 0 }, |
||||
|
{ label: '队列中', value: 1 }, |
||||
|
{ label: '打包中', value: 2 }, |
||||
|
{ label: '打包成功', value: 3 }, |
||||
|
{ label: '打包失败', value: 4 }, |
||||
|
] |
||||
|
}, |
||||
|
render: (_text, record) => { |
||||
|
//0未处理 1队列中 2打包中 3打包成功 4打包失败
|
||||
|
return <Badge status={[ 'default', 'processing', 'processing', 'success', 'error' ][record.status]} |
||||
|
text={[ '处理', '队列中', '打包中', '打包成功', '打包失败' ][record.status]}/> |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
title: t(`${i18nPrefix}.columns.message`, '打包信息'), |
||||
|
dataIndex: 'message', |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
title: t(`${i18nPrefix}.columns.x_86`, '32位'), |
||||
|
dataIndex: 'x_86', |
||||
|
valueType: 'switch', |
||||
|
render: (_text, record) => { |
||||
|
return <Switch checked={record.x_86 === 1} size={'small'}/> |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
title: t(`${i18nPrefix}.columns.uid`, 'uid'), |
||||
|
dataIndex: 'uid', |
||||
|
hideInTable: true, |
||||
|
hideInSearch: true, |
||||
|
formItemProps: { hidden: true } |
||||
|
}, |
||||
|
|
||||
|
{ |
||||
|
title: t(`${i18nPrefix}.columns.option`, '操作'), |
||||
|
key: 'option', |
||||
|
valueType: 'option', |
||||
|
fixed: 'right', |
||||
|
render: (_, record) => [ |
||||
|
<Action key="edit" |
||||
|
as={'a'} |
||||
|
onClick={() => { |
||||
|
form.setFieldsValue(record) |
||||
|
setOpen(true) |
||||
|
}}>{t('actions.edit')}</Action>, |
||||
|
<Popconfirm |
||||
|
key={'del_confirm'} |
||||
|
disabled={isDeleting} |
||||
|
onConfirm={() => { |
||||
|
deleteAppPackage(record.id ) |
||||
|
}} |
||||
|
title={t('message.deleteConfirm')}> |
||||
|
<a key="del"> |
||||
|
{t('actions.delete', '删除')} |
||||
|
</a> |
||||
|
</Popconfirm> |
||||
|
] |
||||
|
} |
||||
|
] as ProColumns[] |
||||
|
}, [ isDeleting, currentAppPackage, search ]) |
||||
|
|
||||
|
useEffect(() => { |
||||
|
|
||||
|
setSearchKey(search?.title) |
||||
|
filterForm.setFieldsValue(search) |
||||
|
|
||||
|
}, [ search ]) |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (isSuccess) { |
||||
|
setOpen(false) |
||||
|
} |
||||
|
}, [ isSuccess ]) |
||||
|
|
||||
|
return ( |
||||
|
<ListPageLayout className={styles.container}> |
||||
|
<ProTable |
||||
|
rowKey="id" |
||||
|
headerTitle={t(`${i18nPrefix}.title`, '应用打包管理')} |
||||
|
toolbar={{ |
||||
|
search: { |
||||
|
loading: isFetching && !!search?.title, |
||||
|
onSearch: (value: string) => { |
||||
|
setSearch(prev => ({ |
||||
|
...prev, |
||||
|
title: value |
||||
|
})) |
||||
|
}, |
||||
|
allowClear: true, |
||||
|
onChange: (e) => { |
||||
|
setSearchKey(e.target?.value) |
||||
|
}, |
||||
|
value: searchKey, |
||||
|
placeholder: t(`${i18nPrefix}.placeholder`, '输入应用打包名称') |
||||
|
}, |
||||
|
actions: [ |
||||
|
<Tooltip key={'filter'} title={t(`${i18nPrefix}.filter.tooltip`, '高级查询')}> |
||||
|
<Badge count={getValueCount(search)}> |
||||
|
<Button |
||||
|
onClick={() => { |
||||
|
setFilterOpen(true) |
||||
|
}} |
||||
|
icon={<FilterOutlined/>} shape={'circle'} size={'small'}/> |
||||
|
</Badge> |
||||
|
</Tooltip>, |
||||
|
<Divider type={'vertical'} key={'divider'}/>, |
||||
|
<Button key={'add'} |
||||
|
onClick={() => { |
||||
|
form.resetFields() |
||||
|
form.setFieldsValue({ |
||||
|
id: 0, |
||||
|
}) |
||||
|
setOpen(true) |
||||
|
}} |
||||
|
type={'primary'}>{t(`${i18nPrefix}.add`, '添加')}</Button> |
||||
|
] |
||||
|
}} |
||||
|
scroll={{ |
||||
|
x: columns.length * 200, |
||||
|
y: 'calc(100vh - 290px)' |
||||
|
}} |
||||
|
search={false} |
||||
|
onRow={(record) => { |
||||
|
return { |
||||
|
className: cx({ |
||||
|
'ant-table-row-selected': currentAppPackage?.id === record.id |
||||
|
}), |
||||
|
onClick: () => { |
||||
|
setAppPackage(record) |
||||
|
} |
||||
|
} |
||||
|
}} |
||||
|
dateFormatter="string" |
||||
|
loading={isLoading || isFetching} |
||||
|
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 |
||||
|
grid={true} |
||||
|
shouldUpdate={false} |
||||
|
width={1000} |
||||
|
form={form} |
||||
|
layout={'vertical'} |
||||
|
scrollToFirstError={true} |
||||
|
title={t(`${i18nPrefix}.title_${form.getFieldValue('id') !== 0 ? 'edit' : 'add'}`, form.getFieldValue('id') !== 0 ? '应用打包编辑' : '应用打包添加')} |
||||
|
layoutType={'DrawerForm'} |
||||
|
open={open} |
||||
|
drawerProps={{ |
||||
|
maskClosable: false, |
||||
|
}} |
||||
|
onOpenChange={(open) => { |
||||
|
setOpen(open) |
||||
|
}} |
||||
|
loading={isSubmitting} |
||||
|
onValuesChange={(values) => { |
||||
|
|
||||
|
}} |
||||
|
onFinish={async (values) => { |
||||
|
saveOrUpdate(values) |
||||
|
}} |
||||
|
columns={columns as ProFormColumnsType[]}/> |
||||
|
<BetaSchemaForm |
||||
|
title={t(`${i18nPrefix}.filter.title`, '应用打包高级查询')} |
||||
|
grid={true} |
||||
|
shouldUpdate={false} |
||||
|
width={500} |
||||
|
form={filterForm} |
||||
|
open={openFilter} |
||||
|
onOpenChange={open => { |
||||
|
setFilterOpen(open) |
||||
|
}} |
||||
|
layout={'vertical'} |
||||
|
scrollToFirstError={true} |
||||
|
layoutType={'DrawerForm'} |
||||
|
drawerProps={{ |
||||
|
maskClosable: false, |
||||
|
mask: false, |
||||
|
}} |
||||
|
submitter={{ |
||||
|
searchConfig: { |
||||
|
resetText: t(`${i18nPrefix}.filter.reset`, '清空'), |
||||
|
submitText: t(`${i18nPrefix}.filter.submit`, '查询'), |
||||
|
}, |
||||
|
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> |
||||
|
) |
||||
|
}, |
||||
|
|
||||
|
}} |
||||
|
onValuesChange={(values) => { |
||||
|
|
||||
|
}} |
||||
|
|
||||
|
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 AppPackage |
@ -0,0 +1,26 @@ |
|||||
|
import { createStyles } from '@/theme' |
||||
|
|
||||
|
export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => { |
||||
|
const prefix = `${prefixCls}-${token?.proPrefix}-appPackage-list-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,25 @@ |
|||||
|
import { createCURD } from '@/service/base.ts' |
||||
|
import { APP } from '@/types/app/package' |
||||
|
import request from '@/request.ts' |
||||
|
import { IPageResult } from '@/global' |
||||
|
|
||||
|
const appPackage = { |
||||
|
...createCURD<any, APP.IAppPackage>('/package'), |
||||
|
list: async (params: any) => { |
||||
|
return await request.get<IPageResult<APP.IAppPackage>>(`/package/list`, { ...params }) |
||||
|
}, |
||||
|
//http://154.88.7.8:45321/package/v1/create
|
||||
|
add: async (params: any) => { |
||||
|
return await request.post<IPageResult<APP.IAppPackage>>(`/package/create`, { ...params }) |
||||
|
}, |
||||
|
//delete
|
||||
|
delete: async (params: any) => { |
||||
|
return await request.post<IPageResult<APP.IAppPackage>>(`/package/delete`, { ...params }) |
||||
|
}, |
||||
|
//package
|
||||
|
package: async (params: any) => { |
||||
|
return await request.post<IPageResult<APP.IAppPackage>>(`/package/package`, { ...params }) |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
export default appPackage |
@ -0,0 +1,90 @@ |
|||||
|
import { atom } from 'jotai' |
||||
|
import { IApiResult, IPage } from '@/global' |
||||
|
import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query' |
||||
|
import { message } from 'antd' |
||||
|
import { t } from 'i18next' |
||||
|
import { APP } from '@/types/app/package' |
||||
|
import aPPServ from '@/service/app/package' |
||||
|
|
||||
|
type SearchParams = IPage & { |
||||
|
key?: string |
||||
|
|
||||
|
[key: string]: any |
||||
|
} |
||||
|
|
||||
|
export const appPackageIdAtom = atom(0) |
||||
|
|
||||
|
export const appPackageIdsAtom = atom<number[]>([]) |
||||
|
|
||||
|
export const appPackageAtom = atom<APP.IAppPackage>(undefined as unknown as APP.IAppPackage ) |
||||
|
|
||||
|
export const appPackageSearchAtom = atom<SearchParams>({ |
||||
|
key: '', |
||||
|
pageSize: 10, |
||||
|
page: 1, |
||||
|
} as SearchParams) |
||||
|
|
||||
|
export const appPackagePageAtom = atom<IPage>({ |
||||
|
pageSize: 10, |
||||
|
page: 1, |
||||
|
}) |
||||
|
|
||||
|
export const appPackagesAtom = atomWithQuery((get) => { |
||||
|
return { |
||||
|
queryKey: [ 'appPackages', get(appPackageSearchAtom) ], |
||||
|
queryFn: async ({ queryKey: [ , params ] }) => { |
||||
|
return await aPPServ.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
|
||||
|
export const saveOrUpdateAppPackageAtom = atomWithMutation<IApiResult, APP.IAppPackage>((get) => { |
||||
|
|
||||
|
return { |
||||
|
mutationKey: [ 'updateAppPackage' ], |
||||
|
mutationFn: async (data) => { |
||||
|
//data.status = data.status ? '1' : '0'
|
||||
|
if (data.id === 0) { |
||||
|
return await aPPServ.add(data) |
||||
|
} |
||||
|
return await aPPServ.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: [ 'appPackages', get(appPackageSearchAtom) ] }) |
||||
|
|
||||
|
return res |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
export const deleteAppPackageAtom = atomWithMutation((get) => { |
||||
|
return { |
||||
|
mutationKey: [ 'deleteAppPackage' ], |
||||
|
mutationFn: async (ids: number) => { |
||||
|
return await aPPServ.delete({ id: ids } ?? get(appPackageIdsAtom)) |
||||
|
}, |
||||
|
onSuccess: (res) => { |
||||
|
message.success('message.deleteSuccess') |
||||
|
//更新列表
|
||||
|
get(queryClientAtom).invalidateQueries({ queryKey: [ 'appPackages', get(appPackageSearchAtom) ] }) |
||||
|
return res |
||||
|
} |
||||
|
} |
||||
|
}) |
@ -0,0 +1,25 @@ |
|||||
|
export namespace APP { |
||||
|
export interface IAppPackage { |
||||
|
id: number; |
||||
|
package_name: string; |
||||
|
app_name: string; |
||||
|
app_icon: string; |
||||
|
web_url: string; |
||||
|
splash_url: string; |
||||
|
conf: string; |
||||
|
jks_url: string; |
||||
|
app_url: string; |
||||
|
splash_color: string; |
||||
|
key_alias: string; |
||||
|
store_pwd: string; |
||||
|
key_pwd: string; |
||||
|
created_by: number; |
||||
|
created_at: string; |
||||
|
updated_at: string; |
||||
|
updated_by: number; |
||||
|
status: number; |
||||
|
message: string; |
||||
|
x_86: number; |
||||
|
uid: number; |
||||
|
} |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue