feat(web): 渠道配置面板重做(按模式动态显隐 + 下拉 + 真鉴权键 + 掩码不回填)
新增:channelFieldRules.ts(纯函数规则层:选择器归一化 / 按 transport×mode 显隐与必填 /
写回补丁 / 模式提示文案)、channelConnFields.tsx(「连接配置」区:只渲染当前组合生效的字段,
逐字段「谁连谁」说明 + 必填/可选标记 + 掩码清除开关)、channelAccountId.tsx(账号 id 与
「覆盖/新建」状态 Tag)、channelPersonaField.tsx(从表单抽出,逻辑逐字保留)。
qq-obv11:transport / mode 改下拉;remote_url 仅 ws+client 显示且必填;listen_addr 按
server / http+client 条件显示;api_base_url 仅 http(http+client 必填);report_path 仅 http;
access_token 掩码;self_id 仅记录。每种组合顶部一句 modeHints;保存前拦截必填缺失并红框点名。
⚠️ 修掉一处数据毁伤:后端把 access_token 掩码成 ****abcd 下发,旧代码回填进表单 ⇒
「打开就保存」会把掩码写成真 token。现在掩码不回填、只记 configuredKeys,并提示
「已配置,留空 = 不改动」;要清空必须显式打开「清除已配置的 access_token」开关
(顺带给掩码秘密补上了以前没有的「清除」能力)。可见字段清空发 ""、隐藏字段不进补丁(缺位 = 保留)。
Token 误导修掉:PLATFORM_GUIDE.authKey 声明平台真正读取的键 —— qq-obv11 不再渲染 bot_token
输入框、不再标必填(改指向 access_token);渠道列表与只读配置卡的凭据行同步改用真实 authKey。
空 id:结论是后端契约(UpsertChannel 空 id = 新建),前端不碰后端修好:记住 savedId
(留空 = 覆盖当前条目)+ 改新 id 时内联二次确认 + 保存后用返回的 accountId 刷新 + 状态 Tag。
测试:新增真组件 SSR 渲染测试(68 checks)+ 规则单测(58 checks);17 个前端测试文件逐跑全绿;
tsc --noEmit 与 npm run build 通过(产物 index-CK_2RXKo.js)。
This commit is contained in:
1 parent
8c437866e7
commit
c4ea02a887
12 files changed
+1358
-132
No files matched your search
@@ -2,6 +2,7 @@ import type { AdminChannelAccount } from "../../../lib/types";
|
||||
import { Button, Surface, Tag } from "../../../components/primitives";
|
||||
import { Notice } from "../../components";
|
||||
import { PLATFORM_FIELDS, PLATFORM_LABEL } from "../channelFields";
|
||||
import { authKeyOf } from "../channelFieldRules";
|
||||
|
||||
/** 一条只读字段。 */
|
||||
interface Field {
|
||||
@@ -9,8 +10,11 @@ interface Field {
|
||||
value: string;
|
||||
}
|
||||
|
||||
/** 通用连接键(各平台都有;平台专属键见 channelFields 的 PLATFORM_FIELDS)。 */
|
||||
const COMMON_CONNECTION_KEYS = ["bot_token", "base_url"];
|
||||
/** 通用连接键:该平台**真正读取**的凭据键(qq-obv11 = access_token、webhook = secret,
|
||||
* 不是硬编码的 bot_token)+ base_url。平台专属键见 channelFields 的 PLATFORM_FIELDS。 */
|
||||
function commonConnectionKeys(platform: string): string[] {
|
||||
return [authKeyOf(platform), "base_url"];
|
||||
}
|
||||
|
||||
/** 限流 / 会话额度**不在 config 里**:后端把它们作为账号顶层字段下发
|
||||
* (`AdminChannelAccount.GroupMsgPerMin/MsgPerMin/MaxMessages`,见
|
||||
@@ -38,19 +42,25 @@ function fmtList(values?: readonly string[]): string {
|
||||
return values && values.length > 0 ? values.join("、") : "未设置";
|
||||
}
|
||||
|
||||
/** 该平台连接字段的键:通用键 + 平台专属键(channelFields 是字段元数据的单一
|
||||
* 真源,此处不另造一份字段表)。 */
|
||||
/** 该平台连接字段的键:凭据键 + base_url + 平台专属键(channelFields 是字段元数据的单一
|
||||
* 真源,此处不另造一份字段表)。凭据键同时也是平台专属键时只算一次,避免同一行出现两遍。 */
|
||||
function connectionKeys(platform: string): string[] {
|
||||
return [...COMMON_CONNECTION_KEYS, ...(PLATFORM_FIELDS[platform] ?? []).map((f) => f.key)];
|
||||
return [...new Set([...commonConnectionKeys(platform), ...(PLATFORM_FIELDS[platform] ?? []).map((f) => f.key)])];
|
||||
}
|
||||
|
||||
function connectionFields(platform: string, account: AdminChannelAccount): Field[] {
|
||||
const cfg = account.config ?? {};
|
||||
const defs = PLATFORM_FIELDS[platform] ?? [];
|
||||
const labelOf = (key: string) => defs.find((f) => f.key === key)?.label ?? key;
|
||||
const out: Field[] = [{ label: "账号 id", value: account.id.trim() || "default" }];
|
||||
for (const key of COMMON_CONNECTION_KEYS) out.push({ label: key, value: fmtValue(cfg[key]) });
|
||||
for (const f of PLATFORM_FIELDS[platform] ?? []) {
|
||||
out.push({ label: f.label, value: fmtValue(cfg[f.key]) });
|
||||
}
|
||||
const seen = new Set<string>();
|
||||
const push = (key: string) => {
|
||||
if (seen.has(key)) return;
|
||||
seen.add(key);
|
||||
out.push({ label: labelOf(key), value: fmtValue(cfg[key]) });
|
||||
};
|
||||
for (const key of commonConnectionKeys(platform)) push(key);
|
||||
for (const f of defs) push(f.key);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user