diff --git a/apps/frontend/src/components/admin/SecretFieldControl.tsx b/apps/frontend/src/components/admin/SecretFieldControl.tsx
new file mode 100644
index 00000000..640661b3
--- /dev/null
+++ b/apps/frontend/src/components/admin/SecretFieldControl.tsx
@@ -0,0 +1,158 @@
+import { EditOutlined, UndoOutlined } from '@ant-design/icons'
+import { Button, Input, Space, Tag, Typography } from 'antd'
+import { useState } from 'react'
+
+/**
+ * 脱敏密钥输入控件。
+ *
+ * 已配置时只显示掩码 + 「修改」按钮,点开后才出现密码输入框;
+ * 未编辑时对外 value 恒为 '',避免把掩码值当成新密钥回填提交。
+ */
+export function SecretFieldControl({
+ label,
+ field,
+ masked,
+ hasSecret,
+ value,
+ onChange,
+ placeholder,
+}: {
+ label: string
+ field: string
+ masked?: string | undefined
+ hasSecret?: boolean | undefined
+ value: string
+ onChange: (value: string) => void
+ placeholder?: string | undefined
+}) {
+ const [isEditing, setIsEditing] = useState(false)
+ const [draft, setDraft] = useState('')
+
+ const isStaged = Boolean(value && value.trim())
+ const configured = Boolean(hasSecret || (masked && masked.trim()))
+
+ function handleStartEdit() {
+ setDraft('')
+ setIsEditing(true)
+ }
+
+ function handleApply() {
+ const trimmed = draft.trim()
+ if (trimmed) {
+ onChange(trimmed)
+ setIsEditing(false)
+ setDraft('')
+ }
+ }
+
+ function handleCancel() {
+ setDraft('')
+ setIsEditing(false)
+ }
+
+ function handleRevert() {
+ onChange('')
+ setDraft('')
+ setIsEditing(false)
+ }
+
+ return (
+
+
+ {label}
+ {isStaged ? (
+ }
+ style={{ padding: 0, height: 'auto', fontSize: 12 }}
+ onClick={handleRevert}
+ >
+ 撤销修改
+
+ ) : null}
+
+
+ {isEditing ? (
+
+ setDraft(e.target.value)}
+ onPressEnter={handleApply}
+ />
+
+
+
+ ) : (
+
+
+
+ {isStaged
+ ? '已输入新密钥(待保存)'
+ : masked || (configured ? '••••••••••••••••' : '未配置')}
+
+ {isStaged ? (
+
+ 待保存
+
+ ) : configured ? (
+
+ 已配置
+
+ ) : (
+ 未配置
+ )}
+
+
} onClick={handleStartEdit} style={{ flexShrink: 0 }}>
+ {configured ? '修改' : '设置'}
+
+
+ )}
+
+ )
+}
diff --git a/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx b/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx
index f9fc96eb..3235fff9 100644
--- a/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx
+++ b/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx
@@ -1,14 +1,12 @@
import {
CopyOutlined,
DeleteOutlined,
- EditOutlined,
LinkOutlined,
PlusOutlined,
ReloadOutlined,
SaveOutlined,
SearchOutlined,
SendOutlined,
- UndoOutlined,
} from '@ant-design/icons'
import {
Alert,
@@ -31,6 +29,8 @@ import { useEffect, useState } from 'react'
import { useSearchParams } from 'react-router'
import PageHeader from '@/components/admin/PageHeader'
+import { SecretFieldControl } from '@/components/admin/SecretFieldControl'
+import { DouyinMashidaPanel } from './DouyinMashidaPanel'
import { PddPlatformPanel } from './PddPlatformPanel'
import { showError, showSuccess } from '@/lib/feedback'
import {
@@ -69,7 +69,7 @@ import { hasAdminRole } from '@/utils/admin-auth'
import { buildAdminLocalTablePagination } from '@/utils/admin-pagination'
import { formatAdminDateTime } from '@/utils/admin-time'
-type PlatformTab = 'notifications' | 'kuaishouIndustry' | 'affiliateDash' | 'pdd'
+type PlatformTab = 'notifications' | 'kuaishouIndustry' | 'affiliateDash' | 'pdd' | 'douyinMashida'
type NotificationConfigResponse = {
filePath: string
@@ -82,7 +82,13 @@ type ScheduledJobsState = {
runtime: AdminScheduledJobRuntimeState[]
}
-const platformTabs: PlatformTab[] = ['notifications', 'kuaishouIndustry', 'affiliateDash', 'pdd']
+const platformTabs: PlatformTab[] = [
+ 'notifications',
+ 'kuaishouIndustry',
+ 'affiliateDash',
+ 'pdd',
+ 'douyinMashida',
+]
export default function AdminPlatformShopsPage() {
const [searchParams, setSearchParams] = useSearchParams()
@@ -200,6 +206,11 @@ export default function AdminPlatformShopsPage() {
label: '拼多多 (PDD)',
children: ,
},
+ {
+ key: 'douyinMashida',
+ label: '抖音码速达',
+ children: ,
+ },
]}
/>
>
@@ -644,155 +655,6 @@ function formatScheduledJobTitle(job: AdminScheduledJobItem) {
return job.id || job.type || '定时任务'
}
-function SecretFieldControl({
- label,
- field,
- masked,
- hasSecret,
- value,
- onChange,
- placeholder,
-}: {
- label: string
- field: string
- masked?: string | undefined
- hasSecret?: boolean | undefined
- value: string
- onChange: (value: string) => void
- placeholder?: string | undefined
-}) {
- const [isEditing, setIsEditing] = useState(false)
- const [draft, setDraft] = useState('')
-
- const isStaged = Boolean(value && value.trim())
- const configured = Boolean(hasSecret || (masked && masked.trim()))
-
- function handleStartEdit() {
- setDraft('')
- setIsEditing(true)
- }
-
- function handleApply() {
- const trimmed = draft.trim()
- if (trimmed) {
- onChange(trimmed)
- setIsEditing(false)
- setDraft('')
- }
- }
-
- function handleCancel() {
- setDraft('')
- setIsEditing(false)
- }
-
- function handleRevert() {
- onChange('')
- setDraft('')
- setIsEditing(false)
- }
-
- return (
-
-
- {label}
- {isStaged ? (
- }
- style={{ padding: 0, height: 'auto', fontSize: 12 }}
- onClick={handleRevert}
- >
- 撤销修改
-
- ) : null}
-
-
- {isEditing ? (
-
- setDraft(e.target.value)}
- onPressEnter={handleApply}
- />
-
-
-
- ) : (
-
-
-
- {isStaged
- ? '已输入新密钥(待保存)'
- : masked || (configured ? '••••••••••••••••' : '未配置')}
-
- {isStaged ? (
-
- 待保存
-
- ) : configured ? (
-
- 已配置
-
- ) : (
- 未配置
- )}
-
-
} onClick={handleStartEdit} style={{ flexShrink: 0 }}>
- {configured ? '修改' : '设置'}
-
-
- )}
-
- )
-}
-
function KuaishouIndustryPanel({
config,
onChange,
diff --git a/apps/frontend/src/pages/admin/platform/DouyinMashidaPanel.tsx b/apps/frontend/src/pages/admin/platform/DouyinMashidaPanel.tsx
new file mode 100644
index 00000000..239edae2
--- /dev/null
+++ b/apps/frontend/src/pages/admin/platform/DouyinMashidaPanel.tsx
@@ -0,0 +1,346 @@
+/**
+ * Douyin Mashida Platform Admin Panel
+ * 抖音码速达接入配置面板 (凭据脱敏读写、配置完整度自检与本地地址误配提示)
+ */
+
+import { ApiOutlined, ReloadOutlined, SaveOutlined } from '@ant-design/icons'
+import {
+ Alert,
+ Badge,
+ Button,
+ Card,
+ Col,
+ Input,
+ InputNumber,
+ Row,
+ Select,
+ Space,
+ Spin,
+ Switch,
+ Tag,
+ Typography,
+} from 'antd'
+import { useEffect, useState } from 'react'
+
+import { SecretFieldControl } from '@/components/admin/SecretFieldControl'
+import { showError, showSuccess } from '@/lib/feedback'
+import {
+ fetchAdminDouyinMashidaConfig,
+ saveAdminDouyinMashidaConfig,
+} from '@/services/admin/platform-config'
+import type { AdminDouyinMashidaConfig } from '@/types/admin/platform-config'
+import {
+ buildDouyinMashidaMessageUrl,
+ describeMissingConfigFields,
+ isLocalDevBaseUrl,
+} from '@/utils/douyin-mashida'
+
+const { Text } = Typography
+
+/** 平台侧可订阅的消息类型(与后端 message_type 取值一致) */
+const MESSAGE_TYPE_OPTIONS = [
+ { value: 1, label: '1 - 订单状态' },
+ { value: 2, label: '2 - 订单发货' },
+ { value: 4, label: '4 - 图片' },
+ { value: 5, label: '5 - 视频' },
+]
+
+/** 默认本地处理白名单,作为 Select 的候选标签 */
+const DEFAULT_HANDLED_TOPICS = ['trade_TradePaid', 'trade_TradeCanceled', 'refund_RefundSuccess']
+
+const DEFAULT_BASE_URL = 'https://dy-openplatform-api.wanmahui.com'
+
+function createEmptyConfig(): AdminDouyinMashidaConfig {
+ return {
+ enabled: true,
+ baseUrl: DEFAULT_BASE_URL,
+ uid: '',
+ secret: '',
+ secretMasked: '',
+ hasSecret: false,
+ certLink: '',
+ afterVerifyLink: '',
+ afterVerifyMessage: '您的订单已兑换成功',
+ messageType: 2,
+ timeoutMs: 10000,
+ handledTopics: [...DEFAULT_HANDLED_TOPICS],
+ ready: false,
+ }
+}
+
+export function DouyinMashidaPanel() {
+ const [loading, setLoading] = useState(true)
+ const [saving, setSaving] = useState(false)
+ const [config, setConfig] = useState(createEmptyConfig)
+
+ useEffect(() => {
+ void loadConfig()
+ }, [])
+
+ /**
+ * Load Douyin Mashida config
+ * 读取抖音码速达接入配置(secret 出参恒为空串,不回显)
+ */
+ async function loadConfig() {
+ setLoading(true)
+ try {
+ const response = await fetchAdminDouyinMashidaConfig()
+ if (response && response.data) {
+ setConfig({ ...response.data, secret: '' })
+ }
+ } catch (error) {
+ showError(error instanceof Error ? error.message : '读取抖音码速达配置失败')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ /**
+ * Save config. Empty / masked secret keeps the stored value (backend semantics).
+ * 保存配置:secret 留空或保持掩码即「不修改」,不会被空串覆盖。
+ */
+ async function handleSave() {
+ setSaving(true)
+ try {
+ const payload: AdminDouyinMashidaConfig = {
+ ...config,
+ secret: String(config.secret || '').trim(),
+ }
+ const response = await saveAdminDouyinMashidaConfig(payload)
+ if (response && response.data) {
+ setConfig({ ...response.data, secret: '' })
+ }
+ showSuccess('抖音码速达配置已保存')
+ await loadConfig()
+ } catch (error) {
+ showError(error instanceof Error ? error.message : '保存抖音码速达配置失败')
+ } finally {
+ setSaving(false)
+ }
+ }
+
+ function updateField(
+ key: K,
+ value: AdminDouyinMashidaConfig[K],
+ ) {
+ setConfig((prev) => ({ ...prev, [key]: value }))
+ }
+
+ if (loading) {
+ return (
+
+
+
+
+
+ )
+ }
+
+ const missingFields = describeMissingConfigFields({
+ enabled: config.enabled,
+ uid: config.uid,
+ hasSecret: config.hasSecret,
+ certLink: config.certLink,
+ })
+ const isLocalDev = isLocalDevBaseUrl(config.baseUrl)
+ const messageUrl = buildDouyinMashidaMessageUrl(window.location.origin)
+
+ return (
+
+
+
+ 抖音码速达接入配置
+
+
+ }
+ extra={
+
+ } onClick={() => void loadConfig()}>
+ 刷新
+
+ } loading={saving} onClick={handleSave}>
+ 保存配置
+
+
+ }
+ >
+
+
+ 接入状态:
+
+ {config.ready ? '配置完整' : '待补齐'}
+
+
+ {config.enabled ? '接入开关已开启' : '接入开关已关闭'}
+
+ }
+ />
+
+
+ {!config.ready && missingFields.length > 0 ? (
+
+ ) : null}
+
+ {isLocalDev ? (
+
+ ) : null}
+
+
+ {messageUrl}
+
+ }
+ />
+
+
+
+
+
+ 接入开关
+
+ updateField('enabled', checked)}
+ />
+
+ {config.enabled ? '已启用' : '已停用'}
+
+
+
+
+
+
+
+ 网关地址 (baseUrl)
+ updateField('baseUrl', event.target.value)}
+ placeholder={DEFAULT_BASE_URL}
+ />
+
+ 真实联调请填 https://dy-openplatform-api.wanmahui.com(本地地址收不到平台推送)
+
+
+
+
+
+
+ 抖店商户号 (uid)
+ updateField('uid', event.target.value)}
+ placeholder="平台方分配的商户 uid"
+ />
+
+
+
+
+ updateField('secret', value)}
+ placeholder="输入新的用户秘钥(留空保持原值)"
+ />
+
+
+
+
+ 兑换链接 (certLink)
+ updateField('certLink', event.target.value)}
+ placeholder="https://你的域名/redeem"
+ />
+
+ 卡券里下发给买家的固定兑换链接
+
+
+
+
+
+
+ 去使用链接 (afterVerifyLink)
+ updateField('afterVerifyLink', event.target.value)}
+ placeholder="兑换成功后发给买家,留空则只发文案"
+ />
+
+
+
+
+
+ 通知正文 (afterVerifyMessage)
+ updateField('afterVerifyMessage', event.target.value)}
+ placeholder="您的订单已兑换成功"
+ />
+
+
+
+
+
+ 消息类型 (messageType)
+
+
+
+
+
+ 出网超时 (timeoutMs)
+ updateField('timeoutMs', Number(value || 10000))}
+ />
+
+
+
+
+
+ 处理 topic 白名单 (handledTopics)
+
+
+
+
+ )
+}
diff --git a/apps/frontend/src/services/admin/platform-config/douyin-mashida.ts b/apps/frontend/src/services/admin/platform-config/douyin-mashida.ts
new file mode 100644
index 00000000..0e975fdc
--- /dev/null
+++ b/apps/frontend/src/services/admin/platform-config/douyin-mashida.ts
@@ -0,0 +1,15 @@
+/**
+ * Douyin Mashida Admin Platform Config Service
+ * 抖音码速达后台接入配置 HTTP 服务
+ */
+
+import { apiGet, apiPost } from '@/lib/http'
+import type { AdminDouyinMashidaConfig } from '@/types/admin/platform-config'
+
+export function fetchAdminDouyinMashidaConfig() {
+ return apiGet('/api/v1/admin/platform-config/douyin-mashida')
+}
+
+export function saveAdminDouyinMashidaConfig(payload: AdminDouyinMashidaConfig) {
+ return apiPost('/api/v1/admin/platform-config/douyin-mashida', payload)
+}
diff --git a/apps/frontend/src/services/admin/platform-config/index.ts b/apps/frontend/src/services/admin/platform-config/index.ts
index 9885667b..29fc3710 100644
--- a/apps/frontend/src/services/admin/platform-config/index.ts
+++ b/apps/frontend/src/services/admin/platform-config/index.ts
@@ -4,3 +4,4 @@ export * from './kuaishou-industry'
export * from './affiliate-dash'
export * from './fulfillment-routing'
export * from './pdd'
+export * from './douyin-mashida'
diff --git a/apps/frontend/src/types/admin/platform-config/douyin-mashida.ts b/apps/frontend/src/types/admin/platform-config/douyin-mashida.ts
new file mode 100644
index 00000000..a9a1081b
--- /dev/null
+++ b/apps/frontend/src/types/admin/platform-config/douyin-mashida.ts
@@ -0,0 +1,34 @@
+/**
+ * Douyin Mashida Admin Platform Config Types
+ * 抖音码速达(抖店中转开放平台)后台配置数据结构定义
+ *
+ * 与后端 `services/admin/platform-config/douyin-mashida-service.ts` 的出参一一对应:
+ * `secret` 出参永远为空串(不回显明文),只通过 `secretMasked` / `hasSecret` 表达「已配置」。
+ */
+
+export interface AdminDouyinMashidaConfig {
+ enabled: boolean
+ /** 码速达开放平台网关地址(本地联调可填 http://127.0.0.1:3000 之类的桩地址) */
+ baseUrl: string
+ /** 抖店商户号 */
+ uid: string
+ /** 用户秘钥:出参永远是 '',提交时填了新值才会更新 */
+ secret: string
+ /** 脱敏后的秘钥,形如 abcd****wxyz */
+ secretMasked: string
+ hasSecret: boolean
+ /** 卡券里的固定兑换链接,如 https://<域名>/redeem */
+ certLink: string
+ /** 兑换成功后发给买家的「去使用链接」 */
+ afterVerifyLink: string
+ /** 兑换成功通知正文,默认「您的订单已兑换成功」 */
+ afterVerifyMessage: string
+ /** 1-订单状态 2-订单发货 4-图片 5-视频 */
+ messageType: number
+ /** 出网超时(毫秒),默认 10000 */
+ timeoutMs: number
+ /** 本地处理白名单 topic */
+ handledTopics: string[]
+ /** enabled + uid + secret + certLink 齐全(只读) */
+ ready: boolean
+}
diff --git a/apps/frontend/src/types/admin/platform-config/index.ts b/apps/frontend/src/types/admin/platform-config/index.ts
index 302eae48..1477b15d 100644
--- a/apps/frontend/src/types/admin/platform-config/index.ts
+++ b/apps/frontend/src/types/admin/platform-config/index.ts
@@ -51,3 +51,5 @@ export type {
AdminPddSyncAllResponse,
AdminPddStoreSyncTokenResponse,
} from './pdd'
+
+export type { AdminDouyinMashidaConfig } from './douyin-mashida'
diff --git a/apps/frontend/src/utils/douyin-mashida.ts b/apps/frontend/src/utils/douyin-mashida.ts
new file mode 100644
index 00000000..3659a17e
--- /dev/null
+++ b/apps/frontend/src/utils/douyin-mashida.ts
@@ -0,0 +1,64 @@
+/**
+ * 抖音码速达后台配置的纯展示 / 校验逻辑。
+ *
+ * 刻意写成纯函数:不依赖 React / antd,也不使用 `@/` 别名,只做 `import type`,
+ * 方便 `node --experimental-strip-types --test` 直接跑单测。
+ */
+import type { AdminDouyinMashidaConfig } from '../types/admin/platform-config'
+
+/** 配置完整度检查所需的配置子集 */
+type MissingConfigCheck = Pick<
+ AdminDouyinMashidaConfig,
+ 'enabled' | 'uid' | 'hasSecret' | 'certLink'
+>
+
+/** 配置完整度检查的固定展示顺序 */
+const MISSING_FIELD_LABELS = ['接入开关', '商户uid', '用户秘钥', '兑换链接'] as const
+
+/** 本地/内网 host:命中即认为不会请求真实码速达 */
+const LOCAL_DEV_HOSTS = new Set(['127.0.0.1', 'localhost', 'host.docker.internal'])
+
+/**
+ * 判断 baseUrl 是否指向本地/内网地址(这类地址平台永远访问不到)。
+ *
+ * 容忍端口、结尾斜杠、大小写与缺失协议(如 `127.0.0.1:3000`)。
+ * 无法解析的地址一律视为非本地。
+ */
+export function isLocalDevBaseUrl(baseUrl: string): boolean {
+ const text = String(baseUrl ?? '').trim()
+ if (!text) return false
+
+ try {
+ const url = new URL(text.includes('://') ? text : `http://${text}`)
+ return LOCAL_DEV_HOSTS.has(url.hostname.toLowerCase())
+ } catch {
+ return false
+ }
+}
+
+/**
+ * 拼接平台侧需要配置的消息推送地址(容错结尾斜杠)。
+ * 例:`https://ks.khhao.com/` → `https://ks.khhao.com/api/v1/open/douyin-mashida/message`
+ */
+export function buildDouyinMashidaMessageUrl(origin: string): string {
+ const normalized = String(origin ?? '')
+ .trim()
+ .replace(/\/+$/, '')
+ return `${normalized}/api/v1/open/douyin-mashida/message`
+}
+
+/**
+ * 返回缺失项的中文名数组,顺序固定为:接入开关 / 商户uid / 用户秘钥 / 兑换链接。
+ * 全齐时返回空数组。
+ */
+export function describeMissingConfigFields(config: MissingConfigCheck): string[] {
+ const source = config ?? ({} as MissingConfigCheck)
+ const missing: string[] = []
+
+ if (!source.enabled) missing.push(MISSING_FIELD_LABELS[0])
+ if (!String(source.uid ?? '').trim()) missing.push(MISSING_FIELD_LABELS[1])
+ if (!source.hasSecret) missing.push(MISSING_FIELD_LABELS[2])
+ if (!String(source.certLink ?? '').trim()) missing.push(MISSING_FIELD_LABELS[3])
+
+ return missing
+}
diff --git a/apps/frontend/tests/douyin-mashida.test.ts b/apps/frontend/tests/douyin-mashida.test.ts
new file mode 100644
index 00000000..183bd622
--- /dev/null
+++ b/apps/frontend/tests/douyin-mashida.test.ts
@@ -0,0 +1,86 @@
+import assert from 'node:assert/strict'
+import test from 'node:test'
+
+import {
+ buildDouyinMashidaMessageUrl,
+ describeMissingConfigFields,
+ isLocalDevBaseUrl,
+} from '../src/utils/douyin-mashida.ts'
+
+/** 固定顺序:接入开关 / 商户uid / 用户秘钥 / 兑换链接 */
+const ALL_MISSING = ['接入开关', '商户uid', '用户秘钥', '兑换链接']
+
+test('isLocalDevBaseUrl:127.0.0.1 / localhost / host.docker.internal(含端口与结尾斜杠)判为 true', () => {
+ assert.equal(isLocalDevBaseUrl('http://127.0.0.1:3000'), true)
+ assert.equal(isLocalDevBaseUrl('http://127.0.0.1:3000/'), true)
+ assert.equal(isLocalDevBaseUrl('http://127.0.0.1'), true)
+ assert.equal(isLocalDevBaseUrl('http://localhost:8080/'), true)
+ assert.equal(isLocalDevBaseUrl('http://localhost'), true)
+ assert.equal(isLocalDevBaseUrl('http://host.docker.internal:3000/'), true)
+ assert.equal(isLocalDevBaseUrl(' http://localhost:5173// '), true)
+})
+
+test('isLocalDevBaseUrl:线上地址 / 空串 / 非本地域名判为 false', () => {
+ assert.equal(isLocalDevBaseUrl('https://dy-openplatform-api.wanmahui.com'), false)
+ assert.equal(isLocalDevBaseUrl('https://dy-openplatform-api.wanmahui.com/'), false)
+ assert.equal(isLocalDevBaseUrl(''), false)
+ assert.equal(isLocalDevBaseUrl(' '), false)
+ assert.equal(isLocalDevBaseUrl('http://example.com'), false)
+ assert.equal(isLocalDevBaseUrl('http://notlocalhost:3000'), false)
+})
+
+test('buildDouyinMashidaMessageUrl:拼接推送地址并容忍结尾斜杠', () => {
+ const suffix = '/api/v1/open/douyin-mashida/message'
+ assert.equal(
+ buildDouyinMashidaMessageUrl('https://ks.khhao.com'),
+ `https://ks.khhao.com${suffix}`,
+ )
+ assert.equal(
+ buildDouyinMashidaMessageUrl('https://ks.khhao.com/'),
+ `https://ks.khhao.com${suffix}`,
+ )
+ assert.equal(
+ buildDouyinMashidaMessageUrl('http://127.0.0.1:3000//'),
+ `http://127.0.0.1:3000${suffix}`,
+ )
+})
+
+test('describeMissingConfigFields:四项齐全返回空数组', () => {
+ assert.deepEqual(
+ describeMissingConfigFields({
+ enabled: true,
+ uid: 'uid_123',
+ hasSecret: true,
+ certLink: 'https://ks.khhao.com/redeem',
+ }),
+ [],
+ )
+})
+
+test('describeMissingConfigFields:缺多项按固定顺序返回中文名', () => {
+ assert.deepEqual(
+ describeMissingConfigFields({ enabled: false, uid: '', hasSecret: false, certLink: '' }),
+ ALL_MISSING,
+ )
+})
+
+test('describeMissingConfigFields:缺单项只返回对应中文名', () => {
+ assert.deepEqual(
+ describeMissingConfigFields({
+ enabled: true,
+ uid: 'uid_123',
+ hasSecret: false,
+ certLink: 'https://ks.khhao.com/redeem',
+ }),
+ ['用户秘钥'],
+ )
+ assert.deepEqual(
+ describeMissingConfigFields({
+ enabled: false,
+ uid: 'uid_123',
+ hasSecret: true,
+ certLink: ' ',
+ }),
+ ['接入开关', '兑换链接'],
+ )
+})