diff --git a/contracts/admin-openapi.json b/contracts/admin-openapi.json index 891a55d..b17e634 100644 --- a/contracts/admin-openapi.json +++ b/contracts/admin-openapi.json @@ -1130,6 +1130,11 @@ } }, "/admin/app-config/banners": { + "parameters": [ + { + "$ref": "#/components/parameters/AppCodeHeader" + } + ], "get": { "operationId": "listBanners", "responses": { @@ -1152,6 +1157,11 @@ } }, "/admin/app-config/banners/{banner_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/AppCodeHeader" + } + ], "put": { "operationId": "updateBanner", "responses": { @@ -1194,6 +1204,11 @@ } }, "/admin/app-config/splash-screens": { + "parameters": [ + { + "$ref": "#/components/parameters/AppCodeHeader" + } + ], "get": { "operationId": "listSplashScreens", "responses": { @@ -1216,6 +1231,11 @@ } }, "/admin/app-config/splash-screens/{splash_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/AppCodeHeader" + } + ], "put": { "operationId": "updateSplashScreen", "responses": { @@ -1258,6 +1278,11 @@ } }, "/admin/app-config/popups": { + "parameters": [ + { + "$ref": "#/components/parameters/AppCodeHeader" + } + ], "get": { "operationId": "listPopups", "responses": { @@ -1280,6 +1305,11 @@ } }, "/admin/app-config/popups/{popup_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/AppCodeHeader" + } + ], "put": { "operationId": "updatePopup", "responses": { @@ -1436,6 +1466,11 @@ } }, "/admin/app-config/explore-tabs": { + "parameters": [ + { + "$ref": "#/components/parameters/AppCodeHeader" + } + ], "get": { "operationId": "listExploreTabs", "responses": { @@ -1458,6 +1493,11 @@ } }, "/admin/app-config/explore-tabs/{tab_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/AppCodeHeader" + } + ], "put": { "operationId": "updateExploreTab", "responses": { @@ -1500,6 +1540,11 @@ } }, "/admin/app-config/versions": { + "parameters": [ + { + "$ref": "#/components/parameters/AppCodeHeader" + } + ], "get": { "operationId": "listAppVersions", "responses": { @@ -1522,6 +1567,11 @@ } }, "/admin/app-config/versions/{version_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/AppCodeHeader" + } + ], "put": { "operationId": "updateAppVersion", "responses": { diff --git a/src/features/app-config/api.test.ts b/src/features/app-config/api.test.ts index 01f347e..953ee1b 100644 --- a/src/features/app-config/api.test.ts +++ b/src/features/app-config/api.test.ts @@ -1,12 +1,32 @@ import { afterEach, expect, test, vi } from "vitest"; import { setAccessToken, setSelectedAppCode } from "@/shared/api/request"; import { + createAppVersion, + createBanner, + createExploreTab, createH5Link, + createPopup, + createSplashScreen, createSystemMessagePushFanout, + deleteAppVersion, + deleteBanner, + deleteExploreTab, deleteH5Link, + deletePopup, + deleteSplashScreen, + listAppVersions, + listBanners, + listExploreTabs, listH5Links, + listPopups, + listSplashScreens, + updateAppVersion, + updateBanner, + updateExploreTab, updateH5Link, updateH5Links, + updatePopup, + updateSplashScreen, } from "./api"; afterEach(() => { @@ -56,6 +76,52 @@ test("H5 config API pins every request to its explicit app scope", async () => { expect(JSON.parse(String(calls[3][1]?.body))).toEqual(payload); }); +test("every APP config API pins reads and writes to the explicit app scope", async () => { + setSelectedAppCode("lalu"); + vi.stubGlobal( + "fetch", + vi.fn(async (_url, init) => { + const method = String(init?.method || "GET"); + const data = method === "GET" ? { items: [], total: 0 } : method === "DELETE" ? { deleted: true } : {}; + return new Response(JSON.stringify({ code: 0, data }), { status: method === "POST" ? 201 : 200 }); + }), + ); + + const appCode = " Huwaa "; + const banner = { bannerType: "h5", coverUrl: "https://cdn.example.com/banner.png", platform: "android", status: "active" } as const; + const splash = { coverUrl: "https://cdn.example.com/splash.png", platform: "android", splashType: "h5", status: "active" } as const; + const popup = { code: "welcome", jumpType: "h5", jumpUrl: "https://h5.example.com/welcome", name: "Welcome", status: "active" } as const; + const explore = { enabled: true, h5Url: "https://h5.example.com/explore", tab: "Explore" }; + const version = { buildNumber: 10, downloadUrl: "https://cdn.example.com/app.apk", forceUpdate: false, platform: "android", version: "1.0.0" } as const; + + await listBanners(appCode); + await createBanner(appCode, banner); + await updateBanner(appCode, 1, banner); + await deleteBanner(appCode, 1); + await listSplashScreens(appCode); + await createSplashScreen(appCode, splash); + await updateSplashScreen(appCode, 2, splash); + await deleteSplashScreen(appCode, 2); + await listPopups(appCode); + await createPopup(appCode, popup); + await updatePopup(appCode, 3, popup); + await deletePopup(appCode, 3); + await listExploreTabs(appCode); + await createExploreTab(appCode, explore); + await updateExploreTab(appCode, 4, explore); + await deleteExploreTab(appCode, 4); + await listAppVersions(appCode); + await createAppVersion(appCode, version); + await updateAppVersion(appCode, 5, version); + await deleteAppVersion(appCode, 5); + + const calls = vi.mocked(fetch).mock.calls; + expect(calls).toHaveLength(20); + calls.forEach(([, init]) => { + expect(init?.headers).toMatchObject({ "X-App-Code": "huwaa" }); + }); +}); + test("system message push fanout API keeps existing operations path", async () => { vi.stubGlobal( "fetch", diff --git a/src/features/app-config/api.ts b/src/features/app-config/api.ts index 643b82c..c42cfb6 100644 --- a/src/features/app-config/api.ts +++ b/src/features/app-config/api.ts @@ -100,181 +100,201 @@ export function deleteH5Link(appCode: string, key: EntityId): Promise<{ deleted: } function appScopeHeaders(appCode: string) { - // H5 配置是强 App 隔离数据;显式固定请求头可避免用户切换 App 时全局请求上下文变化导致写入错误租户。 + // APP 配置全部是强 App 隔离数据;显式固定请求头可避免切换 App 时全局请求上下文变化导致写入错误租户。 return { "X-App-Code": String(appCode || "").trim().toLowerCase() }; } -export function listExploreTabs(query: PageQuery = {}): Promise> { +export function listExploreTabs(appCode: string, query: PageQuery = {}): Promise> { const endpoint = API_ENDPOINTS.listExploreTabs; return apiRequest>(apiEndpointPath(API_OPERATIONS.listExploreTabs), { + headers: appScopeHeaders(appCode), method: endpoint.method, query, }); } -export function createExploreTab(payload: ExploreTabPayload): Promise { +export function createExploreTab(appCode: string, payload: ExploreTabPayload): Promise { const endpoint = API_ENDPOINTS.createExploreTab; return apiRequest(apiEndpointPath(API_OPERATIONS.createExploreTab), { body: payload, + headers: appScopeHeaders(appCode), method: endpoint.method, }); } -export function updateExploreTab(tabId: EntityId, payload: ExploreTabPayload): Promise { +export function updateExploreTab(appCode: string, tabId: EntityId, payload: ExploreTabPayload): Promise { const endpoint = API_ENDPOINTS.updateExploreTab; return apiRequest( apiEndpointPath(API_OPERATIONS.updateExploreTab, { tab_id: tabId }), { body: payload, + headers: appScopeHeaders(appCode), method: endpoint.method, }, ); } -export function deleteExploreTab(tabId: EntityId): Promise<{ deleted: boolean }> { +export function deleteExploreTab(appCode: string, tabId: EntityId): Promise<{ deleted: boolean }> { const endpoint = API_ENDPOINTS.deleteExploreTab; return apiRequest<{ deleted: boolean }>(apiEndpointPath(API_OPERATIONS.deleteExploreTab, { tab_id: tabId }), { + headers: appScopeHeaders(appCode), method: endpoint.method, }); } -export function listBanners(query: PageQuery = {}): Promise> { +export function listBanners(appCode: string, query: PageQuery = {}): Promise> { const endpoint = API_ENDPOINTS.listBanners; return apiRequest>(apiEndpointPath(API_OPERATIONS.listBanners), { + headers: appScopeHeaders(appCode), method: endpoint.method, query, }); } -export function createBanner(payload: AppBannerPayload): Promise { +export function createBanner(appCode: string, payload: AppBannerPayload): Promise { const endpoint = API_ENDPOINTS.createBanner; return apiRequest(apiEndpointPath(API_OPERATIONS.createBanner), { body: payload, + headers: appScopeHeaders(appCode), method: endpoint.method, }); } -export function updateBanner(bannerId: EntityId, payload: AppBannerPayload): Promise { +export function updateBanner(appCode: string, bannerId: EntityId, payload: AppBannerPayload): Promise { const endpoint = API_ENDPOINTS.updateBanner; return apiRequest( apiEndpointPath(API_OPERATIONS.updateBanner, { banner_id: bannerId }), { body: payload, + headers: appScopeHeaders(appCode), method: endpoint.method, }, ); } -export function deleteBanner(bannerId: EntityId): Promise<{ deleted: boolean }> { +export function deleteBanner(appCode: string, bannerId: EntityId): Promise<{ deleted: boolean }> { const endpoint = API_ENDPOINTS.deleteBanner; return apiRequest<{ deleted: boolean }>(apiEndpointPath(API_OPERATIONS.deleteBanner, { banner_id: bannerId }), { + headers: appScopeHeaders(appCode), method: endpoint.method, }); } -export function listSplashScreens(query: PageQuery = {}): Promise> { +export function listSplashScreens(appCode: string, query: PageQuery = {}): Promise> { const endpoint = API_ENDPOINTS.listSplashScreens; return apiRequest>(apiEndpointPath(API_OPERATIONS.listSplashScreens), { + headers: appScopeHeaders(appCode), method: endpoint.method, query, }); } -export function createSplashScreen(payload: AppSplashScreenPayload): Promise { +export function createSplashScreen(appCode: string, payload: AppSplashScreenPayload): Promise { const endpoint = API_ENDPOINTS.createSplashScreen; return apiRequest(apiEndpointPath(API_OPERATIONS.createSplashScreen), { body: payload, + headers: appScopeHeaders(appCode), method: endpoint.method, }); } -export function updateSplashScreen(splashId: EntityId, payload: AppSplashScreenPayload): Promise { +export function updateSplashScreen(appCode: string, splashId: EntityId, payload: AppSplashScreenPayload): Promise { const endpoint = API_ENDPOINTS.updateSplashScreen; return apiRequest( apiEndpointPath(API_OPERATIONS.updateSplashScreen, { splash_id: splashId }), { body: payload, + headers: appScopeHeaders(appCode), method: endpoint.method, }, ); } -export function deleteSplashScreen(splashId: EntityId): Promise<{ deleted: boolean }> { +export function deleteSplashScreen(appCode: string, splashId: EntityId): Promise<{ deleted: boolean }> { const endpoint = API_ENDPOINTS.deleteSplashScreen; return apiRequest<{ deleted: boolean }>( apiEndpointPath(API_OPERATIONS.deleteSplashScreen, { splash_id: splashId }), { + headers: appScopeHeaders(appCode), method: endpoint.method, }, ); } -export function listPopups(query: PageQuery = {}): Promise> { +export function listPopups(appCode: string, query: PageQuery = {}): Promise> { const endpoint = API_ENDPOINTS.listPopups; return apiRequest>(apiEndpointPath(API_OPERATIONS.listPopups), { + headers: appScopeHeaders(appCode), method: endpoint.method, query, }); } -export function createPopup(payload: AppPopupPayload): Promise { +export function createPopup(appCode: string, payload: AppPopupPayload): Promise { const endpoint = API_ENDPOINTS.createPopup; return apiRequest(apiEndpointPath(API_OPERATIONS.createPopup), { body: payload, + headers: appScopeHeaders(appCode), method: endpoint.method, }); } -export function updatePopup(popupId: EntityId, payload: AppPopupPayload): Promise { +export function updatePopup(appCode: string, popupId: EntityId, payload: AppPopupPayload): Promise { const endpoint = API_ENDPOINTS.updatePopup; return apiRequest( apiEndpointPath(API_OPERATIONS.updatePopup, { popup_id: popupId }), { body: payload, + headers: appScopeHeaders(appCode), method: endpoint.method, }, ); } -export function deletePopup(popupId: EntityId): Promise<{ deleted: boolean }> { +export function deletePopup(appCode: string, popupId: EntityId): Promise<{ deleted: boolean }> { const endpoint = API_ENDPOINTS.deletePopup; return apiRequest<{ deleted: boolean }>(apiEndpointPath(API_OPERATIONS.deletePopup, { popup_id: popupId }), { + headers: appScopeHeaders(appCode), method: endpoint.method, }); } -export function listAppVersions(query: PageQuery = {}): Promise> { +export function listAppVersions(appCode: string, query: PageQuery = {}): Promise> { const endpoint = API_ENDPOINTS.listAppVersions; return apiRequest>(apiEndpointPath(API_OPERATIONS.listAppVersions), { + headers: appScopeHeaders(appCode), method: endpoint.method, query, }); } -export function createAppVersion(payload: AppVersionPayload): Promise { +export function createAppVersion(appCode: string, payload: AppVersionPayload): Promise { const endpoint = API_ENDPOINTS.createAppVersion; return apiRequest(apiEndpointPath(API_OPERATIONS.createAppVersion), { body: payload, + headers: appScopeHeaders(appCode), method: endpoint.method, }); } -export function updateAppVersion(versionId: EntityId, payload: AppVersionPayload): Promise { +export function updateAppVersion(appCode: string, versionId: EntityId, payload: AppVersionPayload): Promise { const endpoint = API_ENDPOINTS.updateAppVersion; return apiRequest( apiEndpointPath(API_OPERATIONS.updateAppVersion, { version_id: versionId }), { body: payload, + headers: appScopeHeaders(appCode), method: endpoint.method, }, ); } -export function deleteAppVersion(versionId: EntityId): Promise<{ deleted: boolean }> { +export function deleteAppVersion(appCode: string, versionId: EntityId): Promise<{ deleted: boolean }> { const endpoint = API_ENDPOINTS.deleteAppVersion; return apiRequest<{ deleted: boolean }>( apiEndpointPath(API_OPERATIONS.deleteAppVersion, { version_id: versionId }), { + headers: appScopeHeaders(appCode), method: endpoint.method, }, ); diff --git a/src/features/app-config/hooks/useAppVersionsPage.js b/src/features/app-config/hooks/useAppVersionsPage.js index 1d5dbda..fdc8186 100644 --- a/src/features/app-config/hooks/useAppVersionsPage.js +++ b/src/features/app-config/hooks/useAppVersionsPage.js @@ -1,4 +1,5 @@ -import { useCallback, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useAppScope } from "@/app/app-scope/AppScopeProvider.jsx"; import { createAppVersion, deleteAppVersion, listAppVersions, updateAppVersion } from "@/features/app-config/api"; import { useAppConfigAbilities } from "@/features/app-config/permissions.js"; import { appVersionSchema } from "@/features/app-config/schema"; @@ -19,6 +20,7 @@ const emptyForm = () => ({ }); export function useAppVersionsPage() { + const { appCode } = useAppScope(); const abilities = useAppConfigAbilities(); const confirm = useConfirm(); const { showToast } = useToast(); @@ -30,18 +32,26 @@ export function useAppVersionsPage() { const [loadingAction, setLoadingAction] = useState(""); const filters = useMemo(() => ({ keyword, platform }), [keyword, platform]); - const queryFn = useCallback(() => listAppVersions(filters), [filters]); + const queryFn = useCallback(() => listAppVersions(appCode, filters), [appCode, filters]); const { data = emptyData, error, loading, reload } = useAdminQuery(queryFn, { + enabled: Boolean(appCode), errorMessage: "加载版本配置失败", initialData: emptyData, - queryKey: ["app-config", "versions", filters] + queryKey: ["app-config", "versions", appCode, filters] }); + useEffect(() => { + // 安装包和强更策略必须按 App 隔离;切换 App 后清除旧版本编辑态,避免发布到错误租户。 + setActiveAction(""); + setEditingItem(null); + setFormState(emptyForm()); + }, [appCode]); + const setForm = (patch) => { setFormState((current) => ({ ...current, ...patch })); }; @@ -76,10 +86,10 @@ export function useAppVersionsPage() { setLoadingAction(action); try { if (editingItem) { - await updateAppVersion(editingItem.id, payload); + await updateAppVersion(appCode, editingItem.id, payload); showToast("版本配置已更新", "success"); } else { - await createAppVersion(payload); + await createAppVersion(appCode, payload); showToast("版本配置已新增", "success"); } closeDialog(); @@ -103,7 +113,7 @@ export function useAppVersionsPage() { } setLoadingAction(`delete:${item.id}`); try { - await deleteAppVersion(item.id); + await deleteAppVersion(appCode, item.id); await reload(); showToast("版本配置已删除", "success"); } catch (err) { diff --git a/src/features/app-config/hooks/useBannerConfigPage.js b/src/features/app-config/hooks/useBannerConfigPage.js index 7a4c92f..8cb8036 100644 --- a/src/features/app-config/hooks/useBannerConfigPage.js +++ b/src/features/app-config/hooks/useBannerConfigPage.js @@ -1,4 +1,5 @@ -import { useCallback, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useAppScope } from "@/app/app-scope/AppScopeProvider.jsx"; import { createBanner, deleteBanner, listBanners, updateBanner } from "@/features/app-config/api"; import { useAppConfigAbilities } from "@/features/app-config/permissions.js"; import { appBannerSchema } from "@/features/app-config/schema"; @@ -28,6 +29,7 @@ const emptyForm = () => ({ }); export function useBannerConfigPage() { + const { appCode } = useAppScope(); const abilities = useAppConfigAbilities(); const confirm = useConfirm(); const { showToast } = useToast(); @@ -55,18 +57,26 @@ export function useBannerConfigPage() { }), [countryCode, displayScope, keyword, platform, regionId, status], ); - const queryFn = useCallback(() => listBanners(filters), [filters]); + const queryFn = useCallback(() => listBanners(appCode, filters), [appCode, filters]); const { data = emptyData, error, loading, reload, } = useAdminQuery(queryFn, { + enabled: Boolean(appCode), errorMessage: "加载 BANNER 配置失败", initialData: emptyData, - queryKey: ["app-config", "banners", filters], + queryKey: ["app-config", "banners", appCode, filters], }); + useEffect(() => { + // 切换 App 后丢弃旧租户表单;已发出的请求仍携带旧 appCode,只会回写旧租户缓存键。 + setActiveAction(""); + setEditingItem(null); + setFormState(emptyForm()); + }, [appCode]); + const setForm = (patch) => { setFormState((current) => ({ ...current, ...patch })); }; @@ -105,10 +115,10 @@ export function useBannerConfigPage() { setLoadingAction(action); try { if (editingItem) { - await updateBanner(editingItem.id, payload); + await updateBanner(appCode, editingItem.id, payload); showToast("BANNER 已更新", "success"); } else { - await createBanner(payload); + await createBanner(appCode, payload); showToast("BANNER 已新增", "success"); } closeDialog(); @@ -132,7 +142,7 @@ export function useBannerConfigPage() { } setLoadingAction(`delete:${item.id}`); try { - await deleteBanner(item.id); + await deleteBanner(appCode, item.id); await reload(); showToast("BANNER 已删除", "success"); } catch (err) { diff --git a/src/features/app-config/hooks/useExploreConfigPage.js b/src/features/app-config/hooks/useExploreConfigPage.js index 0d57f68..f98f065 100644 --- a/src/features/app-config/hooks/useExploreConfigPage.js +++ b/src/features/app-config/hooks/useExploreConfigPage.js @@ -1,4 +1,5 @@ -import { useCallback, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useAppScope } from "@/app/app-scope/AppScopeProvider.jsx"; import { createExploreTab, deleteExploreTab, listExploreTabs, updateExploreTab } from "@/features/app-config/api"; import { useAppConfigAbilities } from "@/features/app-config/permissions.js"; import { exploreTabSchema } from "@/features/app-config/schema"; @@ -17,6 +18,7 @@ const emptyForm = () => ({ }); export function useExploreConfigPage() { + const { appCode } = useAppScope(); const abilities = useAppConfigAbilities(); const confirm = useConfirm(); const { showToast } = useToast(); @@ -28,18 +30,26 @@ export function useExploreConfigPage() { const [loadingAction, setLoadingAction] = useState(""); const filters = useMemo(() => ({ enabled, keyword }), [enabled, keyword]); - const queryFn = useCallback(() => listExploreTabs(filters), [filters]); + const queryFn = useCallback(() => listExploreTabs(appCode, filters), [appCode, filters]); const { data = emptyData, error, loading, reload, } = useAdminQuery(queryFn, { + enabled: Boolean(appCode), errorMessage: "加载 Explore 配置失败", initialData: emptyData, - queryKey: ["app-config", "explore-tabs", filters], + queryKey: ["app-config", "explore-tabs", appCode, filters], }); + useEffect(() => { + // Explore tab 的文案和 H5 地址属于 App 品牌内容,切换 App 后不能保留旧租户编辑态。 + setActiveAction(""); + setEditingItem(null); + setFormState(emptyForm()); + }, [appCode]); + const setForm = (patch) => { setFormState((current) => ({ ...current, ...patch })); }; @@ -74,10 +84,10 @@ export function useExploreConfigPage() { setLoadingAction(action); try { if (editingItem) { - await updateExploreTab(editingItem.id, payload); + await updateExploreTab(appCode, editingItem.id, payload); showToast("Explore 配置已更新", "success"); } else { - await createExploreTab(payload); + await createExploreTab(appCode, payload); showToast("Explore 配置已新增", "success"); } closeDialog(); @@ -92,7 +102,7 @@ export function useExploreConfigPage() { const toggleExploreTab = async (item, nextEnabled) => { setLoadingAction(`toggle:${item.id}`); try { - await updateExploreTab(item.id, { + await updateExploreTab(appCode, item.id, { enabled: nextEnabled, h5Url: item.h5Url || "", sortOrder: item.sortOrder || 0, @@ -119,7 +129,7 @@ export function useExploreConfigPage() { } setLoadingAction(`delete:${item.id}`); try { - await deleteExploreTab(item.id); + await deleteExploreTab(appCode, item.id); await reload(); showToast("Explore 配置已删除", "success"); } catch (err) { diff --git a/src/features/app-config/hooks/usePopupConfigPage.js b/src/features/app-config/hooks/usePopupConfigPage.js index 2a4081a..c66656b 100644 --- a/src/features/app-config/hooks/usePopupConfigPage.js +++ b/src/features/app-config/hooks/usePopupConfigPage.js @@ -1,4 +1,5 @@ -import { useCallback, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useAppScope } from "@/app/app-scope/AppScopeProvider.jsx"; import { createPopup, deletePopup, listPopups, updatePopup } from "@/features/app-config/api"; import { useAppConfigAbilities } from "@/features/app-config/permissions.js"; import { appPopupSchema } from "@/features/app-config/schema"; @@ -23,6 +24,7 @@ const emptyForm = () => ({ }); export function usePopupConfigPage() { + const { appCode } = useAppScope(); const abilities = useAppConfigAbilities(); const confirm = useConfirm(); const { showToast } = useToast(); @@ -40,18 +42,26 @@ export function usePopupConfigPage() { }), [keyword, status], ); - const queryFn = useCallback(() => listPopups(filters), [filters]); + const queryFn = useCallback(() => listPopups(appCode, filters), [appCode, filters]); const { data = emptyData, error, loading, reload, } = useAdminQuery(queryFn, { + enabled: Boolean(appCode), errorMessage: "加载弹窗配置失败", initialData: emptyData, - queryKey: ["app-config", "popups", filters], + queryKey: ["app-config", "popups", appCode, filters], }); + useEffect(() => { + // 弹窗 code 只在单个 App 内唯一;切换时必须丢弃旧编辑态,不能把旧租户内容复制到新租户。 + setActiveAction(""); + setEditingItem(null); + setFormState(emptyForm()); + }, [appCode]); + const setForm = (patch) => { setFormState((current) => ({ ...current, ...patch })); }; @@ -86,10 +96,10 @@ export function usePopupConfigPage() { setLoadingAction(action); try { if (editingItem) { - await updatePopup(editingItem.id, payload); + await updatePopup(appCode, editingItem.id, payload); showToast("弹窗配置已更新", "success"); } else { - await createPopup(payload); + await createPopup(appCode, payload); showToast("弹窗配置已新增", "success"); } closeDialog(); @@ -113,7 +123,7 @@ export function usePopupConfigPage() { } setLoadingAction(`delete:${item.id}`); try { - await deletePopup(item.id); + await deletePopup(appCode, item.id); await reload(); showToast("弹窗配置已删除", "success"); } catch (err) { diff --git a/src/features/app-config/hooks/useSplashScreenConfigPage.js b/src/features/app-config/hooks/useSplashScreenConfigPage.js index 3f1c7a4..dd3cf6e 100644 --- a/src/features/app-config/hooks/useSplashScreenConfigPage.js +++ b/src/features/app-config/hooks/useSplashScreenConfigPage.js @@ -1,4 +1,5 @@ -import { useCallback, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useAppScope } from "@/app/app-scope/AppScopeProvider.jsx"; import { createSplashScreen, deleteSplashScreen, @@ -32,6 +33,7 @@ const emptyForm = () => ({ }); export function useSplashScreenConfigPage() { + const { appCode } = useAppScope(); const abilities = useAppConfigAbilities(); const confirm = useConfirm(); const { showToast } = useToast(); @@ -57,18 +59,26 @@ export function useSplashScreenConfigPage() { }), [countryCode, keyword, platform, regionId, status], ); - const queryFn = useCallback(() => listSplashScreens(filters), [filters]); + const queryFn = useCallback(() => listSplashScreens(appCode, filters), [appCode, filters]); const { data = emptyData, error, loading, reload, } = useAdminQuery(queryFn, { + enabled: Boolean(appCode), errorMessage: "加载开屏配置失败", initialData: emptyData, - queryKey: ["app-config", "splash-screens", filters], + queryKey: ["app-config", "splash-screens", appCode, filters], }); + useEffect(() => { + // 开屏配置按 App 独立;切换时关闭旧表单,避免旧图片和投放条件被带到新 App。 + setActiveAction(""); + setEditingItem(null); + setFormState(emptyForm()); + }, [appCode]); + const setForm = (patch) => { setFormState((current) => ({ ...current, ...patch })); }; @@ -106,10 +116,10 @@ export function useSplashScreenConfigPage() { setLoadingAction(action); try { if (editingItem) { - await updateSplashScreen(editingItem.id, payload); + await updateSplashScreen(appCode, editingItem.id, payload); showToast("开屏配置已更新", "success"); } else { - await createSplashScreen(payload); + await createSplashScreen(appCode, payload); showToast("开屏配置已新增", "success"); } closeDialog(); @@ -133,7 +143,7 @@ export function useSplashScreenConfigPage() { } setLoadingAction(`delete:${item.id}`); try { - await deleteSplashScreen(item.id); + await deleteSplashScreen(appCode, item.id); await reload(); showToast("开屏配置已删除", "success"); } catch (err) { diff --git a/src/shared/api/generated/schema.d.ts b/src/shared/api/generated/schema.d.ts index 25affac..cd0aa52 100644 --- a/src/shared/api/generated/schema.d.ts +++ b/src/shared/api/generated/schema.d.ts @@ -855,7 +855,9 @@ export interface paths { "/admin/app-config/banners": { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -871,7 +873,9 @@ export interface paths { "/admin/app-config/banners/{banner_id}": { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -887,7 +891,9 @@ export interface paths { "/admin/app-config/splash-screens": { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -903,7 +909,9 @@ export interface paths { "/admin/app-config/splash-screens/{splash_id}": { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -919,7 +927,9 @@ export interface paths { "/admin/app-config/popups": { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -935,7 +945,9 @@ export interface paths { "/admin/app-config/popups/{popup_id}": { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -987,7 +999,9 @@ export interface paths { "/admin/app-config/explore-tabs": { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -1003,7 +1017,9 @@ export interface paths { "/admin/app-config/explore-tabs/{tab_id}": { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -1019,7 +1035,9 @@ export interface paths { "/admin/app-config/versions": { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -1035,7 +1053,9 @@ export interface paths { "/admin/app-config/versions/{version_id}": { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -5725,7 +5745,11 @@ export interface components { providerStatus?: string; providerPayType?: string; receiveAddress?: string; - orderDate?: string; + /** + * Format: int64 + * @description 订单业务时间;历史订单为 0 时回退到 createdAtMs + */ + orderDateMs?: number; status: string; verifyStatus: string; grantStatus: string; @@ -5783,6 +5807,11 @@ export interface components { usdAmount: number; /** @description 补单时为 true;订单正常计入充值,但服务端强制金币数为 0 */ isMakeup?: boolean; + /** + * Format: int64 + * @description 订单业务时间;未传时服务端默认为当前时间 + */ + orderDateMs?: number; /** @enum {string} */ providerCode: "mifapay" | "v5pay" | "usdt"; externalOrderNo: string; @@ -5860,6 +5889,11 @@ export interface components { chain?: "TRON" | "BSC"; usdAmount?: number; usdMinorAmount?: number; + /** + * Format: int64 + * @description 订单业务时间,用于三方凭证按日期查询 + */ + orderDateMs?: number; providerCurrencyCode?: string; providerAmountMinor?: number; }; @@ -7570,7 +7604,9 @@ export interface operations { listBanners: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -7582,7 +7618,9 @@ export interface operations { createBanner: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -7594,7 +7632,9 @@ export interface operations { updateBanner: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path: { banner_id: number; }; @@ -7608,7 +7648,9 @@ export interface operations { deleteBanner: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path: { banner_id: number; }; @@ -7622,7 +7664,9 @@ export interface operations { listSplashScreens: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -7634,7 +7678,9 @@ export interface operations { createSplashScreen: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -7646,7 +7692,9 @@ export interface operations { updateSplashScreen: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path: { splash_id: number; }; @@ -7660,7 +7708,9 @@ export interface operations { deleteSplashScreen: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path: { splash_id: number; }; @@ -7674,7 +7724,9 @@ export interface operations { listPopups: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -7686,7 +7738,9 @@ export interface operations { createPopup: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -7698,7 +7752,9 @@ export interface operations { updatePopup: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path: { popup_id: number; }; @@ -7712,7 +7768,9 @@ export interface operations { deletePopup: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path: { popup_id: number; }; @@ -7812,7 +7870,9 @@ export interface operations { listExploreTabs: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -7824,7 +7884,9 @@ export interface operations { createExploreTab: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -7836,7 +7898,9 @@ export interface operations { updateExploreTab: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path: { tab_id: number; }; @@ -7850,7 +7914,9 @@ export interface operations { deleteExploreTab: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path: { tab_id: number; }; @@ -7864,7 +7930,9 @@ export interface operations { listAppVersions: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -7876,7 +7944,9 @@ export interface operations { createAppVersion: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -7888,7 +7958,9 @@ export interface operations { updateAppVersion: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path: { version_id: number; }; @@ -7902,7 +7974,9 @@ export interface operations { deleteAppVersion: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path: { version_id: number; };