Browse Source

Merge remote-tracking branch 'origin/main'

main
lk 2 weeks ago
parent
commit
f91d54b65f
  1. 3
      src/components/action/Action.tsx
  2. 401
      src/pages/app/package/index.tsx
  3. 8
      src/service/app/package.ts
  4. 14
      src/store/app/package.ts
  5. 22
      src/types/app/package.d.ts

3
src/components/action/Action.tsx

@ -10,7 +10,8 @@ const Action = ({ title, as, children, ...props }: ActionProps) => {
const { styles } = useStyle() const { styles } = useStyle()
const isLink = as === 'a' || props.type === 'link' const isLink = as === 'a' || props.type === 'link'
const Comp = isLink ? 'a' : Button
//fixme 如果外部同时设置 as={'a'} disabled={true} ,这里a标签会置灰,但是仍可点击,为什么不直接用Button?
const Comp = isLink ? 'a' : Button
return ( return (
<span className={styles.container}> <span className={styles.container}>
<Comp {...props} <Comp {...props}

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

@ -1,11 +1,11 @@
import { useTranslation } from '@/i18n.ts'
import { Button, Form, Popconfirm, Divider, Space, Tooltip, Badge } from 'antd'
import { useAtom, useAtomValue } from 'jotai'
import {useTranslation} from '@/i18n.ts'
import {Button, Form, Popconfirm, Divider, Space, Tooltip, Badge} from 'antd'
import {useAtom, useAtomValue} from 'jotai'
import { import {
deleteAppPackageAtom, deleteAppPackageAtom,
saveOrUpdateAppPackageAtom, appPackageAtom, appPackagesAtom, appPackageSearchAtom, packageAppAtom, saveOrUpdateAppPackageAtom, appPackageAtom, appPackagesAtom, appPackageSearchAtom, packageAppAtom,
} from '@/store/app/package' } from '@/store/app/package'
import { useEffect, useMemo, useState } from 'react'
import {useEffect, useMemo, useState} from 'react'
import Action from '@/components/action/Action.tsx' import Action from '@/components/action/Action.tsx'
import { import {
BetaSchemaForm, BetaSchemaForm,
@ -13,228 +13,440 @@ import {
ProFormColumnsType, ProFormColumnsType,
} from '@ant-design/pro-components' } from '@ant-design/pro-components'
import ListPageLayout from '@/layout/ListPageLayout.tsx' 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'
import {useStyle} from './style'
import {FilterOutlined} from '@ant-design/icons'
import {getValueCount, unSetColumnRules} from '@/utils'
import {Table as ProTable} from '@/components/table'
import aPPServ from "@/service/app/package.ts";
import {useDialog} from "@/components/dialog";
const i18nPrefix = 'appPackages.list' const i18nPrefix = 'appPackages.list'
const AppPackage = () => { 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 { mutate: packageApp, isPending: packageing } = useAtomValue(packageAppAtom)
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 {mutate: packageApp, isPending: packageing} = useAtomValue(packageAppAtom)
const [open, setOpen] = useState(false)
const [openFilter, setFilterOpen] = useState(false)
const [searchKey, setSearchKey] = useState(search?.title)
const [trafficEnabled, setTrafficEnabled] = useState(false);
const [, dialog, openDialog] = useDialog({
title: '提示',
children: <div></div>,
okText: '去购买',
onOk: () => {
//todo 跳转应用插件市场
}
})
const [ open, setOpen ] = useState(false)
const [ openFilter, setFilterOpen ] = useState(false)
const [ searchKey, setSearchKey ] = useState(search?.title)
const handleTrafficSwitchClick = async () => {
const data = await aPPServ.check()
setTrafficEnabled(data.data.success)
if (!data.data.success) {
openDialog()
}
};
const columns = useMemo(() => {
const drawerColumns = useMemo(() => {
return [ return [
{ {
title: 'ID', title: 'ID',
dataIndex: 'id', dataIndex: 'id',
hideInTable: true, hideInTable: true,
hideInSearch: true, hideInSearch: true,
formItemProps: { hidden: true }
formItemProps: {hidden: true}
}, },
{ {
title: t(`${i18nPrefix}.columns.package_name`, '包名'),
dataIndex: 'package_name',
title: t(`${i18nPrefix}.columns.app_name`, '应用名称'),
dataIndex: 'app_name',
valueType: 'text',
fieldProps: {
maxLength: 10,
showCount: true,
},
formItemProps: { formItemProps: {
tooltip: '手机端建议不超过6个字',
rules: [ rules: [
{ {
required: true, required: true,
message: t('message.required', '包名必填')
message: t('message.required', '应用名称必填')
} }
] ]
} }
}, },
{ {
title: t(`${i18nPrefix}.columns.app_name`, '应用名'),
dataIndex: 'app_name',
title: t(`${i18nPrefix}.columns.package_name`, '包名'),
dataIndex: 'package_name',
valueType: 'text',
fieldProps: {
placeholder: '例: com.baidu.app',
maxLength: 20,
showCount: true,
},
formItemProps: { formItemProps: {
rules: [ rules: [
{ {
required: true, required: true,
message: t('message.required', '应用名必填')
message: t('message.required', '名必填')
} }
] ]
} }
}, },
{ {
title: t(`${i18nPrefix}.columns.app_icon`, '应用图标'),
title: t(`${i18nPrefix}.columns.app_icon`, '应用图标链接'),
dataIndex: 'app_icon', dataIndex: 'app_icon',
valueType: 'input',
formItemProps: { formItemProps: {
tooltip: '建议图标最大尺寸512x512,格式png,Win应用可以直接用favicon.ico链接',
rules: [ rules: [
{ {
required: true, required: true,
message: t('message.required', '应用图标必填') message: t('message.required', '应用图标必填')
},
{
pattern: /^(http|https):\/\/[^\s$.?#].[^\s]*$/,
message: t('message.invalidUrl', '请输入有效的URL')
} }
] ]
} }
}, },
{ {
title: t(`${i18nPrefix}.columns.web_url`, '主页地址'),
title: t(`${i18nPrefix}.columns.web_url`, '应用主页链接'),
dataIndex: 'web_url', dataIndex: 'web_url',
valueType: 'input',
formItemProps: { formItemProps: {
tooltip: '填自己网站的链接',
rules: [ rules: [
{ {
required: true, required: true,
message: t('message.required', '主页地址必填')
message: t('message.required', '应用主页链接必填')
},
{
pattern: /^(http|https):\/\/[^\s$.?#].[^\s]*$/,
message: t('message.invalidUrl', '请输入有效的URL')
} }
] ]
} }
}, },
{ {
title: t(`${i18nPrefix}.columns.splash_url`, '启动图'),
dataIndex: 'splash_url',
title: t(`${i18nPrefix}.columns.os`, '目标系统'),
dataIndex: 'os',
valueType: 'select',
fieldProps: {
options: [
{label: 'Android', value: 0},
{label: 'Windows', value: 1}
],
defaultValue: 0,
allowClear: false
},
render: (_text, record) => {
return <Badge status={[record.os] as any}
text={['Android', 'Windows'][record.os]}/>
},
formItemProps: {
rules: [
{
required: true,
}
]
}
},
{
title: t(`${i18nPrefix}.columns.enable_traffic`, '是否启用流量加速功能'),
dataIndex: 'enable_traffic',
valueType: 'switch',
fieldProps: {
onClick: handleTrafficSwitchClick,
checked: trafficEnabled
},
formItemProps: {
tooltip: '防屏蔽功能,需先购买流量套餐',
}
}, },
{ {
title: t(`${i18nPrefix}.columns.conf`, '配置地址'),
dataIndex: 'conf',
title: t(`${i18nPrefix}.columns.splash_url`, 'Android启动页图片链接'),
dataIndex: 'splash_url',
valueType: 'input',
formItemProps: { formItemProps: {
tooltip: '启动页的图片,只支持Android',
rules: [ rules: [
{ {
required: true,
message: t('message.required', '配置地址必填')
pattern: /^(http|https):\/\/[^\s$.?#].[^\s]*$/,
message: t('message.invalidUrl', '请输入有效的URL')
} }
] ]
} }
}, },
{ {
title: t(`${i18nPrefix}.columns.jks_url`, '签名文件地址'),
title: t(`${i18nPrefix}.columns.splash_color`, 'Android主题色'),
dataIndex: 'splash_color',
valueType: 'color',
formItemProps: {
tooltip: '建议选择启动页图片的底色,效果更美观,只支持Android',
}
},
{
title: t(`${i18nPrefix}.columns.jks_url`, 'Android签名文件链接'),
dataIndex: 'jks_url', dataIndex: 'jks_url',
valueType: 'input',
formItemProps: { formItemProps: {
tooltip: 'jks签名文件url,不填则使用默认的签名',
rules: [ rules: [
{ {
required: true,
message: t('message.required', '签名文件地址必填')
pattern: /^(http|https):\/\/[^\s$.?#].[^\s]*$/,
message: t('message.invalidUrl', '请输入有效的URL')
} }
] ]
} }
}, },
{ {
title: t(`${i18nPrefix}.columns.app_url`, '应用地址'),
dataIndex: 'app_url',
title: t(`${i18nPrefix}.columns.store_pwd`, 'Android签名文件密码'),
dataIndex: 'store_pwd',
valueType: 'password',
formItemProps: {
tooltip: '如果自定义jks签名文件,此项必填',
rules: [
{
validator: (_, value, callback) => {
form.validateFields(['jks_url'])
.then(() => {
const jksUrl = form.getFieldValue('jks_url');
if (jksUrl && !value) {
callback(t('message.required', '密码必填'));
} else {
callback();
}
})
.catch(() => callback());
}
}
]
}
}, },
{ {
title: t(`${i18nPrefix}.columns.splash_color`, 'App背景色'),
dataIndex: 'splash_color',
title: t(`${i18nPrefix}.columns.key_alias`, 'Android密钥别名'),
dataIndex: 'key_alias',
valueType: 'input',
formItemProps: {
tooltip: '如果自定义jks签名文件,此项必填',
rules: [
{
validator: (_, value, callback) => {
form.validateFields(['jks_url'])
.then(() => {
const jksUrl = form.getFieldValue('jks_url');
if (jksUrl && !value) {
callback(t('message.required', '别名必填'));
} else {
callback();
}
})
.catch(() => callback());
}
}
]
}
}, },
{
title: t(`${i18nPrefix}.columns.key_pwd`, 'Android密钥密码'),
dataIndex: 'key_pwd',
valueType: 'password',
formItemProps: {
tooltip: '如果自定义jks签名文件,此项必填',
rules: [
{
validator: (_, value, callback) => {
form.validateFields(['jks_url'])
.then(() => {
const jksUrl = form.getFieldValue('jks_url');
if (jksUrl && !value) {
callback(t('message.required', '密码必填'));
} else {
callback();
}
})
.catch(() => callback());
}
}
]
}
},
] as ProColumns[]
}, [isDeleting, currentAppPackage, search])
const columns = useMemo(() => {
return [
{ {
title: t(`${i18nPrefix}.columns.key_alias`, 'key别名'),
dataIndex: 'key_alias',
title: 'ID',
dataIndex: 'id',
hideInTable: true,
hideInSearch: true,
formItemProps: {hidden: true}
},
{
title: t(`${i18nPrefix}.columns.app_name`, '应用名'),
dataIndex: 'app_name',
formItemProps: { formItemProps: {
rules: [ rules: [
{ {
required: true, required: true,
message: t('message.required', 'key别名必填')
message: t('message.required', '应用名必填')
} }
] ]
} }
}, },
{ {
title: t(`${i18nPrefix}.columns.store_pwd`, 'jks密码'),
dataIndex: 'store_pwd',
title: t(`${i18nPrefix}.columns.pkgStatus`, '打包状态'),
dataIndex: 'pkgStatus.status',
render: (_text, record) => {
return <Badge
status={['default', 'processing', 'processing', 'success', 'error'][record.pkgStatus.status] as any}
text={['处理中', '队列中', '打包中', '打包成功', '打包失败'][record.pkgStatus.status]}/>
}
},
{
title: t(`${i18nPrefix}.columns.package_name`, '包名'),
dataIndex: 'package_name',
formItemProps: { formItemProps: {
rules: [ rules: [
{ {
required: true, required: true,
message: t('message.required', 'jks密码必填')
message: t('message.required', '包名必填')
} }
] ]
} }
}, },
{ {
title: t(`${i18nPrefix}.columns.key_pwd`, 'key密码'),
dataIndex: 'key_pwd',
title: t(`${i18nPrefix}.columns.app_icon`, '应用图标'),
dataIndex: 'app_icon',
formItemProps: { formItemProps: {
rules: [ rules: [
{ {
required: true, required: true,
message: t('message.required', 'key密码必填')
message: t('message.required', '应用图标必填')
} }
] ]
} }
}, },
{ {
title: t(`${i18nPrefix}.columns.status`, '状态'),
dataIndex: 'status',
title: t(`${i18nPrefix}.columns.web_url`, '主页地址'),
dataIndex: 'web_url',
formItemProps: {
rules: [
{
required: true,
message: t('message.required', '主页地址必填')
}
]
}
},
{
title: t(`${i18nPrefix}.columns.os`, '目标系统'),
dataIndex: 'os',
valueType: 'select', valueType: 'select',
fieldProps: { fieldProps: {
options: [ options: [
{ label: '未处理', value: 0 },
{ label: '队列中', value: 1 },
{ label: '打包中', value: 2 },
{ label: '打包成功', value: 3 },
{ label: '打包失败', value: 4 },
{label: 'Android', value: 0},
{label: 'Windows', value: 1}
] ]
}, },
formItemProps: {
rules: [
{
required: true,
message: t('message.required', 'key密码必填')
}
]
},
render: (_text, record) => {
return <Badge status={[record.os] as any}
text={['Android', 'Windows'][record.os]}/>
}
},
{
title: t(`${i18nPrefix}.columns.enable_traffic`, '是否已启用流量加速'),
dataIndex: 'enable_traffic',
render: (_text, record) => { render: (_text, record) => {
//0未处理 1队列中 2打包中 3打包成功 4打包失败
return <Badge status={[ 'default', 'processing', 'processing', 'success', 'error' ][record.status] as any}
text={[ '处理', '队列中', '打包中', '打包成功', '打包失败' ][record.status]}/>
return <div>{['否', '是'][record.enable_traffic]}</div>
} }
}, },
{ {
title: t(`${i18nPrefix}.columns.message`, '打包信息'),
dataIndex: 'message',
title: t(`${i18nPrefix}.columns.splash_url`, '启动图'),
dataIndex: 'splash_url',
},
{
title: t(`${i18nPrefix}.columns.jks_url`, '签名文件地址'),
dataIndex: 'jks_url',
}, },
{ {
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.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',
},
{
title: t(`${i18nPrefix}.columns.store_pwd`, 'jks密码'),
dataIndex: 'store_pwd',
},
{
title: t(`${i18nPrefix}.columns.key_pwd`, 'key密码'),
dataIndex: 'key_pwd',
},
{
title: t(`${i18nPrefix}.columns.uid`, 'uid'), title: t(`${i18nPrefix}.columns.uid`, 'uid'),
dataIndex: 'uid', dataIndex: 'uid',
hideInTable: true, hideInTable: true,
hideInSearch: true, hideInSearch: true,
formItemProps: { hidden: true }
formItemProps: {hidden: true}
}, },
{ {
title: t(`${i18nPrefix}.columns.option`, '操作'), title: t(`${i18nPrefix}.columns.option`, '操作'),
key: 'option', key: 'option',
valueType: 'option', valueType: 'option',
tooltip: '打包预计5分钟左右,需要刷新网页',
fixed: 'right', fixed: 'right',
render: (_, record) => [ render: (_, record) => [
<Action key="package" <Action key="package"
as={'a'} as={'a'}
loading={packageing} loading={packageing}
disabled={![ 0, 4 ].includes(record.status) || packageing}
disabled={[1, 2].includes(record.pkgStatus.status) || packageing}
onClick={() => { onClick={() => {
packageApp(record) packageApp(record)
}} }}
>{t('actions.package', '应用打包')}</Action>,
>{t('actions.package', '打包')}</Action>,
<Divider type={'vertical'}/>,
<Action key="download"
as={'a'}
type={'link'}
target={'_blank'}
href={record.pkgStatus.app_url}
disabled={!record.pkgStatus.app_url}
>{t('actions.download', '下载')}</Action>,
<Divider type={'vertical'}/>, <Divider type={'vertical'}/>,
<Action key="edit" <Action key="edit"
as={'a'} as={'a'}
@ -257,20 +469,20 @@ const AppPackage = () => {
] ]
} }
] as ProColumns[] ] as ProColumns[]
}, [ isDeleting, currentAppPackage, search ])
}, [isDeleting, currentAppPackage, search])
useEffect(() => { useEffect(() => {
setSearchKey(search?.title) setSearchKey(search?.title)
filterForm.setFieldsValue(search) filterForm.setFieldsValue(search)
}, [ search ])
}, [search])
useEffect(() => { useEffect(() => {
if (isSuccess) { if (isSuccess) {
setOpen(false) setOpen(false)
} }
}, [ isSuccess ])
}, [isSuccess])
return ( return (
<ListPageLayout className={styles.container}> <ListPageLayout className={styles.container}>
@ -378,13 +590,13 @@ const AppPackage = () => {
setOpen(open) setOpen(open)
}} }}
loading={isSubmitting} loading={isSubmitting}
onValuesChange={(values) => {
onValuesChange={() => {
}} }}
onFinish={async (values) => { onFinish={async (values) => {
saveOrUpdate(values) saveOrUpdate(values)
}} }}
columns={columns as ProFormColumnsType[]}/>
columns={drawerColumns as ProFormColumnsType[]}/>
<BetaSchemaForm <BetaSchemaForm
title={t(`${i18nPrefix}.filter.title`, '应用打包高级查询')} title={t(`${i18nPrefix}.filter.title`, '应用打包高级查询')}
grid={true} grid={true}
@ -412,7 +624,7 @@ const AppPackage = () => {
}, },
render: (props,) => { render: (props,) => {
return ( return (
<div style={{ textAlign: 'right' }}>
<div style={{textAlign: 'right'}}>
<Space> <Space>
<Button onClick={() => { <Button onClick={() => {
props.reset() props.reset()
@ -429,7 +641,7 @@ const AppPackage = () => {
}, },
}} }}
onValuesChange={(values) => {
onValuesChange={() => {
}} }}
@ -445,6 +657,9 @@ const AppPackage = () => {
}} }}
columns={unSetColumnRules(columns.filter(item => !item.hideInSearch)) as ProFormColumnsType[]}/> columns={unSetColumnRules(columns.filter(item => !item.hideInSearch)) as ProFormColumnsType[]}/>
{
dialog
}
</ListPageLayout> </ListPageLayout>
) )
} }

8
src/service/app/package.ts

@ -20,6 +20,14 @@ const appPackage = {
package: async (params: any) => { package: async (params: any) => {
return await request.post<IPageResult<APP.IAppPackage>>(`/package/package`, { ...params }) return await request.post<IPageResult<APP.IAppPackage>>(`/package/package`, { ...params })
}, },
check: async () => {
return await request.get<APP.IAppBoolRes>(`/package/traffic/check`)
},
getPkgStatus: async (params: any) => {
return await request.get<APP.IAppPkgStatus>(`/package/status`, { ...params })
},
} }
export default appPackage export default appPackage

14
src/store/app/package.ts

@ -33,7 +33,17 @@ export const appPackagesAtom = atomWithQuery((get) => {
return { return {
queryKey: [ 'appPackages', get(appPackageSearchAtom) ], queryKey: [ 'appPackages', get(appPackageSearchAtom) ],
queryFn: async ({ queryKey: [ , params ] }) => { queryFn: async ({ queryKey: [ , params ] }) => {
return await aPPServ.list(params as SearchParams)
const list = await aPPServ.list(params as SearchParams)
// 获取打包状态
await Promise.all(
list.data.rows?.map(async (row) => {
const pkgStatus = await aPPServ.getPkgStatus({ id: row.id })
row.pkgStatus = pkgStatus.data
return row
})
)
return list
}, },
select: res => { select: res => {
const data = res.data const data = res.data
@ -97,7 +107,7 @@ export const packageAppAtom = atomWithMutation((get) => {
return await aPPServ.package({ id: data.id }) return await aPPServ.package({ id: data.id })
}, },
onSuccess: (res) => { onSuccess: (res) => {
message.success('message.packageSuccess')
message.success('打包预计5分钟左右,请稍候刷新网页查看')
//更新列表 //更新列表
get(queryClientAtom).invalidateQueries({ queryKey: [ 'appPackages', get(appPackageSearchAtom) ] }) get(queryClientAtom).invalidateQueries({ queryKey: [ 'appPackages', get(appPackageSearchAtom) ] })
return res return res

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

@ -21,5 +21,27 @@ export namespace APP {
message: string; message: string;
x_86: number; x_86: number;
uid: number; uid: number;
pkgStatus: IAppPkgStatus
}
/**
* Bool类型返回结果
*/
export interface IAppBoolRes {
success: boolean;
}
/**
*
*/
export interface IAppPkgStatus {
id: number;
status: number; // 0未处理 1队列中 2打包中 3打包成功 4打包失败
message: string;
start_time: string;
end_time: string;
app_url: string;
position: number;
total: number;
} }
} }
Loading…
Cancel
Save