dsh-remote-web 0.7.12-beta.1 → 0.7.12-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -675,6 +675,9 @@ window.__ModuleLoader__.load({
675
675
  ".dru-ia-tier-link:hover{text-decoration:underline}",
676
676
  ".dru-ia-badge{display:inline-flex;align-items:center;padding:1px 6px;border-radius:999px;font-size:11px;font-weight:600;line-height:1.4;color:var(--dru-accent-fg);background:var(--dru-accent-tint);border:1px solid var(--dru-accent-tint-border);cursor:help}",
677
677
  "select.dru-ia-input[disabled]{opacity:.6;cursor:not-allowed;background:var(--dru-surface-3)}",
678
+ // 飞书 8 步向导:编号列表紧凑可读(一屏内可滚,不撑破弹层)
679
+ ".dru-ia-steps{margin:6px 0 8px;padding-left:20px;font-size:12px;line-height:1.7;color:var(--dsw-alias-label-secondary,var(--dru-fg-muted))}",
680
+ ".dru-ia-steps li{margin:2px 0}",
678
681
  // A3/A6(2026-11-07 业主逐条 UI 修正):状态头右侧按钮组 / 后台服务横向紧凑布局 / 卸载反馈单选行
679
682
  ".dru-ia-head-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end}",
680
683
  ".dru-bridge-keep{display:flex;align-items:center;gap:6px;margin:8px 0 10px;padding:7px 10px;border-radius:8px;font-size:12px;line-height:1.5;color:var(--dru-warn-fg);background:var(--dru-warn-soft);border:1px solid var(--dru-warn-border)}",
@@ -3584,6 +3587,21 @@ window.__ModuleLoader__.load({
3584
3587
  // 未登录守卫:为什么弹登录(A4/A5)+ 登录后自动回到哪个流程("手机访问"登录完继续生成访问码)。
3585
3588
  var iaLoginWhyArr = useState(""); var iaLoginWhy = iaLoginWhyArr[0]; var setIaLoginWhy = iaLoginWhyArr[1];
3586
3589
  var iaAfterLoginArr = useState(""); var iaAfterLogin = iaAfterLoginArr[0]; var setIaAfterLogin = iaAfterLoginArr[1];
3590
+ // ── 钉钉 / 飞书(2026-11-08):与 wecom 并列的两条通道 ──
3591
+ var imDingIdArr = useState(""); var imDingId = imDingIdArr[0]; var setImDingId = imDingIdArr[1];
3592
+ var imDingSecretArr = useState(""); var imDingSecret = imDingSecretArr[0]; var setImDingSecret = imDingSecretArr[1];
3593
+ var imDingRobotArr = useState(""); var imDingRobot = imDingRobotArr[0]; var setImDingRobot = imDingRobotArr[1];
3594
+ var imFeishuAppIdArr = useState(""); var imFeishuAppId = imFeishuAppIdArr[0]; var setImFeishuAppId = imFeishuAppIdArr[1];
3595
+ var imFeishuSecretArr = useState(""); var imFeishuSecret = imFeishuSecretArr[0]; var setImFeishuSecret = imFeishuSecretArr[1];
3596
+ var imFeishuRecvArr = useState(""); var imFeishuRecv = imFeishuRecvArr[0]; var setImFeishuRecv = imFeishuRecvArr[1];
3597
+ var imFeishuTypeArr = useState("open_id"); var imFeishuType = imFeishuTypeArr[0]; var setImFeishuType = imFeishuTypeArr[1];
3598
+ // 平台级消息/错误/忙碌(与 wecom 的 imBotsMsg 分开:切平台时不会串台)
3599
+ var imPlatMsgArr = useState(null); var imPlatMsg = imPlatMsgArr[0]; var setImPlatMsg = imPlatMsgArr[1]; // {kind,text}
3600
+ var imPlatErrArr = useState({}); var imPlatErr = imPlatErrArr[0]; var setImPlatErr = imPlatErrArr[1]; // {field: msg}
3601
+ var imPlatBusyArr = useState(""); var imPlatBusy = imPlatBusyArr[0]; var setImPlatBusy = imPlatBusyArr[1]; // ""|"save"|"test"|"deps"
3602
+ // 飞书 SDK 依赖(按需下载):installed/version/phase/lastError
3603
+ var imDepsArr = useState(null); var imDeps = imDepsArr[0]; var setImDeps = imDepsArr[1];
3604
+ var imFeishuWizardArr = useState(false); var imFeishuWizard = imFeishuWizardArr[0]; var setImFeishuWizard = imFeishuWizardArr[1];
3587
3605
 
3588
3606
  /** 读通道配置(GET 只回脱敏视图:botId/hasSecret/url/targetChatId + 桥写的状态)。 */
3589
3607
  function loadImBots() {
@@ -5989,6 +6007,21 @@ window.__ModuleLoader__.load({
5989
6007
  setImBotsSessionTitle(String(w.sessionTitle || ""));
5990
6008
  setImBotsWhyOpen(false);
5991
6009
  setImBotsUrl(String(w.url || ""));
6010
+ var d = (b && b.dingtalk) || {};
6011
+ setImDingId(String(d.clientId || ""));
6012
+ setImDingSecret("");
6013
+ setImDingRobot(String(d.robotCode || ""));
6014
+ var f = (b && b.feishu) || {};
6015
+ setImFeishuAppId(String(f.appId || ""));
6016
+ setImFeishuSecret("");
6017
+ setImFeishuRecv(String(f.receiveId || ""));
6018
+ setImFeishuType(String(f.receiveIdType || "open_id"));
6019
+ setImFeishuWizard(false);
6020
+ setImPlatMsg(null);
6021
+ setImPlatErr({});
6022
+ var deps = b && b.status && b.status.deps && b.status.deps.feishu;
6023
+ if (deps) setImDeps(deps);
6024
+ loadImDeps();
5992
6025
  });
5993
6026
  }
5994
6027
  // 数据加载交给下面的 effect(按"弹层是否打开"触发):
@@ -6554,6 +6587,283 @@ window.__ModuleLoader__.load({
6554
6587
  }, "去登录"))] }));
6555
6588
  }
6556
6589
 
6590
+ // ══════════════════════════════════════════════════════════════════════════════════
6591
+ // 钉钉 / 飞书(2026-11-08):与 wecom 并列的两条通道;飞书 SDK 走「按需下载」。
6592
+ // ══════════════════════════════════════════════════════════════════════════════════
6593
+
6594
+ /** 飞书官方文档(配置页顶部固定展示;外链一律 target=_blank + rel=noopener)。 */
6595
+ var FEISHU_DOCS = {
6596
+ createApp: "https://open.feishu.cn/document/develop-an-app/create-an-app",
6597
+ longConn: "https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/event-subscription-configure-/long-connection"
6598
+ };
6599
+ /** 8 步向导(业主/文档要求;顺序坑单独强调,见 renderFeishuStep 的提示)。 */
6600
+ var FEISHU_STEPS = [
6601
+ "创建企业自建应用:飞书开放平台 → 开发者后台 → 创建企业自建应用",
6602
+ "复制 App ID / App Secret(应用详情 → 凭证与基础信息)",
6603
+ "开通机器人能力:应用功能 → 机器人 → 启用",
6604
+ "申请权限:收 p2p / 群@消息,发 im:message:send_as_bot",
6605
+ "事件配置:订阅方式选「长连接」,添加「接收消息 v2.0」",
6606
+ "回调配置:订阅方式选「长连接」,添加 card.action.trigger",
6607
+ "发布版本:版本管理与发布 → 创建版本 → 申请发布(管理员审核)",
6608
+ "回本插件填 App ID / Secret / 接收人并点「保存并连接」"
6609
+ ];
6610
+
6611
+ /** GET /dsh-remote/im-deps:飞书 SDK 是否已装 + phase(下载进度轮询用)。 */
6612
+ function loadImDeps() {
6613
+ return api("/dsh-remote/im-deps").then(function (b) {
6614
+ var f = b && b.feishu;
6615
+ if (f) setImDeps(f);
6616
+ return f || null;
6617
+ }).catch(function () { return null; });
6618
+ }
6619
+
6620
+ /** 触发飞书 SDK 下载(后台 npm install;进度靠轮询 im-deps 的 phase)。 */
6621
+ function startFeishuDownload() {
6622
+ if (imPlatBusy) return;
6623
+ setImPlatBusy("deps");
6624
+ setImPlatMsg({ kind: "ok", text: "正在下载依赖(约 41MB,仅装一次)…" });
6625
+ post("/dsh-remote/im-deps/feishu", {}).then(function (b) {
6626
+ if (b && b.deps && b.deps.feishu) setImDeps(b.deps.feishu);
6627
+ loadImDeps();
6628
+ }).catch(function (e) {
6629
+ setImPlatMsg({ kind: "err", text: "下载失败:" + ((e && e.message) || e) + "(可重试)" });
6630
+ }).finally(function () { setImPlatBusy(""); });
6631
+ }
6632
+
6633
+ /** 依赖下载中:每 ~900ms 轮询一次 phase;失败/完成后自动停。 */
6634
+ useEffect(function () {
6635
+ if (iaModal !== "channel" || iaChStep !== "feishu") return undefined;
6636
+ var phase = imDeps && imDeps.phase;
6637
+ if (phase !== "downloading" && phase !== "installing") return undefined;
6638
+ var t = null;
6639
+ try {
6640
+ t = setTimeout(function () { loadImDeps(); }, 900);
6641
+ } catch (e) { /* 忽略 */ }
6642
+ return function () { try { if (t) clearTimeout(t); } catch (e) { /* 忽略 */ } };
6643
+ }, [iaModal, iaChStep, imDeps]);
6644
+
6645
+ /** 钉钉表单提交体(空 secret = 不修改,与 wecom 一致)。 */
6646
+ function imDingPayload() {
6647
+ return {
6648
+ clientId: String(imDingId || "").trim(),
6649
+ clientSecret: String(imDingSecret || ""),
6650
+ robotCode: String(imDingRobot || "").trim()
6651
+ };
6652
+ }
6653
+ /** 飞书表单提交体。 */
6654
+ function imFeishuPayload() {
6655
+ return {
6656
+ appId: String(imFeishuAppId || "").trim(),
6657
+ appSecret: String(imFeishuSecret || ""),
6658
+ receiveId: String(imFeishuRecv || "").trim(),
6659
+ receiveIdType: String(imFeishuType || "open_id")
6660
+ };
6661
+ }
6662
+ function imPlatFieldErrOf(code) {
6663
+ var map = {
6664
+ missing_client_id: "clientId", missing_client_secret: "clientSecret", missing_robot_code: "robotCode",
6665
+ missing_app_id: "appId", missing_app_secret: "appSecret", missing_receive_id: "receiveId",
6666
+ invalid_receive_id_type: "receiveIdType"
6667
+ };
6668
+ return map[String(code || "")] || "";
6669
+ }
6670
+ var IM_PLAT_FIELD_NAMES = {
6671
+ clientId: "Client ID", clientSecret: "Client Secret", robotCode: "Robot Code",
6672
+ appId: "App ID", appSecret: "App Secret", receiveId: "接收人", receiveIdType: "接收对象类型"
6673
+ };
6674
+
6675
+ /**
6676
+ * 保存 / 测试(钉钉·飞书共用)。
6677
+ * @param {"dingtalk"|"feishu"} platform
6678
+ * @param {"save"|"test"} mode save=只保存;test=先保存再连接测试(「保存并连接」)
6679
+ */
6680
+ function saveImPlatform(platform, mode) {
6681
+ if (imPlatBusy) return;
6682
+ var cfg = platform === "dingtalk" ? imDingPayload() : imFeishuPayload();
6683
+ setImPlatBusy(mode === "test" ? "test" : "save");
6684
+ setImPlatMsg(null);
6685
+ setImPlatErr({});
6686
+ // ⚠️ 必须是 PUT:/dsh-remote/im-bots 在节点半按 method 分发(GET/PUT),POST 会被 405。
6687
+ api("/dsh-remote/im-bots", {
6688
+ method: "PUT", headers: { "content-type": "application/json" },
6689
+ body: JSON.stringify({ platform: platform, config: cfg })
6690
+ }, 15000).then(function () {
6691
+ if (mode !== "test") {
6692
+ setImPlatMsg({ kind: "ok", text: "已保存(配置只在本机,Secret 不会回显)" });
6693
+ return loadImBots();
6694
+ }
6695
+ return post("/dsh-remote/im-bots/test", { platform: platform, config: cfg }, 15000).then(function (r) {
6696
+ if (r && r.connected) setImPlatMsg({ kind: "ok", text: "已连接,消息可以正常收发了" });
6697
+ else setImPlatMsg({ kind: "warn", text: "已保存,但连接还没成功" + (r && r.lastError ? ":" + r.lastError : "(稍后自动重试)") });
6698
+ return loadImBots();
6699
+ });
6700
+ }).catch(function (e) {
6701
+ var body = (e && e.body) || {};
6702
+ var code = String(body.error || "");
6703
+ var field = imPlatFieldErrOf(code);
6704
+ if (field) {
6705
+ var errs = {}; errs[field] = String(body.message || "请填写完整");
6706
+ setImPlatErr(errs);
6707
+ setImPlatMsg({ kind: "err", text: "请补全:" + IM_PLAT_FIELD_NAMES[field] });
6708
+ } else {
6709
+ setImPlatMsg({ kind: "err", text: "保存失败:" + (body.message || (e && e.message) || "未知错误") });
6710
+ }
6711
+ return null;
6712
+ }).finally(function () { setImPlatBusy(""); });
6713
+ }
6714
+
6715
+ /** 平台状态行(连接中 / 已连接 / 失败;文案面向非技术用户)。 */
6716
+ function imPlatStateLine(st) {
6717
+ if (imPlatBusy === "test") return h("div", { className: "dru-ia-state", role: "status" }, h("span", { className: "dru-spin" }), "正在连接…(最多 10 秒)");
6718
+ if (imPlatMsg) return h("div", { className: "dru-ia-state " + (imPlatMsg.kind === "ok" ? "is-ok" : imPlatMsg.kind === "warn" ? "is-warn" : "is-err"), role: "status" }, imPlatMsg.text);
6719
+ if (st && st.connected) return h("div", { className: "dru-ia-state is-ok", role: "status" }, druIcon("check"), "已连接");
6720
+ if (st && st.lastError) return h("div", { className: "dru-ia-state is-err", role: "status" }, "上次连接失败:" + st.lastError);
6721
+ return null;
6722
+ }
6723
+
6724
+ /** 钉钉配置页:Client ID / Client Secret / Robot Code(三个都要)。 */
6725
+ function renderDingtalkStep(close) {
6726
+ var d = (imBots && imBots.dingtalk) || {};
6727
+ var st = imBots && imBots.status && imBots.status.dingtalk;
6728
+ var err = imPlatErr || {};
6729
+ return iaDialog(Object.assign({}, {
6730
+ title: "钉钉机器人",
6731
+ sub: "Client ID / Secret 就是钉钉应用的 AppKey / AppSecret;Robot Code 是机器人编码",
6732
+ onClose: close,
6733
+ foot: [
6734
+ h("button", { type: "button", className: "dru-ia-btn is-primary", disabled: imPlatBusy !== "", onClick: function () { saveImPlatform("dingtalk", "test"); } },
6735
+ imPlatBusy === "test" ? "连接中…" : "保存并连接"),
6736
+ h("button", { type: "button", className: "dru-ia-btn", disabled: imPlatBusy !== "", onClick: function () { saveImPlatform("dingtalk", "save"); } },
6737
+ imPlatBusy === "save" ? "保存中…" : "仅保存"),
6738
+ h("button", { type: "button", className: "dru-ia-btn is-ghost", disabled: imPlatBusy !== "", onClick: function () {
6739
+ if (imPlatBusy) return;
6740
+ setImPlatBusy("test"); setImPlatMsg(null);
6741
+ post("/dsh-remote/im-bots/test", { platform: "dingtalk" }, 15000).then(function (r) {
6742
+ setImPlatMsg({ kind: r && r.connected ? "ok" : "warn", text: r && r.connected ? "已连接" : ("连接失败" + (r && r.lastError ? ":" + r.lastError : "(稍后自动重试)")) });
6743
+ }).catch(function (e) { setImPlatMsg({ kind: "err", text: "测试失败:" + ((e && e.message) || e) }); })
6744
+ .finally(function () { setImPlatBusy(""); });
6745
+ } }, "测试")
6746
+ ]
6747
+ }, { children: [
6748
+ h("div", { className: "dru-ia-state" }, druIcon("send"), d.hasSecret ? "已保存凭据(Secret 不回显)" : "还没有保存凭据"),
6749
+ iaField("Client ID(= AppKey)", "dru-ding-client", h("input", {
6750
+ id: "dru-ding-client", className: "dru-ia-input", type: "text", value: imDingId, autoComplete: "off", spellCheck: false,
6751
+ placeholder: "钉钉应用 AppKey", "aria-invalid": err.clientId ? "true" : "false",
6752
+ onChange: function (e) { setImDingId(e.target.value); }
6753
+ }), err.clientId || ""),
6754
+ iaField("Client Secret(= AppSecret)", "dru-ding-secret", h("input", {
6755
+ id: "dru-ding-secret", className: "dru-ia-input", type: "password", value: imDingSecret, autoComplete: "new-password", spellCheck: false,
6756
+ placeholder: d.hasSecret ? "已保存(留空表示不修改)" : "钉钉应用 AppSecret", "aria-invalid": err.clientSecret ? "true" : "false",
6757
+ onChange: function (e) { setImDingSecret(e.target.value); }
6758
+ }), err.clientSecret || "", "只保存在本机;已保存的 Secret 永远不回显。"),
6759
+ iaField("Robot Code", "dru-ding-robot", h("input", {
6760
+ id: "dru-ding-robot", className: "dru-ia-input", type: "text", value: imDingRobot, autoComplete: "off", spellCheck: false,
6761
+ placeholder: "机器人编码(开放平台 → 机器人页复制)", "aria-invalid": err.robotCode ? "true" : "false",
6762
+ onChange: function (e) { setImDingRobot(e.target.value); }
6763
+ }), err.robotCode || "", "三个字段都要填:Client ID/Secret 建应用时给,Robot Code 在机器人页。"),
6764
+ imPlatStateLine(st)
6765
+ ] }));
6766
+ }
6767
+
6768
+ /** 飞书 SDK 依赖未安装时的那一屏(按需下载,不装也能用其它通道)。 */
6769
+ function renderFeishuDepsGate(close) {
6770
+ var deps = imDeps || {};
6771
+ var running = deps.phase === "downloading" || deps.phase === "installing";
6772
+ return iaDialog(Object.assign({}, {
6773
+ title: "飞书机器人",
6774
+ sub: "首次使用需要先下载依赖(约 41MB,仅装一次)",
6775
+ onClose: close,
6776
+ foot: [
6777
+ h("button", { type: "button", className: "dru-ia-btn is-primary", disabled: imPlatBusy !== "" || running, onClick: startFeishuDownload },
6778
+ running ? "下载中…" : (deps.phase === "failed" ? "重试下载" : "下载依赖")),
6779
+ h("button", { type: "button", className: "dru-ia-btn", onClick: function () { setIaChStep("list"); } }, "返回平台列表")
6780
+ ]
6781
+ }, { children: [
6782
+ h("div", { className: "dru-ia-state" }, druIcon("bell"),
6783
+ running ? "正在下载并安装(约 41MB,仅装一次)…" : (deps.phase === "failed" ? "上次下载失败" : "还没有安装飞书依赖")),
6784
+ deps.lastError ? h("div", { className: "dru-ia-state is-err", role: "alert" }, deps.lastError) : null,
6785
+ h("div", { className: "dru-ia-hint" }, "下载的是飞书官方 Node SDK,仅装在这个插件自己的目录里,不影响其它插件和系统环境。"),
6786
+ h("div", { className: "dru-ia-hint" }, "不下载也能用其它通道:企业微信、钉钉都可正常配置;飞书随时回来装即可,不会影响已有通知。")
6787
+ ] }));
6788
+ }
6789
+
6790
+ /** 飞书配置页(依赖已装):官方文档 + 8 步向导 + 三个字段。 */
6791
+ function renderFeishuStep(close) {
6792
+ var deps = imDeps || {};
6793
+ if (deps.installed !== true) return renderFeishuDepsGate(close);
6794
+ var f = (imBots && imBots.feishu) || {};
6795
+ var st = imBots && imBots.status && imBots.status.feishu;
6796
+ var err = imPlatErr || {};
6797
+ return iaDialog(Object.assign({}, {
6798
+ title: "飞书机器人",
6799
+ sub: "依赖已就绪(v" + (deps.version || f.sdkVersion || "?") + ");填 App ID / Secret / 接收人即可",
6800
+ onClose: close,
6801
+ foot: [
6802
+ h("button", { type: "button", className: "dru-ia-btn is-primary", disabled: imPlatBusy !== "", onClick: function () { saveImPlatform("feishu", "test"); } },
6803
+ imPlatBusy === "test" ? "连接中…" : "保存并连接"),
6804
+ h("button", { type: "button", className: "dru-ia-btn", disabled: imPlatBusy !== "", onClick: function () { saveImPlatform("feishu", "save"); } },
6805
+ imPlatBusy === "save" ? "保存中…" : "仅保存"),
6806
+ h("button", { type: "button", className: "dru-ia-btn is-ghost", disabled: imPlatBusy !== "", onClick: function () {
6807
+ if (imPlatBusy) return;
6808
+ setImPlatBusy("test"); setImPlatMsg(null);
6809
+ post("/dsh-remote/im-bots/test", { platform: "feishu" }, 15000).then(function (r) {
6810
+ setImPlatMsg({ kind: r && r.connected ? "ok" : "warn", text: r && r.connected ? "已连接" : ("连接失败" + (r && r.lastError ? ":" + r.lastError : "(稍后自动重试)")) });
6811
+ }).catch(function (e) { setImPlatMsg({ kind: "err", text: "测试失败:" + ((e && e.message) || e) }); })
6812
+ .finally(function () { setImPlatBusy(""); });
6813
+ } }, "测试")
6814
+ ]
6815
+ }, { children: [
6816
+ // 官方文档固定放顶部(两个外链)
6817
+ h("div", { className: "dru-ia-hint" },
6818
+ "官方文档:",
6819
+ h("a", { className: "dru-ia-tier-link", href: FEISHU_DOCS.createApp, target: "_blank", rel: "noopener" }, "飞书怎么创建应用"),
6820
+ " · ",
6821
+ h("a", { className: "dru-ia-tier-link", href: FEISHU_DOCS.longConn, target: "_blank", rel: "noopener" }, "长连接事件订阅")),
6822
+ // 8 步向导(折叠;默认收起,避免一屏文字堆砌)
6823
+ h("div", { className: "dru-ia-field" },
6824
+ h("button", {
6825
+ type: "button", className: "dru-ia-disclose", "aria-expanded": imFeishuWizard ? "true" : "false",
6826
+ onClick: function () { setImFeishuWizard(!imFeishuWizard); }
6827
+ }, "配置步骤(8 步)", druIcon(imFeishuWizard ? "left" : "right")),
6828
+ imFeishuWizard
6829
+ ? h("div", null,
6830
+ h("ol", { className: "dru-ia-steps" }, FEISHU_STEPS.map(function (t, i) {
6831
+ return h("li", { key: String(i) }, t);
6832
+ })),
6833
+ // 顺序坑(必须写明):事件配置保存时「长连接必须已在线」
6834
+ h("div", { className: "dru-ia-state is-warn", role: "note" },
6835
+ "顺序很重要:保存事件/回调配置时飞书的「长连接必须已在线」——",
6836
+ "所以请**先在第 8 步填凭据让插件连上**,再回飞书后台保存事件配置;顺序反了会保存失败。"))
6837
+ : null),
6838
+ h("div", { className: "dru-ia-state" }, druIcon("send"), f.hasSecret ? "已保存凭据(Secret 不回显)" : "还没有保存凭据"),
6839
+ iaField("App ID", "dru-fs-appid", h("input", {
6840
+ id: "dru-fs-appid", className: "dru-ia-input", type: "text", value: imFeishuAppId, autoComplete: "off", spellCheck: false,
6841
+ placeholder: "cli_xxxxxxxx", "aria-invalid": err.appId ? "true" : "false",
6842
+ onChange: function (e) { setImFeishuAppId(e.target.value); }
6843
+ }), err.appId || ""),
6844
+ iaField("App Secret", "dru-fs-secret", h("input", {
6845
+ id: "dru-fs-secret", className: "dru-ia-input", type: "password", value: imFeishuSecret, autoComplete: "new-password", spellCheck: false,
6846
+ placeholder: f.hasSecret ? "已保存(留空表示不修改)" : "从应用详情 → 凭证与基础信息复制",
6847
+ "aria-invalid": err.appSecret ? "true" : "false",
6848
+ onChange: function (e) { setImFeishuSecret(e.target.value); }
6849
+ }), err.appSecret || "", "只保存在本机;已保存的 Secret 永远不回显。"),
6850
+ iaField("接收人(open_id 或 chat_id)", "dru-fs-recv", h("input", {
6851
+ id: "dru-fs-recv", className: "dru-ia-input", type: "text", value: imFeishuRecv, autoComplete: "off", spellCheck: false,
6852
+ placeholder: "ou_xxxxxxxx 或 oc_xxxxxxxx", "aria-invalid": err.receiveId ? "true" : "false",
6853
+ onChange: function (e) { setImFeishuRecv(e.target.value); }
6854
+ }), err.receiveId || ""),
6855
+ iaField("接收对象类型", "dru-fs-rtype",
6856
+ h("select", {
6857
+ id: "dru-fs-rtype", className: "dru-ia-input", value: imFeishuType,
6858
+ onChange: function (e) { setImFeishuType(e.target.value); }
6859
+ },
6860
+ h("option", { value: "open_id" }, "个人 open_id(ou_ 开头)"),
6861
+ h("option", { value: "chat_id" }, "群 chat_id(oc_ 开头)")),
6862
+ err.receiveIdType || "", "发给个人选 open_id;发到群选 chat_id。"),
6863
+ imPlatStateLine(st)
6864
+ ] }));
6865
+ }
6866
+
6557
6867
  /** 还没支持的平台:只给**诚实**的空状态(说明 + 反馈入口),绝不放假表单/假入口。 */
6558
6868
  function renderComingSoonStep(close, kind) {
6559
6869
  var meta = {
@@ -6600,12 +6910,30 @@ window.__ModuleLoader__.load({
6600
6910
  if (iaModal !== "channel") return null;
6601
6911
  if (iaChStep === "wecom") return renderWecomStep(closeIAModal);
6602
6912
  if (iaChStep === "wechat") return renderWechatStep(closeIAModal);
6603
- if (iaChStep === "feishu" || iaChStep === "dingtalk" || iaChStep === "webhook") return renderComingSoonStep(closeIAModal, iaChStep);
6913
+ if (iaChStep === "dingtalk") return renderDingtalkStep(closeIAModal);
6914
+ if (iaChStep === "feishu") return renderFeishuStep(closeIAModal);
6915
+ if (iaChStep === "webhook") return renderComingSoonStep(closeIAModal, "webhook");
6604
6916
  var w = (imBots && imBots.wecom) || {};
6605
6917
  var ws = (imBots && imBots.status) || {};
6606
6918
  var wecomSub = ws.connected
6607
6919
  ? "已连接 · 推送消息到企业微信"
6608
6920
  : (w.enabled || w.hasSecret ? "已配置,等待连接" : "未配置 · 填 Bot ID 与 Secret 即可");
6921
+ var d = (imBots && imBots.dingtalk) || {};
6922
+ var f = (imBots && imBots.feishu) || {};
6923
+ var dst = (imBots && imBots.status && imBots.status.dingtalk) || {};
6924
+ var fst = (imBots && imBots.status && imBots.status.feishu) || {};
6925
+ var dingSub = dst.connected
6926
+ ? "已连接 · 推送消息到钉钉"
6927
+ : (d.enabled || d.hasSecret ? "已配置,等待连接" : "未配置 · 填 Client ID / Secret / Robot Code");
6928
+ var feishuSub = !(imDeps && imDeps.installed)
6929
+ ? "需要先下载依赖(约 41MB,仅装一次)"
6930
+ : (fst.connected ? "已连接 · 推送消息到飞书" : (f.enabled || f.hasSecret ? "已配置,等待连接" : "依赖已就绪 · 填 App ID / Secret / 接收人"));
6931
+ var resetPlatStep = function (step) {
6932
+ setIaChStep(step);
6933
+ setImPlatMsg(null);
6934
+ setImPlatErr({});
6935
+ if (step === "feishu") loadImDeps();
6936
+ };
6609
6937
  return iaDialog(Object.assign({}, {
6610
6938
  title: "通知与消息通道",
6611
6939
  sub: "选一个平台,让电脑端在任务完成、出错或需要拍板时主动通知你",
@@ -6613,10 +6941,10 @@ window.__ModuleLoader__.load({
6613
6941
  foot: h("button", { type: "button", className: "dru-ia-btn", onClick: closeIAModal }, "关闭")
6614
6942
  }, { children: [h("div", { className: "dru-ia-list" },
6615
6943
  iaChannelRow("chat", "企业微信智能机器人", wecomSub, function () { setIaChStep("wecom"); setImBotsMsg(null); setImBotsFieldErr({}); }, "配置"),
6944
+ iaChannelRow("send", "钉钉机器人", dingSub, function () { resetPlatStep("dingtalk"); }, "配置"),
6945
+ iaChannelRow("send", "飞书机器人", feishuSub, function () { resetPlatStep("feishu"); }, imDeps && imDeps.installed ? "配置" : "下载依赖"),
6616
6946
  iaChannelRow("chat", "微信机器人(扫码绑定)", "扫码绑定 · 在微信里收通知 / 回数字拍板", function () { setIaChStep("wechat"); }, "打开"),
6617
- iaChannelRow("send", "飞书机器人", "即将支持 · 需要企业管理员配置应用", function () { setIaChStep("feishu"); }, "查看"),
6618
- iaChannelRow("send", "钉钉机器人", "即将支持 · 需要企业管理员创建应用", function () { setIaChStep("dingtalk"); }, "查看"),
6619
- iaChannelRow("code", "高级 · Webhook", "高级用法 · 自备公网端点", function () { setIaChStep("webhook"); }, "查看")
6947
+ iaChannelRow("code", "高级 · Webhook", "免费 · 高级用法:自备公网端点", function () { setIaChStep("webhook"); }, "查看")
6620
6948
  )] }));
6621
6949
  }
6622
6950