fix: scope H5 config by app code

This commit is contained in:
zhx 2026-07-10 20:20:58 +08:00
parent ef260df749
commit 9065382192
7 changed files with 620 additions and 36 deletions

View File

@ -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"],

View File

@ -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",

View File

@ -53,47 +53,57 @@ export function createSystemMessagePushFanout(payload: SystemMessagePushPayload)
);
}
export function listH5Links(): Promise<ApiList<H5LinkConfigDto>> {
export function listH5Links(appCode: string): Promise<ApiList<H5LinkConfigDto>> {
const endpoint = API_ENDPOINTS.listH5Links;
return apiRequest<ApiList<H5LinkConfigDto>>(apiEndpointPath(API_OPERATIONS.listH5Links), {
headers: appScopeHeaders(appCode),
method: endpoint.method,
});
}
export function updateH5Links(payload: H5LinkConfigUpdatePayload): Promise<ApiList<H5LinkConfigDto>> {
export function updateH5Links(appCode: string, payload: H5LinkConfigUpdatePayload): Promise<ApiList<H5LinkConfigDto>> {
const endpoint = API_ENDPOINTS.updateH5Links;
return apiRequest<ApiList<H5LinkConfigDto>, H5LinkConfigUpdatePayload>(
apiEndpointPath(API_OPERATIONS.updateH5Links),
{
body: payload,
headers: appScopeHeaders(appCode),
method: endpoint.method,
},
);
}
export function createH5Link(payload: H5LinkConfigPayload): Promise<H5LinkConfigDto> {
export function createH5Link(appCode: string, payload: H5LinkConfigPayload): Promise<H5LinkConfigDto> {
const endpoint = API_ENDPOINTS.createH5Link;
return apiRequest<H5LinkConfigDto, H5LinkConfigPayload>(apiEndpointPath(API_OPERATIONS.createH5Link), {
body: payload,
headers: appScopeHeaders(appCode),
method: endpoint.method,
});
}
export function updateH5Link(key: EntityId, payload: H5LinkConfigPayload): Promise<H5LinkConfigDto> {
export function updateH5Link(appCode: string, key: EntityId, payload: H5LinkConfigPayload): Promise<H5LinkConfigDto> {
const endpoint = API_ENDPOINTS.updateH5Link;
return apiRequest<H5LinkConfigDto, H5LinkConfigPayload>(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<ApiList<ExploreTabDto>> {
const endpoint = API_ENDPOINTS.listExploreTabs;
return apiRequest<ApiList<ExploreTabDto>>(apiEndpointPath(API_OPERATIONS.listExploreTabs), {

View File

@ -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();
}

View File

@ -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");
});

View File

@ -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<string, never>;
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: {

View File

@ -1506,6 +1506,7 @@ export interface AdminAppDto {
}
export interface H5LinkConfigDto {
appCode: string;
key: string;
label: string;
updatedAtMs?: number;