refactor: 统一使用全局租户ID并优化客户端和外部应用管理页面
- 在 `ClientDownloadServiceImpl` 和 `ExternalAppServiceImpl` 中使用 `GLOBAL_TENANT_ID` - 移除 `requireOwned` 方法中的租户ID检查,更新为 `requireExisting` - 更新前端 `ClientManagement` 和 `ExternalAppManagement` 页面,优化平台选项和状态过滤 - 添加数据字典驱动的平台分组和选项加载逻辑 - 修复前端组件中的字段名称和代理配置问题dev_na
parent
3b7ba2c47a
commit
b2430abe73
|
|
@ -23,13 +23,14 @@ import java.nio.file.StandardCopyOption;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ClientDownloadServiceImpl extends ServiceImpl<ClientDownloadMapper, ClientDownload> implements ClientDownloadService {
|
||||
|
||||
private static final long GLOBAL_TENANT_ID = 0L;
|
||||
|
||||
@Value("${unisbase.app.upload-path}")
|
||||
private String uploadPath;
|
||||
|
||||
|
|
@ -39,7 +40,6 @@ public class ClientDownloadServiceImpl extends ServiceImpl<ClientDownloadMapper,
|
|||
@Override
|
||||
public List<ClientDownload> listForAdmin(LoginUser loginUser, String platformCode, Integer status) {
|
||||
LambdaQueryWrapper<ClientDownload> wrapper = new LambdaQueryWrapper<ClientDownload>()
|
||||
.eq(ClientDownload::getTenantId, loginUser.getTenantId())
|
||||
.orderByAsc(ClientDownload::getPlatformType)
|
||||
.orderByDesc(ClientDownload::getVersionCode)
|
||||
.orderByDesc(ClientDownload::getId);
|
||||
|
|
@ -58,7 +58,7 @@ public class ClientDownloadServiceImpl extends ServiceImpl<ClientDownloadMapper,
|
|||
validate(dto, false);
|
||||
ClientDownload entity = new ClientDownload();
|
||||
applyDto(entity, dto, false);
|
||||
entity.setTenantId(loginUser.getTenantId());
|
||||
entity.setTenantId(GLOBAL_TENANT_ID);
|
||||
entity.setCreatedBy(loginUser.getUserId());
|
||||
if (entity.getStatus() == null) {
|
||||
entity.setStatus(1);
|
||||
|
|
@ -74,8 +74,9 @@ public class ClientDownloadServiceImpl extends ServiceImpl<ClientDownloadMapper,
|
|||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ClientDownload update(Long id, ClientDownloadDTO dto, LoginUser loginUser) {
|
||||
ClientDownload entity = requireOwned(id, loginUser);
|
||||
ClientDownload entity = requireExisting(id);
|
||||
applyDto(entity, dto, true);
|
||||
entity.setTenantId(GLOBAL_TENANT_ID);
|
||||
clearLatestFlagIfNeeded(entity.getTenantId(), entity.getPlatformCode(), entity.getIsLatest(), entity.getId());
|
||||
this.updateById(entity);
|
||||
return entity;
|
||||
|
|
@ -84,7 +85,7 @@ public class ClientDownloadServiceImpl extends ServiceImpl<ClientDownloadMapper,
|
|||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void removeClient(Long id, LoginUser loginUser) {
|
||||
ClientDownload entity = requireOwned(id, loginUser);
|
||||
ClientDownload entity = requireExisting(id);
|
||||
this.removeById(entity.getId());
|
||||
}
|
||||
|
||||
|
|
@ -188,9 +189,9 @@ public class ClientDownloadServiceImpl extends ServiceImpl<ClientDownloadMapper,
|
|||
this.update(update);
|
||||
}
|
||||
|
||||
private ClientDownload requireOwned(Long id, LoginUser loginUser) {
|
||||
private ClientDownload requireExisting(Long id) {
|
||||
ClientDownload entity = this.getById(id);
|
||||
if (entity == null || !Objects.equals(entity.getTenantId(), loginUser.getTenantId())) {
|
||||
if (entity == null) {
|
||||
throw new RuntimeException("Client version not found");
|
||||
}
|
||||
return entity;
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ import java.util.stream.Collectors;
|
|||
@RequiredArgsConstructor
|
||||
public class ExternalAppServiceImpl extends ServiceImpl<ExternalAppMapper, ExternalApp> implements ExternalAppService {
|
||||
|
||||
private static final long GLOBAL_TENANT_ID = 0L;
|
||||
|
||||
private final SysUserMapper sysUserMapper;
|
||||
|
||||
@Value("${unisbase.app.upload-path}")
|
||||
|
|
@ -46,7 +48,6 @@ public class ExternalAppServiceImpl extends ServiceImpl<ExternalAppMapper, Exter
|
|||
@Override
|
||||
public List<Map<String, Object>> listForAdmin(LoginUser loginUser, String appType, Integer status) {
|
||||
LambdaQueryWrapper<ExternalApp> wrapper = new LambdaQueryWrapper<ExternalApp>()
|
||||
.eq(ExternalApp::getTenantId, loginUser.getTenantId())
|
||||
.orderByAsc(ExternalApp::getSortOrder)
|
||||
.orderByDesc(ExternalApp::getId);
|
||||
if (appType != null && !appType.isBlank()) {
|
||||
|
|
@ -92,7 +93,7 @@ public class ExternalAppServiceImpl extends ServiceImpl<ExternalAppMapper, Exter
|
|||
validate(dto, false);
|
||||
ExternalApp entity = new ExternalApp();
|
||||
applyDto(entity, dto, false);
|
||||
entity.setTenantId(loginUser.getTenantId());
|
||||
entity.setTenantId(GLOBAL_TENANT_ID);
|
||||
entity.setCreatedBy(loginUser.getUserId());
|
||||
if (entity.getStatus() == null) {
|
||||
entity.setStatus(1);
|
||||
|
|
@ -104,8 +105,9 @@ public class ExternalAppServiceImpl extends ServiceImpl<ExternalAppMapper, Exter
|
|||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ExternalApp update(Long id, ExternalAppDTO dto, LoginUser loginUser) {
|
||||
ExternalApp entity = requireOwned(id, loginUser);
|
||||
ExternalApp entity = requireExisting(id);
|
||||
applyDto(entity, dto, true);
|
||||
entity.setTenantId(GLOBAL_TENANT_ID);
|
||||
this.updateById(entity);
|
||||
return entity;
|
||||
}
|
||||
|
|
@ -113,7 +115,7 @@ public class ExternalAppServiceImpl extends ServiceImpl<ExternalAppMapper, Exter
|
|||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void removeApp(Long id, LoginUser loginUser) {
|
||||
ExternalApp entity = requireOwned(id, loginUser);
|
||||
ExternalApp entity = requireExisting(id);
|
||||
this.removeById(entity.getId());
|
||||
}
|
||||
|
||||
|
|
@ -179,9 +181,9 @@ public class ExternalAppServiceImpl extends ServiceImpl<ExternalAppMapper, Exter
|
|||
}
|
||||
}
|
||||
|
||||
private ExternalApp requireOwned(Long id, LoginUser loginUser) {
|
||||
private ExternalApp requireExisting(Long id) {
|
||||
ExternalApp entity = this.getById(id);
|
||||
if (entity == null || !Objects.equals(entity.getTenantId(), loginUser.getTenantId())) {
|
||||
if (entity == null) {
|
||||
throw new RuntimeException("External app not found");
|
||||
}
|
||||
return entity;
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ unisbase:
|
|||
- biz_llm_models
|
||||
- biz_asr_models
|
||||
- biz_prompt_templates
|
||||
- biz_client_downloads
|
||||
- biz_external_apps
|
||||
security:
|
||||
enabled: true
|
||||
mode: embedded
|
||||
|
|
|
|||
|
|
@ -1,13 +1,24 @@
|
|||
import { App, Button, Card, Col, Drawer, Form, Input, InputNumber, Popconfirm, Row, Select, Space, Switch, Table, Tabs, Tag, Typography, Upload } from "antd";
|
||||
import { App, Button, Card, Col, Drawer, Empty, Form, Input, InputNumber, Popconfirm, Row, Select, Space, Switch, Table, Tabs, Tag, Typography, Upload } from "antd";
|
||||
import type { ColumnsType } from "antd/es/table";
|
||||
import { CloudUploadOutlined, DeleteOutlined, DownloadOutlined, EditOutlined, LaptopOutlined, MobileOutlined, PlusOutlined, ReloadOutlined, SearchOutlined, SaveOutlined, UploadOutlined } from "@ant-design/icons";
|
||||
import { CloudUploadOutlined, DeleteOutlined, DownloadOutlined, EditOutlined, LaptopOutlined, MobileOutlined, PlusOutlined, ReloadOutlined, SearchOutlined, UploadOutlined } from "@ant-design/icons";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import PageHeader from "@/components/shared/PageHeader";
|
||||
import { getStandardPagination } from "@/utils/pagination";
|
||||
import { createClientDownload, deleteClientDownload, listClientDownloads, type ClientDownloadDTO, type ClientDownloadVO, updateClientDownload, uploadClientPackage } from "@/api/business/client";
|
||||
import { fetchDictItemsByTypeCode } from "@/api/dict";
|
||||
import { useDict } from "@/hooks/useDict";
|
||||
import type { SysDictItem } from "@/types";
|
||||
|
||||
const { Text } = Typography;
|
||||
const { TextArea } = Input;
|
||||
const CLIENT_PLATFORM_DICT = "client_platform";
|
||||
|
||||
const STATUS_FILTER_OPTIONS = [
|
||||
{ label: "全部状态", value: "all" },
|
||||
{ label: "已启用", value: "enabled" },
|
||||
{ label: "已停用", value: "disabled" },
|
||||
{ label: "最新版本", value: "latest" },
|
||||
] as const;
|
||||
|
||||
type ClientFormValues = {
|
||||
platformCode: string;
|
||||
|
|
@ -22,45 +33,53 @@ type ClientFormValues = {
|
|||
remark?: string;
|
||||
};
|
||||
|
||||
type ClientPlatformType = "mobile" | "desktop" | "terminal";
|
||||
|
||||
type ClientPlatformOption = {
|
||||
label: string;
|
||||
value: string;
|
||||
platformType: ClientPlatformType;
|
||||
childTypeCode: string;
|
||||
platformType: string;
|
||||
platformName: string;
|
||||
sortOrder: number;
|
||||
};
|
||||
|
||||
const PLATFORM_OPTIONS: ClientPlatformOption[] = [
|
||||
{ label: "Android", value: "android", platformType: "mobile", platformName: "android" },
|
||||
{ label: "iOS", value: "ios", platformType: "mobile", platformName: "ios" },
|
||||
{ label: "Windows", value: "windows", platformType: "desktop", platformName: "windows" },
|
||||
{ label: "macOS", value: "mac", platformType: "desktop", platformName: "mac" },
|
||||
{ label: "Linux", value: "linux", platformType: "desktop", platformName: "linux" },
|
||||
{ label: "专用终端", value: "terminal_android", platformType: "terminal", platformName: "android" },
|
||||
];
|
||||
|
||||
const PLATFORM_GROUPS: Array<{ key: ClientPlatformType; label: string }> = [
|
||||
{ key: "mobile", label: "移动端" },
|
||||
{ key: "desktop", label: "桌面端" },
|
||||
{ key: "terminal", label: "专用终端" },
|
||||
];
|
||||
|
||||
const STATUS_FILTER_OPTIONS = [
|
||||
{ label: "全部状态", value: "all" },
|
||||
{ label: "已启用", value: "enabled" },
|
||||
{ label: "已停用", value: "disabled" },
|
||||
{ label: "最新版本", value: "latest" },
|
||||
] as const;
|
||||
type ClientPlatformGroup = {
|
||||
key: string;
|
||||
label: string;
|
||||
childTypeCode: string;
|
||||
sortOrder: number;
|
||||
options: ClientPlatformOption[];
|
||||
};
|
||||
|
||||
function formatFileSize(fileSize?: number) {
|
||||
if (!fileSize) return "-";
|
||||
return `${(fileSize / (1024 * 1024)).toFixed(2)} MB`;
|
||||
}
|
||||
|
||||
function normalizeStatus(item: SysDictItem) {
|
||||
return item.status === undefined || item.status === 1;
|
||||
}
|
||||
|
||||
function derivePlatformType(childTypeCode: string) {
|
||||
return childTypeCode.startsWith("client_platform_")
|
||||
? childTypeCode.slice("client_platform_".length)
|
||||
: childTypeCode;
|
||||
}
|
||||
|
||||
function derivePlatformName(platformType: string, itemValue: string) {
|
||||
const normalized = (itemValue || "").trim().toLowerCase();
|
||||
const prefix = `${platformType}_`;
|
||||
if (normalized.startsWith(prefix)) {
|
||||
return normalized.slice(prefix.length);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export default function ClientManagement() {
|
||||
const { message } = App.useApp();
|
||||
const [form] = Form.useForm<ClientFormValues>();
|
||||
const { items: platformGroupItems, loading: groupLoading } = useDict(CLIENT_PLATFORM_DICT);
|
||||
const [platformChildren, setPlatformChildren] = useState<Record<string, SysDictItem[]>>({});
|
||||
const [platformLoading, setPlatformLoading] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
|
@ -68,14 +87,72 @@ export default function ClientManagement() {
|
|||
const [editing, setEditing] = useState<ClientDownloadVO | null>(null);
|
||||
const [records, setRecords] = useState<ClientDownloadVO[]>([]);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const [statusFilter, setStatusFilter] = useState<(typeof STATUS_FILTER_OPTIONS)[number]["value"]>("all");
|
||||
const [activeTab, setActiveTab] = useState<ClientPlatformType | "all">("all");
|
||||
const [statusFilter, setStatusFilter] = useState<"all" | "enabled" | "disabled" | "latest">("all");
|
||||
const [activeTab, setActiveTab] = useState<string>("all");
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
const loadChildren = async () => {
|
||||
const childTypeCodes = Array.from(new Set(platformGroupItems.map((item) => item.itemValue).filter(Boolean)));
|
||||
if (childTypeCodes.length === 0) {
|
||||
setPlatformChildren({});
|
||||
return;
|
||||
}
|
||||
|
||||
setPlatformLoading(true);
|
||||
try {
|
||||
const entries = await Promise.all(
|
||||
childTypeCodes.map(async (typeCode) => [typeCode, await fetchDictItemsByTypeCode(typeCode)] as const)
|
||||
);
|
||||
if (!active) return;
|
||||
setPlatformChildren(Object.fromEntries(entries));
|
||||
} finally {
|
||||
if (active) {
|
||||
setPlatformLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void loadChildren();
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [platformGroupItems]);
|
||||
|
||||
const platformGroups = useMemo<ClientPlatformGroup[]>(() => {
|
||||
return platformGroupItems
|
||||
.filter(normalizeStatus)
|
||||
.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0))
|
||||
.map((group) => {
|
||||
const childTypeCode = group.itemValue;
|
||||
const platformType = derivePlatformType(childTypeCode);
|
||||
const options = (platformChildren[childTypeCode] || [])
|
||||
.filter(normalizeStatus)
|
||||
.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0))
|
||||
.map((item) => ({
|
||||
label: item.itemLabel,
|
||||
value: item.itemValue,
|
||||
childTypeCode,
|
||||
platformType,
|
||||
platformName: derivePlatformName(platformType, item.itemValue),
|
||||
sortOrder: item.sortOrder ?? 0,
|
||||
}));
|
||||
return {
|
||||
key: platformType,
|
||||
label: group.itemLabel,
|
||||
childTypeCode,
|
||||
sortOrder: group.sortOrder ?? 0,
|
||||
options,
|
||||
};
|
||||
})
|
||||
.filter((group) => group.options.length > 0);
|
||||
}, [platformChildren, platformGroupItems]);
|
||||
|
||||
const platformMap = useMemo(
|
||||
() => Object.fromEntries(PLATFORM_OPTIONS.map((item) => [item.value, item])) as Record<string, ClientPlatformOption>,
|
||||
[]
|
||||
() => Object.fromEntries(platformGroups.flatMap((group) => group.options.map((option) => [option.value, option]))) as Record<string, ClientPlatformOption>,
|
||||
[platformGroups]
|
||||
);
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
|
|
@ -96,7 +173,7 @@ export default function ClientManagement() {
|
|||
const keyword = searchValue.trim().toLowerCase();
|
||||
return records.filter((item) => {
|
||||
const platform = platformMap[item.platformCode];
|
||||
const platformType = item.platformType || platform?.platformType;
|
||||
const platformType = item.platformType || platform?.platformType || "ungrouped";
|
||||
if (activeTab !== "all" && platformType !== activeTab) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -109,9 +186,7 @@ export default function ClientManagement() {
|
|||
if (statusFilter === "latest" && item.isLatest !== 1) {
|
||||
return false;
|
||||
}
|
||||
if (!keyword) {
|
||||
return true;
|
||||
}
|
||||
if (!keyword) return true;
|
||||
return [item.version, item.platformCode, platform?.label, item.minSystemVersion, item.downloadUrl, item.releaseNotes].some((field) =>
|
||||
String(field || "").toLowerCase().includes(keyword)
|
||||
);
|
||||
|
|
@ -131,14 +206,19 @@ export default function ClientManagement() {
|
|||
total: records.length,
|
||||
enabled: records.filter((item) => item.status === 1).length,
|
||||
latest: records.filter((item) => item.isLatest === 1).length,
|
||||
terminal: records.filter((item) => (item.platformType || platformMap[item.platformCode]?.platformType) === "terminal").length,
|
||||
}), [platformMap, records]);
|
||||
groups: platformGroups.length,
|
||||
}), [platformGroups.length, records]);
|
||||
|
||||
const openCreate = () => {
|
||||
const firstOption = platformGroups[0]?.options[0];
|
||||
if (!firstOption) {
|
||||
message.warning("当前未配置客户端发布平台字典,请先在字典管理中维护 client_platform 及其下级类型");
|
||||
return;
|
||||
}
|
||||
setEditing(null);
|
||||
form.resetFields();
|
||||
form.setFieldsValue({
|
||||
platformCode: PLATFORM_OPTIONS[0].value,
|
||||
platformCode: firstOption.value,
|
||||
statusEnabled: true,
|
||||
latest: false,
|
||||
});
|
||||
|
|
@ -161,6 +241,7 @@ export default function ClientManagement() {
|
|||
});
|
||||
setDrawerOpen(true);
|
||||
};
|
||||
|
||||
const handleDelete = async (record: ClientDownloadVO) => {
|
||||
await deleteClientDownload(record.id);
|
||||
message.success("删除成功");
|
||||
|
|
@ -170,10 +251,15 @@ export default function ClientManagement() {
|
|||
const handleSubmit = async () => {
|
||||
const values = await form.validateFields();
|
||||
const platform = platformMap[values.platformCode];
|
||||
if (!platform) {
|
||||
message.error("未找到所选平台字典项,请刷新后重试");
|
||||
return;
|
||||
}
|
||||
|
||||
const payload: ClientDownloadDTO = {
|
||||
platformCode: values.platformCode,
|
||||
platformType: platform?.platformType,
|
||||
platformName: platform?.platformName,
|
||||
platformType: platform.platformType,
|
||||
platformName: platform.platformName,
|
||||
version: values.version.trim(),
|
||||
versionCode: values.versionCode,
|
||||
downloadUrl: values.downloadUrl.trim(),
|
||||
|
|
@ -234,10 +320,7 @@ export default function ClientManagement() {
|
|||
dataIndex: "platformCode",
|
||||
key: "platformCode",
|
||||
width: 150,
|
||||
render: (value: string) => {
|
||||
const platform = platformMap[value];
|
||||
return <Tag color="blue">{platform?.label || value}</Tag>;
|
||||
},
|
||||
render: (value: string) => <Tag color="blue">{platformMap[value]?.label || value}</Tag>,
|
||||
},
|
||||
{
|
||||
title: "版本信息",
|
||||
|
|
@ -300,10 +383,10 @@ export default function ClientManagement() {
|
|||
<div style={{ height: "100%", display: "flex", flexDirection: "column", overflow: "hidden" }}>
|
||||
<PageHeader
|
||||
title="客户端管理"
|
||||
subtitle="统一维护移动端、桌面端与专用终端的版本发布、安装包上传和启停状态。"
|
||||
subtitle="发布平台由数据字典 client_platform 驱动,并按父子分组展示。"
|
||||
extra={
|
||||
<Space>
|
||||
<Button icon={<ReloadOutlined />} onClick={() => void loadData()} loading={loading}>刷新</Button>
|
||||
<Button icon={<ReloadOutlined />} onClick={() => void loadData()} loading={loading || groupLoading || platformLoading}>刷新</Button>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>新增版本</Button>
|
||||
</Space>
|
||||
}
|
||||
|
|
@ -313,7 +396,7 @@ export default function ClientManagement() {
|
|||
<Col span={6}><Card size="small"><Space><CloudUploadOutlined style={{ color: "#1677ff" }} /><div><div>发布总数</div><Text strong>{stats.total}</Text></div></Space></Card></Col>
|
||||
<Col span={6}><Card size="small"><Space><LaptopOutlined style={{ color: "#52c41a" }} /><div><div>已启用</div><Text strong>{stats.enabled}</Text></div></Space></Card></Col>
|
||||
<Col span={6}><Card size="small"><Space><MobileOutlined style={{ color: "#722ed1" }} /><div><div>最新版本</div><Text strong>{stats.latest}</Text></div></Space></Card></Col>
|
||||
<Col span={6}><Card size="small"><Space><LaptopOutlined style={{ color: "#fa8c16" }} /><div><div>专用终端</div><Text strong>{stats.terminal}</Text></div></Space></Card></Col>
|
||||
<Col span={6}><Card size="small"><Space><LaptopOutlined style={{ color: "#fa8c16" }} /><div><div>平台分组</div><Text strong>{stats.groups}</Text></div></Space></Card></Col>
|
||||
</Row>
|
||||
|
||||
<Card className="app-page__filter-card mb-4" styles={{ body: { padding: 16 } }}>
|
||||
|
|
@ -322,23 +405,23 @@ export default function ClientManagement() {
|
|||
<Input placeholder="搜索平台、版本、系统要求或下载地址" prefix={<SearchOutlined />} allowClear style={{ width: 320 }} value={searchValue} onChange={(event) => setSearchValue(event.target.value)} />
|
||||
<Select style={{ width: 150 }} value={statusFilter} options={STATUS_FILTER_OPTIONS as unknown as { label: string; value: string }[]} onChange={(value) => setStatusFilter(value as typeof statusFilter)} />
|
||||
</Space>
|
||||
<Tabs activeKey={activeTab} onChange={(value) => setActiveTab(value as ClientPlatformType | "all")} items={[{ key: "all", label: "全部" }, ...PLATFORM_GROUPS.map((item) => ({ key: item.key, label: item.label }))]} />
|
||||
<Tabs activeKey={activeTab} onChange={setActiveTab} items={[{ key: "all", label: "全部" }, ...platformGroups.map((group) => ({ key: group.key, label: group.label }))]} />
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
<Card className="app-page__content-card flex-1 flex flex-col overflow-hidden" styles={{ body: { padding: 0, flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" } }}>
|
||||
<Table rowKey="id" columns={columns} dataSource={pagedRecords} loading={loading} scroll={{ x: 960, y: "calc(100vh - 360px)" }} pagination={getStandardPagination(filteredRecords.length, page, pageSize, (nextPage, nextSize) => { setPage(nextPage); setPageSize(nextSize); })} />
|
||||
<Table rowKey="id" columns={columns} dataSource={pagedRecords} loading={loading || groupLoading || platformLoading} locale={platformGroups.length === 0 ? { emptyText: <Empty description="未配置 client_platform 字典项" /> } : undefined} scroll={{ x: 960, y: "calc(100vh - 360px)" }} pagination={getStandardPagination(filteredRecords.length, page, pageSize, (nextPage, nextSize) => { setPage(nextPage); setPageSize(nextSize); })} />
|
||||
</Card>
|
||||
|
||||
<Drawer title={editing ? "编辑客户端版本" : "新增客户端版本"} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={680} destroyOnHidden forceRender footer={<div className="app-page__drawer-footer"><Button onClick={() => setDrawerOpen(false)}>取消</Button><Button type="primary" icon={<SaveOutlined />} loading={saving} onClick={() => void handleSubmit()}>保存</Button></div>}>
|
||||
<Drawer title={editing ? "编辑客户端版本" : "新增客户端版本"} open={drawerOpen} onClose={() => setDrawerOpen(false)} width={680} destroyOnHidden forceRender footer={<div className="app-page__drawer-footer"><Button onClick={() => setDrawerOpen(false)}>取消</Button><Button type="primary" icon={<UploadOutlined />} loading={saving} onClick={() => void handleSubmit()}>保存</Button></div>}>
|
||||
<Form form={form} layout="vertical">
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item name="platformCode" label="发布平台" rules={[{ required: true, message: "请选择发布平台" }]}>
|
||||
<Select placeholder="选择平台" disabled={!!editing}>
|
||||
{PLATFORM_GROUPS.map((group) => (
|
||||
<Select.OptGroup key={group.key} label={group.label}>
|
||||
{PLATFORM_OPTIONS.filter((item) => item.platformType === group.key).map((item) => (
|
||||
<Select placeholder="选择平台" disabled={!!editing || platformGroups.length === 0}>
|
||||
{platformGroups.map((group) => (
|
||||
<Select.OptGroup key={group.childTypeCode} label={group.label}>
|
||||
{group.options.map((item) => (
|
||||
<Select.Option key={item.value} value={item.value}>{item.label}</Select.Option>
|
||||
))}
|
||||
</Select.OptGroup>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { App, Avatar, Button, Card, Col, Drawer, Form, Input, InputNumber, Popconfirm, Row, Select, Space, Switch, Table, Tag, Typography, Upload } from "antd";
|
||||
import { App, Avatar, Button, Card, Col, Drawer, Form, Input, InputNumber, Popconfirm, Row, Select, Space, Switch, Table, Tag, Typography, Upload } from "antd";
|
||||
import type { ColumnsType } from "antd/es/table";
|
||||
import { AppstoreOutlined, DeleteOutlined, EditOutlined, GlobalOutlined, LinkOutlined, PictureOutlined, PlusOutlined, ReloadOutlined, RobotOutlined, SaveOutlined, SearchOutlined, UploadOutlined } from "@ant-design/icons";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
|
@ -144,8 +144,8 @@ export default function ExternalAppManagement() {
|
|||
};
|
||||
|
||||
const handleDelete = async (record: ExternalAppVO) => {
|
||||
await deleteExternalApp(record.id);
|
||||
message.success("删除成功");
|
||||
message.success("Deleted successfully");
|
||||
await loadData();
|
||||
};
|
||||
|
||||
|
|
@ -177,6 +177,7 @@ export default function ExternalAppManagement() {
|
|||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUploadApk = async (file: File) => {
|
||||
setUploadingApk(true);
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in New Issue