Browse Source

增加app_package模块

main
dark 3 months ago
parent
commit
ef0a68a7b4
  1. 1
      package.json
  2. 441
      src/pages/app/package/index.tsx
  3. 26
      src/pages/app/package/style.ts
  4. 25
      src/service/app/package.ts
  5. 90
      src/store/app/package.ts
  6. 25
      src/types/app/package.d.ts
  7. 8
      src/utils/index.ts
  8. 11
      vite.config.ts

1
package.json

@ -26,6 +26,7 @@
"axios": "^1.6.8", "axios": "^1.6.8",
"bunshi": "^2.1.4", "bunshi": "^2.1.4",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"fast-copy": "^3.0.2",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"i18next": "^23.11.2", "i18next": "^23.11.2",
"i18next-browser-languagedetector": "^7.2.1", "i18next-browser-languagedetector": "^7.2.1",

441
src/pages/app/package/index.tsx

@ -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

26
src/pages/app/package/style.ts

@ -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),
}
})

25
src/service/app/package.ts

@ -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

90
src/store/app/package.ts

@ -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
}
}
})

25
src/types/app/package.d.ts

@ -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;
}
}

8
src/utils/index.ts

@ -2,6 +2,8 @@ import { IMenu } from '@/types/system/menus'
import { FiledNames, FlattenData, MenuItem } from '@/global' import { FiledNames, FlattenData, MenuItem } from '@/global'
import { getIcon } from '@/components/icon' import { getIcon } from '@/components/icon'
import { TreeDataNode, MenuItemProps } from 'antd' import { TreeDataNode, MenuItemProps } from 'antd'
import deepCopy from 'fast-copy'
//vite环境变量, 判断是否是开发环境 //vite环境变量, 判断是否是开发环境
export const isDev = import.meta.env.MODE === 'development' export const isDev = import.meta.env.MODE === 'development'
@ -177,7 +179,7 @@ export const getValueCount = (obj: any, filterObj: any = {}) => {
export const unSetColumnRules = (columns: any[]) => { export const unSetColumnRules = (columns: any[]) => {
return columns.map(col => {
return deepCopy(columns)?.map(col => {
col.__ignoreRules = true col.__ignoreRules = true
if (col.formItemProps?.rules?.length) { if (col.formItemProps?.rules?.length) {
col.formItemProps.rules = [] col.formItemProps.rules = []
@ -191,7 +193,9 @@ export const unSetColumnRules = (columns: any[]) => {
} }
} }
} }
return col
return {
...col
}
}) })
} }

11
vite.config.ts

@ -26,6 +26,17 @@ export default defineConfig(({ mode }) => {
origin: '*' origin: '*'
}, },
proxy: { proxy: {
'/api/v1/package': {
target: 'http://154.88.7.8:45321',
changeOrigin: true,
rewrite: (path) => {
//replace /api/v1/package to /package/v1
//http://154.88.7.8:45321/package/v1/list
path = path.replace('/api/v1/package', '/package/v1')
console.log(path)
return path
}
},
'/api/v1/movie': { '/api/v1/movie': {
target: 'http://47.113.117.106:10000', target: 'http://47.113.117.106:10000',
changeOrigin: true, changeOrigin: true,

Loading…
Cancel
Save