From 9065382192f4306f7e3d06ae5d5ad669cacf63e9 Mon Sep 17 00:00:00 2001 From: zhx Date: Fri, 10 Jul 2026 20:20:58 +0800 Subject: [PATCH] fix: scope H5 config by app code --- contracts/admin-openapi.json | 228 +++++++++++++++++- src/features/app-config/api.test.ts | 53 +++- src/features/app-config/api.ts | 20 +- .../app-config/hooks/useH5ConfigPage.js | 59 ++++- .../app-config/hooks/useH5ConfigPage.test.jsx | 179 ++++++++++++++ src/shared/api/generated/schema.d.ts | 116 +++++++-- src/shared/api/types.ts | 1 + 7 files changed, 620 insertions(+), 36 deletions(-) create mode 100644 src/features/app-config/hooks/useH5ConfigPage.test.jsx diff --git a/contracts/admin-openapi.json b/contracts/admin-openapi.json index 8900b6d..b55bfa4 100644 --- a/contracts/admin-openapi.json +++ b/contracts/admin-openapi.json @@ -1214,11 +1214,16 @@ } }, "/admin/app-config/h5-links": { + "parameters": [ + { + "$ref": "#/components/parameters/AppCodeHeader" + } + ], "get": { "operationId": "listH5Links", "responses": { "200": { - "$ref": "#/components/responses/EmptyResponse" + "$ref": "#/components/responses/H5LinkListResponse" } }, "x-permission": "app-config:view", @@ -1226,9 +1231,19 @@ }, "post": { "operationId": "createH5Link", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/H5LinkPayload" + } + } + } + }, "responses": { - "200": { - "$ref": "#/components/responses/EmptyResponse" + "201": { + "$ref": "#/components/responses/H5LinkResponse" } }, "x-permission": "app-config:update", @@ -1236,9 +1251,19 @@ }, "put": { "operationId": "updateH5Links", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/H5LinkBatchUpdateInput" + } + } + } + }, "responses": { "200": { - "$ref": "#/components/responses/EmptyResponse" + "$ref": "#/components/responses/H5LinkListResponse" } }, "x-permission": "app-config:update", @@ -1246,11 +1271,26 @@ } }, "/admin/app-config/h5-links/{key}": { + "parameters": [ + { + "$ref": "#/components/parameters/AppCodeHeader" + } + ], "put": { "operationId": "updateH5Link", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/H5LinkPayload" + } + } + } + }, "responses": { "200": { - "$ref": "#/components/responses/EmptyResponse" + "$ref": "#/components/responses/H5LinkResponse" } }, "parameters": [ @@ -1270,7 +1310,7 @@ "operationId": "deleteH5Link", "responses": { "200": { - "$ref": "#/components/responses/EmptyResponse" + "$ref": "#/components/responses/H5LinkDeleteResponse" } }, "parameters": [ @@ -6729,6 +6769,15 @@ }, "components": { "parameters": { + "AppCodeHeader": { + "name": "X-App-Code", + "in": "header", + "required": true, + "schema": { + "type": "string", + "minLength": 1 + } + }, "AgencyId": { "name": "agency_id", "in": "query", @@ -7167,6 +7216,36 @@ } }, "responses": { + "H5LinkListResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseH5LinkList" + } + } + } + }, + "H5LinkResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseH5Link" + } + } + } + }, + "H5LinkDeleteResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseH5LinkDelete" + } + } + } + }, "AgencyPageResponse": { "description": "OK", "content": { @@ -7609,6 +7688,143 @@ } }, "schemas": { + "ApiResponseH5LinkList": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/H5LinkList" + } + } + } + ] + }, + "ApiResponseH5Link": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/H5Link" + } + } + } + ] + }, + "ApiResponseH5LinkDelete": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/H5LinkDeleteResult" + } + } + } + ] + }, + "H5Link": { + "type": "object", + "required": ["appCode", "key", "label", "url", "updatedAtMs"], + "properties": { + "appCode": { + "type": "string", + "minLength": 1 + }, + "key": { + "type": "string", + "minLength": 1, + "maxLength": 80, + "pattern": "^[A-Za-z0-9_.:-]+$" + }, + "label": { + "type": "string", + "minLength": 1, + "maxLength": 80 + }, + "url": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "pattern": "^\\S+$" + }, + "updatedAtMs": { + "type": "integer", + "format": "int64" + } + } + }, + "H5LinkPayload": { + "type": "object", + "required": ["key", "label", "url"], + "properties": { + "key": { + "type": "string", + "minLength": 1, + "maxLength": 80, + "pattern": "^[A-Za-z0-9_.:-]+$" + }, + "label": { + "type": "string", + "minLength": 1, + "maxLength": 80 + }, + "url": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "pattern": "^\\S+$" + } + } + }, + "H5LinkBatchUpdateInput": { + "type": "object", + "required": ["items"], + "properties": { + "items": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/components/schemas/H5LinkPayload" + } + } + } + }, + "H5LinkList": { + "type": "object", + "required": ["items", "total"], + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/H5Link" + } + }, + "total": { + "type": "integer", + "minimum": 0 + } + } + }, + "H5LinkDeleteResult": { + "type": "object", + "required": ["deleted"], + "properties": { + "deleted": { + "type": "boolean" + } + } + }, "ApiPageAgency": { "type": "object", "required": ["items", "page", "pageSize", "total"], diff --git a/src/features/app-config/api.test.ts b/src/features/app-config/api.test.ts index e1bd8a7..01f347e 100644 --- a/src/features/app-config/api.test.ts +++ b/src/features/app-config/api.test.ts @@ -1,12 +1,61 @@ import { afterEach, expect, test, vi } from "vitest"; -import { setAccessToken } from "@/shared/api/request"; -import { createSystemMessagePushFanout } from "./api"; +import { setAccessToken, setSelectedAppCode } from "@/shared/api/request"; +import { + createH5Link, + createSystemMessagePushFanout, + deleteH5Link, + listH5Links, + updateH5Link, + updateH5Links, +} from "./api"; afterEach(() => { setAccessToken(""); + setSelectedAppCode("lalu"); vi.unstubAllGlobals(); }); +test("H5 config API pins every request to its explicit app scope", async () => { + setSelectedAppCode("lalu"); + const link = { + appCode: "huwaa", + key: "achievement", + label: "成就", + updatedAtMs: 1000, + url: "https://h5.example.com/achievement", + }; + vi.stubGlobal( + "fetch", + vi.fn(async (_url, init) => { + const method = String(init?.method || "GET"); + const data = method === "GET" || (method === "PUT" && !String(_url).endsWith("/achievement")) + ? { items: [link], total: 1 } + : method === "DELETE" + ? { deleted: true } + : link; + return new Response(JSON.stringify({ code: 0, data }), { status: method === "POST" ? 201 : 200 }); + }), + ); + + const payload = { key: link.key, label: link.label, url: link.url }; + await listH5Links(" Huwaa "); + await createH5Link("huwaa", payload); + await updateH5Links("huwaa", { items: [payload] }); + await updateH5Link("huwaa", link.key, payload); + await deleteH5Link("huwaa", link.key); + + const calls = vi.mocked(fetch).mock.calls; + expect(calls.map(([, init]) => init?.method)).toEqual(["GET", "POST", "PUT", "PUT", "DELETE"]); + calls.forEach(([, init]) => { + expect(init?.headers).toMatchObject({ "X-App-Code": "huwaa" }); + }); + expect(String(calls[0][0])).toContain("/api/v1/admin/app-config/h5-links"); + expect(String(calls[3][0])).toContain("/api/v1/admin/app-config/h5-links/achievement"); + expect(JSON.parse(String(calls[1][1]?.body))).toEqual(payload); + expect(JSON.parse(String(calls[2][1]?.body))).toEqual({ items: [payload] }); + expect(JSON.parse(String(calls[3][1]?.body))).toEqual(payload); +}); + 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 d89c9e9..643b82c 100644 --- a/src/features/app-config/api.ts +++ b/src/features/app-config/api.ts @@ -53,47 +53,57 @@ export function createSystemMessagePushFanout(payload: SystemMessagePushPayload) ); } -export function listH5Links(): Promise> { +export function listH5Links(appCode: string): Promise> { const endpoint = API_ENDPOINTS.listH5Links; return apiRequest>(apiEndpointPath(API_OPERATIONS.listH5Links), { + headers: appScopeHeaders(appCode), method: endpoint.method, }); } -export function updateH5Links(payload: H5LinkConfigUpdatePayload): Promise> { +export function updateH5Links(appCode: string, payload: H5LinkConfigUpdatePayload): Promise> { const endpoint = API_ENDPOINTS.updateH5Links; return apiRequest, H5LinkConfigUpdatePayload>( apiEndpointPath(API_OPERATIONS.updateH5Links), { body: payload, + headers: appScopeHeaders(appCode), method: endpoint.method, }, ); } -export function createH5Link(payload: H5LinkConfigPayload): Promise { +export function createH5Link(appCode: string, payload: H5LinkConfigPayload): Promise { const endpoint = API_ENDPOINTS.createH5Link; return apiRequest(apiEndpointPath(API_OPERATIONS.createH5Link), { body: payload, + headers: appScopeHeaders(appCode), method: endpoint.method, }); } -export function updateH5Link(key: EntityId, payload: H5LinkConfigPayload): Promise { +export function updateH5Link(appCode: string, key: EntityId, payload: H5LinkConfigPayload): Promise { const endpoint = API_ENDPOINTS.updateH5Link; return apiRequest(apiEndpointPath(API_OPERATIONS.updateH5Link, { key }), { body: payload, + headers: appScopeHeaders(appCode), method: endpoint.method, }); } -export function deleteH5Link(key: EntityId): Promise<{ deleted: boolean }> { +export function deleteH5Link(appCode: string, key: EntityId): Promise<{ deleted: boolean }> { const endpoint = API_ENDPOINTS.deleteH5Link; return apiRequest<{ deleted: boolean }>(apiEndpointPath(API_OPERATIONS.deleteH5Link, { key }), { + headers: appScopeHeaders(appCode), method: endpoint.method, }); } +function appScopeHeaders(appCode: string) { + // H5 配置是强 App 隔离数据;显式固定请求头可避免用户切换 App 时全局请求上下文变化导致写入错误租户。 + return { "X-App-Code": String(appCode || "").trim().toLowerCase() }; +} + export function listExploreTabs(query: PageQuery = {}): Promise> { const endpoint = API_ENDPOINTS.listExploreTabs; return apiRequest>(apiEndpointPath(API_OPERATIONS.listExploreTabs), { diff --git a/src/features/app-config/hooks/useH5ConfigPage.js b/src/features/app-config/hooks/useH5ConfigPage.js index 05feb70..44768a8 100644 --- a/src/features/app-config/hooks/useH5ConfigPage.js +++ b/src/features/app-config/hooks/useH5ConfigPage.js @@ -1,4 +1,6 @@ -import { useCallback, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; +import { useAppScope } from "@/app/app-scope/AppScopeProvider.jsx"; +import { getSelectedAppCode } from "@/shared/api/request"; import { parseForm } from "@/shared/forms/validation"; import { useAdminQuery } from "@/shared/hooks/useAdminQuery.js"; import { useConfirm } from "@/shared/ui/ConfirmProvider.jsx"; @@ -11,45 +13,66 @@ const emptyData = { items: [], total: 0 }; const emptyForm = () => ({ key: "", label: "", url: "" }); export function useH5ConfigPage() { + const { appCode } = useAppScope(); const abilities = useAppConfigAbilities(); const confirm = useConfirm(); const { showToast } = useToast(); const [activeAction, setActiveAction] = useState(""); + const [actionAppCode, setActionAppCode] = useState(""); const [editingItem, setEditingItem] = useState(null); const [form, setForm] = useState(emptyForm); const [loadingAction, setLoadingAction] = useState(""); - const queryFn = useCallback(() => listH5Links(), []); + const queryFn = useCallback(() => listH5Links(appCode), [appCode]); const { data = emptyData, error, loading, reload } = useAdminQuery(queryFn, { + enabled: Boolean(appCode), errorMessage: "加载 H5 配置失败", initialData: emptyData, - queryKey: ["app-config", "h5-links"] + queryKey: ["app-config", "h5-links", appCode] }); const openCreate = () => { setEditingItem(null); setForm(emptyForm()); + setActionAppCode(appCode); setActiveAction("create"); }; const openEdit = (item) => { + const itemAppCode = normalizeAppCode(item.appCode) || appCode; setEditingItem(item); setForm({ key: item.key || "", label: item.label || "", url: item.url || "" }); + // 响应中的 appCode 是该行数据的归属凭证;若页面正在切换,effect 会发现它与当前 scope 不一致并关闭弹窗。 + setActionAppCode(itemAppCode); setActiveAction("edit"); }; - const closeEdit = () => { + const closeEdit = useCallback(() => { setActiveAction(""); + setActionAppCode(""); setEditingItem(null); setForm(emptyForm()); - }; + }, []); + + useEffect(() => { + if (!activeAction || sameAppCode(actionAppCode, appCode)) { + return; + } + // 弹窗内容来自旧 App;切换后必须丢弃,不能让同名 key 被保存到新 App。 + closeEdit(); + }, [activeAction, actionAppCode, appCode, closeEdit]); const submitEdit = async (event) => { event.preventDefault(); + if (!isCurrentActionScope(actionAppCode, appCode)) { + closeEdit(); + showToast("应用已切换,请重新操作", "warning"); + return; + } if (!editingItem) { const payload = parseForm(h5LinkUpdateSchema, form); setLoadingAction("create"); try { - await createH5Link(payload); + await createH5Link(actionAppCode, payload); closeEdit(); await reload(); showToast("H5配置已新增", "success"); @@ -63,7 +86,7 @@ export function useH5ConfigPage() { const payload = parseForm(h5LinkUpdateSchema, { ...form, key: editingItem.key }); setLoadingAction("edit"); try { - await updateH5Link(editingItem.key, payload); + await updateH5Link(actionAppCode, editingItem.key, payload); closeEdit(); await reload(); showToast("H5配置已更新", "success"); @@ -75,6 +98,7 @@ export function useH5ConfigPage() { }; const removeH5Link = async (item) => { + const targetAppCode = normalizeAppCode(item.appCode) || appCode; const ok = await confirm({ confirmText: "删除", message: item.label || item.key, @@ -84,9 +108,13 @@ export function useH5ConfigPage() { if (!ok) { return; } + if (!isCurrentActionScope(targetAppCode, appCode)) { + showToast("应用已切换,请重新操作", "warning"); + return; + } setLoadingAction(`delete:${item.key}`); try { - await deleteH5Link(item.key); + await deleteH5Link(targetAppCode, item.key); await reload(); showToast("H5配置已删除", "success"); } catch (err) { @@ -114,3 +142,18 @@ export function useH5ConfigPage() { submitEdit }; } + +function isCurrentActionScope(actionAppCode, appCode) { + // Header 的全局值在 AppScope state 提交前已更新,双重校验能挡住切换瞬间点击提交的竞态。 + return Boolean(normalizeAppCode(actionAppCode)) + && sameAppCode(actionAppCode, appCode) + && sameAppCode(actionAppCode, getSelectedAppCode()); +} + +function sameAppCode(left, right) { + return normalizeAppCode(left) === normalizeAppCode(right); +} + +function normalizeAppCode(value) { + return String(value || "").trim().toLowerCase(); +} diff --git a/src/features/app-config/hooks/useH5ConfigPage.test.jsx b/src/features/app-config/hooks/useH5ConfigPage.test.jsx new file mode 100644 index 0000000..707f866 --- /dev/null +++ b/src/features/app-config/hooks/useH5ConfigPage.test.jsx @@ -0,0 +1,179 @@ +import { act, renderHook } from "@testing-library/react"; +import { afterEach, beforeEach, expect, test, vi } from "vitest"; +import { setSelectedAppCode } from "@/shared/api/request"; + +const mocks = vi.hoisted(() => ({ + appCode: "lalu", + confirm: vi.fn(), + createH5Link: vi.fn(), + deleteH5Link: vi.fn(), + listH5Links: vi.fn(), + queryFn: null, + queryOptions: null, + reload: vi.fn(), + showToast: vi.fn(), + updateH5Link: vi.fn() +})); + +vi.mock("@/app/app-scope/AppScopeProvider.jsx", () => ({ + useAppScope: () => ({ appCode: mocks.appCode }) +})); + +vi.mock("@/features/app-config/api", () => ({ + createH5Link: mocks.createH5Link, + deleteH5Link: mocks.deleteH5Link, + listH5Links: mocks.listH5Links, + updateH5Link: mocks.updateH5Link +})); + +vi.mock("@/features/app-config/permissions.js", () => ({ + useAppConfigAbilities: () => ({ canUpdate: true, canView: true }) +})); + +vi.mock("@/shared/hooks/useAdminQuery.js", () => ({ + useAdminQuery: (queryFn, options) => { + mocks.queryFn = queryFn; + mocks.queryOptions = options; + return { + data: { items: [], total: 0 }, + error: "", + loading: false, + reload: mocks.reload + }; + } +})); + +vi.mock("@/shared/ui/ConfirmProvider.jsx", () => ({ + useConfirm: () => mocks.confirm +})); + +vi.mock("@/shared/ui/ToastProvider.jsx", () => ({ + useToast: () => ({ showToast: mocks.showToast }) +})); + +import { useH5ConfigPage } from "./useH5ConfigPage.js"; + +beforeEach(() => { + mocks.appCode = "lalu"; + mocks.confirm.mockResolvedValue(true); + mocks.createH5Link.mockResolvedValue({}); + mocks.deleteH5Link.mockResolvedValue({ deleted: true }); + mocks.listH5Links.mockResolvedValue({ items: [], total: 0 }); + mocks.reload.mockResolvedValue({ items: [], total: 0 }); + mocks.updateH5Link.mockResolvedValue({}); + setSelectedAppCode("lalu"); +}); + +afterEach(() => { + vi.clearAllMocks(); + setSelectedAppCode("lalu"); +}); + +test("uses appCode in the H5 query key and read request", async () => { + const { rerender } = renderHook(() => useH5ConfigPage()); + + expect(mocks.queryOptions.queryKey).toEqual(["app-config", "h5-links", "lalu"]); + await mocks.queryFn(); + expect(mocks.listH5Links).toHaveBeenLastCalledWith("lalu"); + + mocks.appCode = "huwaa"; + setSelectedAppCode("huwaa"); + rerender(); + + expect(mocks.queryOptions.queryKey).toEqual(["app-config", "h5-links", "huwaa"]); + await mocks.queryFn(); + expect(mocks.listH5Links).toHaveBeenLastCalledWith("huwaa"); +}); + +test("closes an edit dialog when the selected App changes", () => { + const item = { + appCode: "lalu", + key: "achievement", + label: "成就", + updatedAtMs: 1000, + url: "https://h5.example.com/achievement" + }; + const { result, rerender } = renderHook(() => useH5ConfigPage()); + + act(() => result.current.openEdit(item)); + expect(result.current.activeAction).toBe("edit"); + + mocks.appCode = "huwaa"; + setSelectedAppCode("huwaa"); + rerender(); + + expect(result.current.activeAction).toBe(""); + expect(result.current.editingItem).toBeNull(); + expect(result.current.form).toEqual({ key: "", label: "", url: "" }); +}); + +test("does not submit stale form state after an App switch", async () => { + const { result, rerender } = renderHook(() => useH5ConfigPage()); + + act(() => { + result.current.openEdit({ + appCode: "lalu", + key: "achievement", + label: "成就", + updatedAtMs: 1000, + url: "https://h5.example.com/achievement" + }); + }); + + mocks.appCode = "huwaa"; + setSelectedAppCode("huwaa"); + rerender(); + await act(async () => { + await result.current.submitEdit({ preventDefault: vi.fn() }); + }); + + expect(mocks.createH5Link).not.toHaveBeenCalled(); + expect(mocks.updateH5Link).not.toHaveBeenCalled(); + expect(mocks.showToast).toHaveBeenCalledWith("应用已切换,请重新操作", "warning"); +}); + +test("pins an update mutation to the App that owns the edited row", async () => { + const { result } = renderHook(() => useH5ConfigPage()); + + act(() => { + result.current.openEdit({ + appCode: "lalu", + key: "achievement", + label: "成就", + updatedAtMs: 1000, + url: "https://h5.example.com/achievement" + }); + }); + await act(async () => { + await result.current.submitEdit({ preventDefault: vi.fn() }); + }); + + expect(mocks.updateH5Link).toHaveBeenCalledWith("lalu", "achievement", { + key: "achievement", + label: "成就", + url: "https://h5.example.com/achievement" + }); +}); + +test("aborts delete when App changes while confirmation is open", async () => { + let resolveConfirm; + mocks.confirm.mockReturnValue(new Promise((resolve) => { + resolveConfirm = resolve; + })); + const { result, rerender } = renderHook(() => useH5ConfigPage()); + let pendingDelete; + + act(() => { + pendingDelete = result.current.removeH5Link({ key: "achievement", label: "成就" }); + }); + mocks.appCode = "huwaa"; + setSelectedAppCode("huwaa"); + rerender(); + await act(async () => { + resolveConfirm(true); + await pendingDelete; + }); + + expect(mocks.deleteH5Link).not.toHaveBeenCalled(); + expect(mocks.showToast).toHaveBeenCalledWith("应用已切换,请重新操作", "warning"); +}); diff --git a/src/shared/api/generated/schema.d.ts b/src/shared/api/generated/schema.d.ts index 4fdd3eb..c716d05 100644 --- a/src/shared/api/generated/schema.d.ts +++ b/src/shared/api/generated/schema.d.ts @@ -887,7 +887,9 @@ export interface paths { "/admin/app-config/h5-links": { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -903,7 +905,9 @@ export interface paths { "/admin/app-config/h5-links/{key}": { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; @@ -4136,6 +4140,38 @@ export interface paths { export type webhooks = Record; export interface components { schemas: { + ApiResponseH5LinkList: components["schemas"]["Envelope"] & { + data?: components["schemas"]["H5LinkList"]; + }; + ApiResponseH5Link: components["schemas"]["Envelope"] & { + data?: components["schemas"]["H5Link"]; + }; + ApiResponseH5LinkDelete: components["schemas"]["Envelope"] & { + data?: components["schemas"]["H5LinkDeleteResult"]; + }; + H5Link: { + appCode: string; + key: string; + label: string; + url: string; + /** Format: int64 */ + updatedAtMs: number; + }; + H5LinkPayload: { + key: string; + label: string; + url: string; + }; + H5LinkBatchUpdateInput: { + items: components["schemas"]["H5LinkPayload"][]; + }; + H5LinkList: { + items: components["schemas"]["H5Link"][]; + total: number; + }; + H5LinkDeleteResult: { + deleted: boolean; + }; ApiPageAgency: { items: components["schemas"]["Agency"][]; page: number; @@ -5143,6 +5179,33 @@ export interface components { }; }; responses: { + /** @description OK */ + H5LinkListResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseH5LinkList"]; + }; + }; + /** @description OK */ + H5LinkResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseH5Link"]; + }; + }; + /** @description OK */ + H5LinkDeleteResponse: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiResponseH5LinkDelete"]; + }; + }; /** @description OK */ AgencyPageResponse: { headers: { @@ -5541,6 +5604,7 @@ export interface components { }; }; parameters: { + AppCodeHeader: string; AgencyId: number; Id: number; Keyword: string; @@ -6766,57 +6830,79 @@ export interface operations { listH5Links: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; requestBody?: never; responses: { - 200: components["responses"]["EmptyResponse"]; + 200: components["responses"]["H5LinkListResponse"]; }; }; updateH5Links: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["H5LinkBatchUpdateInput"]; + }; + }; responses: { - 200: components["responses"]["EmptyResponse"]; + 200: components["responses"]["H5LinkListResponse"]; }; }; createH5Link: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path?: never; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["H5LinkPayload"]; + }; + }; responses: { - 200: components["responses"]["EmptyResponse"]; + 201: components["responses"]["H5LinkResponse"]; }; }; updateH5Link: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path: { key: string; }; cookie?: never; }; - requestBody?: never; + requestBody: { + content: { + "application/json": components["schemas"]["H5LinkPayload"]; + }; + }; responses: { - 200: components["responses"]["EmptyResponse"]; + 200: components["responses"]["H5LinkResponse"]; }; }; deleteH5Link: { parameters: { query?: never; - header?: never; + header: { + "X-App-Code": components["parameters"]["AppCodeHeader"]; + }; path: { key: string; }; @@ -6824,7 +6910,7 @@ export interface operations { }; requestBody?: never; responses: { - 200: components["responses"]["EmptyResponse"]; + 200: components["responses"]["H5LinkDeleteResponse"]; }; }; listExploreTabs: { diff --git a/src/shared/api/types.ts b/src/shared/api/types.ts index f9c627a..5f291e0 100644 --- a/src/shared/api/types.ts +++ b/src/shared/api/types.ts @@ -1506,6 +1506,7 @@ export interface AdminAppDto { } export interface H5LinkConfigDto { + appCode: string; key: string; label: string; updatedAtMs?: number;