From b2430abe736d07ead80891629e57b1210e23fa17 Mon Sep 17 00:00:00 2001 From: chenhao Date: Tue, 14 Apr 2026 11:17:25 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=BB=9F=E4=B8=80=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E5=85=A8=E5=B1=80=E7=A7=9F=E6=88=B7ID=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AE=A2=E6=88=B7=E7=AB=AF=E5=92=8C=E5=A4=96?= =?UTF-8?q?=E9=83=A8=E5=BA=94=E7=94=A8=E7=AE=A1=E7=90=86=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 `ClientDownloadServiceImpl` 和 `ExternalAppServiceImpl` 中使用 `GLOBAL_TENANT_ID` - 移除 `requireOwned` 方法中的租户ID检查,更新为 `requireExisting` - 更新前端 `ClientManagement` 和 `ExternalAppManagement` 页面,优化平台选项和状态过滤 - 添加数据字典驱动的平台分组和选项加载逻辑 - 修复前端组件中的字段名称和代理配置问题 --- .../biz/impl/ClientDownloadServiceImpl.java | 17 +- .../biz/impl/ExternalAppServiceImpl.java | 16 +- backend/src/main/resources/application.yml | 2 + .../src/pages/business/ClientManagement.tsx | 189 +++++++++++++----- .../pages/business/ExternalAppManagement.tsx | 5 +- 5 files changed, 159 insertions(+), 70 deletions(-) diff --git a/backend/src/main/java/com/imeeting/service/biz/impl/ClientDownloadServiceImpl.java b/backend/src/main/java/com/imeeting/service/biz/impl/ClientDownloadServiceImpl.java index 112dc84..880ed95 100644 --- a/backend/src/main/java/com/imeeting/service/biz/impl/ClientDownloadServiceImpl.java +++ b/backend/src/main/java/com/imeeting/service/biz/impl/ClientDownloadServiceImpl.java @@ -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 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 listForAdmin(LoginUser loginUser, String platformCode, Integer status) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper() - .eq(ClientDownload::getTenantId, loginUser.getTenantId()) .orderByAsc(ClientDownload::getPlatformType) .orderByDesc(ClientDownload::getVersionCode) .orderByDesc(ClientDownload::getId); @@ -58,7 +58,7 @@ public class ClientDownloadServiceImpl extends ServiceImpl 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> listForAdmin(LoginUser loginUser, String appType, Integer status) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper() - .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 = [ - { 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(); + const { items: platformGroupItems, loading: groupLoading } = useDict(CLIENT_PLATFORM_DICT); + const [platformChildren, setPlatformChildren] = useState>({}); + 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(null); const [records, setRecords] = useState([]); const [searchValue, setSearchValue] = useState(""); - const [statusFilter, setStatusFilter] = useState<(typeof STATUS_FILTER_OPTIONS)[number]["value"]>("all"); - const [activeTab, setActiveTab] = useState("all"); + const [statusFilter, setStatusFilter] = useState<"all" | "enabled" | "disabled" | "latest">("all"); + const [activeTab, setActiveTab] = useState("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(() => { + 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, - [] + () => Object.fromEntries(platformGroups.flatMap((group) => group.options.map((option) => [option.value, option]))) as Record, + [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 {platform?.label || value}; - }, + render: (value: string) => {platformMap[value]?.label || value}, }, { title: "版本信息", @@ -300,10 +383,10 @@ export default function ClientManagement() {
- + } @@ -313,7 +396,7 @@ export default function ClientManagement() {
发布总数
{stats.total}
已启用
{stats.enabled}
最新版本
{stats.latest}
-
专用终端
{stats.terminal}
+
平台分组
{stats.groups}
@@ -322,23 +405,23 @@ export default function ClientManagement() { } allowClear style={{ width: 320 }} value={searchValue} onChange={(event) => setSearchValue(event.target.value)} /> - {PLATFORM_GROUPS.map((group) => ( - - {PLATFORM_OPTIONS.filter((item) => item.platformType === group.key).map((item) => ( +