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

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,8 @@ 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
+ // 能力行置灰(缺 Robot Code 时不可用,但仍可读 + 有提示)
679
+ ".dru-ia-state.is-disabled{opacity:.6;background:var(--dru-surface-3);cursor:not-allowed}",
678
680
  // 飞书 8 步向导:编号列表紧凑可读(一屏内可滚,不撑破弹层)
679
681
  ".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
682
  ".dru-ia-steps li{margin:2px 0}",
@@ -3591,6 +3593,9 @@ window.__ModuleLoader__.load({
3591
3593
  var imDingIdArr = useState(""); var imDingId = imDingIdArr[0]; var setImDingId = imDingIdArr[1];
3592
3594
  var imDingSecretArr = useState(""); var imDingSecret = imDingSecretArr[0]; var setImDingSecret = imDingSecretArr[1];
3593
3595
  var imDingRobotArr = useState(""); var imDingRobot = imDingRobotArr[0]; var setImDingRobot = imDingRobotArr[1];
3596
+ // 钉钉扫码一键创建(2026-11-08):authId/授权链接/等待态;手动配置默认折叠
3597
+ var imDingAuthArr = useState(null); var imDingAuth = imDingAuthArr[0]; var setImDingAuth = imDingAuthArr[1];
3598
+ var imDingManualArr = useState(false); var imDingManual = imDingManualArr[0]; var setImDingManual = imDingManualArr[1];
3594
3599
  var imFeishuAppIdArr = useState(""); var imFeishuAppId = imFeishuAppIdArr[0]; var setImFeishuAppId = imFeishuAppIdArr[1];
3595
3600
  var imFeishuSecretArr = useState(""); var imFeishuSecret = imFeishuSecretArr[0]; var setImFeishuSecret = imFeishuSecretArr[1];
3596
3601
  var imFeishuRecvArr = useState(""); var imFeishuRecv = imFeishuRecvArr[0]; var setImFeishuRecv = imFeishuRecvArr[1];
@@ -6593,19 +6598,23 @@ window.__ModuleLoader__.load({
6593
6598
 
6594
6599
  /** 飞书官方文档(配置页顶部固定展示;外链一律 target=_blank + rel=noopener)。 */
6595
6600
  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"
6601
+ // 业主给的两个官方入口(必须出现在配置流程里)
6602
+ createApp: "https://open.feishu.cn/app?lang=zh-CN",
6603
+ cardBot: "https://open.feishu.cn/document/uAjLw4CM/ukzMukzMukzM/feishu-cards/quick-start/develop-a-card-interactive-bot"
6598
6604
  };
6599
- /** 8 步向导(业主/文档要求;顺序坑单独强调,见 renderFeishuStep 的提示)。 */
6600
- var FEISHU_STEPS = [
6601
- "创建企业自建应用:飞书开放平台 → 开发者后台 → 创建企业自建应用",
6602
- "复制 App ID / App Secret(应用详情 → 凭证与基础信息)",
6605
+ /** 主路径只有 3 件事(业主:接收人参数用户根本不知道怎么获取,别让用户配)。 */
6606
+ var FEISHU_MAIN_STEPS = [
6607
+ "创建企业自建应用(打开上面的「创建应用」链接)",
6608
+ "复制应用凭证里的 App ID / App Secret",
6609
+ "填到下面两个框,点「保存并连接」——接收群/私聊会自动发现,不用你配"
6610
+ ];
6611
+ /** 细步骤(折叠):开机器人能力 / 权限 / 事件与回调切长连接 / 发布。 */
6612
+ var FEISHU_DETAIL_STEPS = [
6603
6613
  "开通机器人能力:应用功能 → 机器人 → 启用",
6604
6614
  "申请权限:收 p2p / 群@消息,发 im:message:send_as_bot",
6605
6615
  "事件配置:订阅方式选「长连接」,添加「接收消息 v2.0」",
6606
6616
  "回调配置:订阅方式选「长连接」,添加 card.action.trigger",
6607
- "发布版本:版本管理与发布 → 创建版本 → 申请发布(管理员审核)",
6608
- "回本插件填 App ID / Secret / 接收人并点「保存并连接」"
6617
+ "发布版本:版本管理与发布 → 创建版本 → 申请发布(管理员审核)"
6609
6618
  ];
6610
6619
 
6611
6620
  /** GET /dsh-remote/im-deps:飞书 SDK 是否已装 + phase(下载进度轮询用)。 */
@@ -6652,11 +6661,10 @@ window.__ModuleLoader__.load({
6652
6661
  }
6653
6662
  /** 飞书表单提交体。 */
6654
6663
  function imFeishuPayload() {
6664
+ // 只提交两个凭据:接收群/私聊由桥侧自动发现(业主:不让用户填不知道怎么获取的 ID)。
6655
6665
  return {
6656
6666
  appId: String(imFeishuAppId || "").trim(),
6657
- appSecret: String(imFeishuSecret || ""),
6658
- receiveId: String(imFeishuRecv || "").trim(),
6659
- receiveIdType: String(imFeishuType || "open_id")
6667
+ appSecret: String(imFeishuSecret || "")
6660
6668
  };
6661
6669
  }
6662
6670
  function imPlatFieldErrOf(code) {
@@ -6721,47 +6729,256 @@ window.__ModuleLoader__.load({
6721
6729
  return null;
6722
6730
  }
6723
6731
 
6724
- /** 钉钉配置页:Client ID / Client Secret / Robot Code(三个都要)。 */
6732
+ /** 中文人话错误映射(客户端兜底:错误框里绝不出现裸 code/ok/英文原文)。 */
6733
+ var IM_HUMAN_ERR = {
6734
+ network_error: "连不上钉钉接口,请检查网络后重试",
6735
+ dingtalk_api_rejected: "钉钉拒绝了请求,请稍后重试",
6736
+ expired: "授权已过期,点「重试扫码」生成新的授权链接",
6737
+ cancelled: "这次授权已被新的授权取代,请用最新链接完成确认",
6738
+ write_failed: "凭据保存失败(磁盘/权限问题),请检查后重试"
6739
+ };
6740
+ function imHumanErr(code, message) {
6741
+ var c = String(code || "");
6742
+ var m = String(message || "").trim();
6743
+ var looksHuman = /[\u4e00-\u9fa5]/.test(m) && !/^\s*[{[]/.test(m);
6744
+ if (looksHuman) return m;
6745
+ return IM_HUMAN_ERR[c] || "操作失败,请稍后重试";
6746
+ }
6747
+
6748
+ /** 钉钉扫码授权:开始(POST begin)→ 拿授权链接;deviceCode 只在服务端,不落浏览器。
6749
+ * 轮询由**节点半后台**跑(关面板/刷新不影响),这里只查状态。 */
6750
+ function startDingAuth() {
6751
+ if (imPlatBusy) return;
6752
+ setImPlatBusy("auth");
6753
+ setImPlatMsg(null);
6754
+ setImPlatErr({});
6755
+ post("/dsh-remote/im-dingtalk-auth/begin", {}).then(function (b) {
6756
+ if (!b || b.ok !== true || !b.url) {
6757
+ setImDingAuth(null);
6758
+ setImPlatMsg({ kind: "err", text: "发起授权失败:" + imHumanErr(b && b.code, b && b.message) });
6759
+ return;
6760
+ }
6761
+ var expiresIn = Math.max(60, Math.min(7200, Number(b.expiresIn) || 7200));
6762
+ setImDingAuth({
6763
+ authId: String(b.authId || ""),
6764
+ url: String(b.url || ""),
6765
+ interval: Math.max(1, Math.min(10, Number(b.interval) || 3)),
6766
+ expiresAt: Date.now() + expiresIn * 1000,
6767
+ startedAt: Date.now(),
6768
+ phase: "waiting",
6769
+ error: ""
6770
+ });
6771
+ }).catch(function (e) {
6772
+ setImDingAuth(null);
6773
+ var body = (e && e.body) || {};
6774
+ setImPlatMsg({ kind: "err", text: "发起授权失败:" + imHumanErr(body.code || (e && e.code), body.message || (e && e.message)) });
6775
+ }).finally(function () { setImPlatBusy(""); });
6776
+ }
6777
+ /** 查询一次授权状态;manual=true = 用户点「我已完成授权,检查一下」(立刻让服务端补轮一次)。 */
6778
+ function pollDingAuth(manual) {
6779
+ var a = imDingAuth;
6780
+ if (!a || !a.authId || a.phase !== "waiting") return;
6781
+ var qs = "?id=" + encodeURIComponent(a.authId) + (manual ? "&check=1" : "");
6782
+ api("/dsh-remote/im-dingtalk-auth/status" + qs).then(function (b) {
6783
+ if (b && b.status === "success") {
6784
+ setImDingAuth(null); // 成功后自动收起授权块
6785
+ setImPlatMsg({
6786
+ kind: "ok",
6787
+ text: "已连接" + (b.clientIdTail ? " · Client ID 尾号 " + b.clientIdTail : "") + " · 来源:扫码创建"
6788
+ });
6789
+ loadImBots();
6790
+ return;
6791
+ }
6792
+ if (b && (b.status === "failed" || b.status === "expired")) {
6793
+ setImDingAuth({ authId: a.authId, url: a.url, interval: a.interval, expiresAt: a.expiresAt, startedAt: a.startedAt, phase: "failed", error: imHumanErr(b.code, b.message) });
6794
+ return;
6795
+ }
6796
+ // waiting:换新对象触发下一轮(服务端后台仍在推进,这里只负责刷新展示)
6797
+ setImDingAuth({ authId: a.authId, url: a.url, interval: a.interval, expiresAt: a.expiresAt, startedAt: a.startedAt, phase: "waiting", error: "", polledAt: Date.now() });
6798
+ }).catch(function (e) {
6799
+ // 网络抖动:不打断等待(手动按钮仍在),仅在自动轮询里静默
6800
+ if (manual) {
6801
+ var body = (e && e.body) || {};
6802
+ setImPlatMsg({ kind: "warn", text: "检查失败:" + imHumanErr(body.code, body.message || (e && e.message)) + "(可再试)" });
6803
+ }
6804
+ });
6805
+ }
6806
+ /** 等待确认中:自动轮询 ~3s/次;**最多自动轮 10 分钟**,之后保留状态由用户手动检查。 */
6807
+ useEffect(function () {
6808
+ if (iaModal !== "channel" || iaChStep !== "dingtalk") return undefined;
6809
+ if (!imDingAuth || imDingAuth.phase !== "waiting") return undefined;
6810
+ var waited = Date.now() - (imDingAuth.startedAt || Date.now());
6811
+ if (waited > 10 * 60 * 1000) return undefined; // 停止自动轮询(不再"2 分钟就判死")
6812
+ var t = null;
6813
+ try { t = setTimeout(function () { pollDingAuth(false); }, Math.max(1000, (imDingAuth.interval || 3) * 1000)); } catch (e) { /* 忽略 */ }
6814
+ return function () { try { if (t) clearTimeout(t); } catch (e) { /* 忽略 */ } };
6815
+ }, [iaModal, iaChStep, imDingAuth]);
6816
+
6817
+ /** 钉钉手动创建 7 步(2026-11-08 业主给的官方链接;每步一句 + 链接)。 */
6818
+ var DINGTALK_STEPS = [
6819
+ { t: "用钉钉账号登录开发者后台", url: "https://open-dev.dingtalk.com/" },
6820
+ { t: "「应用开发 → 企业内部应用 → 钉钉应用 → 创建应用」,起个名字保存", url: "https://open.dingtalk.com/document/orgapp/create-an-application" },
6821
+ { t: "「基础信息 → 凭证与基础信息」复制 Client ID / Client Secret(Secret 只显示一次,丢了要重置)", url: "" },
6822
+ { t: "「应用能力 → 机器人」开启机器人配置(名称/图标/简介/预览图)", url: "https://open.dingtalk.com/document/orgapp/add-application-capabilities" },
6823
+ { t: "「消息接收模式」必须选 Stream 模式(不要选 HTTP、不要填地址),保存并发布机器人", url: "https://open.dingtalk.com/document/orgapp/configure-the-robot-application" },
6824
+ { t: "同一页找到 Robot Code(一般与 Client ID 相同)一起填", url: "https://open.dingtalk.com/document/orgapp/faq-robot" },
6825
+ { t: "「应用发布 → 版本管理与发布 → 创建新版本 → 发布」,回来点「测试连接」", url: "https://open.dingtalk.com/document/orgapp/robot-receive-message" }
6826
+ ];
6827
+ /** 三个高频坑(折叠底部,警示样式)。 */
6828
+ var DINGTALK_PITFALLS = [
6829
+ "建错机器人:群设置里加的「自定义机器人」只有 webhook 地址,没有 Client ID/Secret,也不可能长连接",
6830
+ "只保存没发布:机器人发布 + 应用版本发布是两步,漏任一步会 400 或群里搜不到机器人",
6831
+ "模式选错/凭证脏:消息接收模式留 HTTP、Secret 复制带空格、开了 IP 白名单 → 都会「连不上但报错含糊」;建议「连接成功才放行」"
6832
+ ];
6833
+
6834
+ /** 钉钉能力行:缺 Robot Code 时置灰并提示(业主:补 Robot Code 可开启)。 */
6835
+ function dingCapabilityRow(label, enabled, hint) {
6836
+ return h("div", {
6837
+ className: "dru-ia-state" + (enabled ? "" : " is-disabled"),
6838
+ "aria-disabled": enabled ? "false" : "true",
6839
+ title: enabled ? "" : hint
6840
+ },
6841
+ druIcon(enabled ? "check" : "alert"),
6842
+ label,
6843
+ enabled ? null : h("span", { className: "dru-ia-badge" }, hint));
6844
+ }
6845
+
6846
+ /** 钉钉配置页:主路径 = 扫码一键创建;手动创建 7 步 + 三个坑折叠在「高级」。 */
6725
6847
  function renderDingtalkStep(close) {
6726
6848
  var d = (imBots && imBots.dingtalk) || {};
6727
- var st = imBots && imBots.status && imBots.status.dingtalk;
6849
+ var st = (imBots && imBots.status && imBots.status.dingtalk) || {};
6728
6850
  var err = imPlatErr || {};
6851
+ var auth = imDingAuth;
6852
+ var sourceText = (st.credentialSource || d.credentialSource) === "scan" ? "扫码创建"
6853
+ : (st.credentialSource || d.credentialSource) === "manual" ? "手动填写" : "";
6854
+ var clientTail = String(d.clientId || "").length > 4 ? String(d.clientId).slice(-4) : String(d.clientId || "");
6855
+ // Robot Code 是否已配置:优先用桥状态;旧桥没这个字段时退回配置里的 robotCode。
6856
+ var robotCodeOk = st.robotCodeConfigured === true || !!String(d.robotCode || "").trim();
6857
+ var caps = st.capabilities || null;
6729
6858
  return iaDialog(Object.assign({}, {
6730
6859
  title: "钉钉机器人",
6731
- sub: "Client ID / Secret 就是钉钉应用的 AppKey / AppSecret;Robot Code 是机器人编码",
6860
+ sub: "不用去开发者后台创建应用:扫码点一下就会自动创建机器人",
6732
6861
  onClose: close,
6733
6862
  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
- } }, "测试")
6863
+ h("button", {
6864
+ type: "button", className: "dru-ia-btn is-primary", disabled: imPlatBusy !== "",
6865
+ onClick: function () { if (!auth || auth.phase !== "waiting") startDingAuth(); }
6866
+ }, imPlatBusy === "auth" ? "正在生成授权链接…" : (auth && auth.phase === "waiting" ? "重新发起扫码" : "用钉钉扫码,一键创建机器人")),
6867
+ h("button", { type: "button", className: "dru-ia-btn is-ghost", onClick: function () { close(); } }, "关闭")
6746
6868
  ]
6747
6869
  }, { 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)
6870
+ // 判断口诀(醒目):帮用户尽早排除"群 webhook 机器人"这个错误方向
6871
+ h("div", { className: "dru-ia-state is-warn", role: "note" },
6872
+ "要你填「消息接收地址」的,就是群 webhook 机器人——那种不能长连接,请退出重来"),
6873
+ h("div", { className: "dru-ia-hint" },
6874
+ "官方入口:",
6875
+ h("a", { className: "dru-ia-tier-link", href: "https://open.dingtalk.com/", target: "_blank", rel: "noopener" }, "钉钉开放平台"),
6876
+ " · ",
6877
+ h("a", { className: "dru-ia-tier-link", href: "https://open-dev.dingtalk.com/", target: "_blank", rel: "noopener" }, "开发者后台")),
6878
+ // 状态行(2026-11-08 业主:"未配置"不许说成"连接失败"):
6879
+ // · 没有任何凭据 → 「未配置」(不是错误)
6880
+ // · 桥明确报 failed/error → 「连接失败:<中文人话>」
6881
+ // · 有凭据/已连接 → 「已连接 · Client ID 尾号 · 来源:…」
6882
+ (function () {
6883
+ var configured = !!d.enabled || !!d.hasSecret || !!String(d.credentialSource || "").trim();
6884
+ var failState = st.state === "failed" || st.state === "error";
6885
+ if (!configured) return h("div", { className: "dru-ia-state" }, druIcon("chat"), "未配置:点下方按钮扫码创建机器人");
6886
+ if (failState) {
6887
+ var human = imHumanErr("", String(st.lastError || ""));
6888
+ return h("div", { className: "dru-ia-state is-err", role: "status" }, "连接失败:" + human);
6889
+ }
6890
+ var tailBit = clientTail ? " · Client ID 尾号 " + clientTail : "";
6891
+ var srcBit = sourceText ? " · 来源:" + sourceText : "";
6892
+ if (st.connected) return h("div", { className: "dru-ia-state is-ok" }, druIcon("check"), "已连接" + tailBit + srcBit);
6893
+ return h("div", { className: "dru-ia-state" }, druIcon("chat"), "已保存凭据,等待连接…" + tailBit + srcBit);
6894
+ })(),
6895
+ // 扫码授权中:链接 + 提示 + 复制/打开(无 QR 库 → 降级,明确告知用手机钉钉扫码)
6896
+ (auth && auth.phase === "waiting")
6897
+ ? h("div", { className: "dru-ia-field" },
6898
+ h("div", { className: "dru-ia-state", role: "status" }, h("span", { className: "dru-spin" }),
6899
+ "等待你在钉钉里确认…(用手机钉钉扫下方链接,或复制到手机打开)"),
6900
+ (function () {
6901
+ var waitedMin = Math.floor((Date.now() - (auth.startedAt || Date.now())) / 60000);
6902
+ return h("div", { className: "dru-ia-hint" },
6903
+ waitedMin >= 10
6904
+ ? "已等待 " + waitedMin + " 分钟:自动刷新先停下(后台仍在继续),完成授权后点「我已完成授权,检查一下」。"
6905
+ : "授权链接 2 小时内有效;关掉面板也不影响,回来点「我已完成授权,检查一下」即可。");
6906
+ })(),
6907
+ h("button", {
6908
+ type: "button", className: "dru-ia-btn is-primary", style: { alignSelf: "flex-start" },
6909
+ onClick: function () { pollDingAuth(true); }
6910
+ }, "我已完成授权,检查一下"),
6911
+ h("div", { className: "dru-copy-row" },
6912
+ h("div", { className: "dru-copy-box" },
6913
+ h("span", { className: "dru-link-val" }, auth.url),
6914
+ h("button", {
6915
+ type: "button", className: "dru-copy-btn", "aria-label": "复制授权链接",
6916
+ onClick: function () {
6917
+ copyText(auth.url).then(function (ok) { if (!ok) setImPlatMsg({ kind: "err", text: "复制失败:请手动选中链接复制" }); });
6918
+ }
6919
+ }, "复制授权链接")),
6920
+ h("div", { className: "dru-actions", style: { marginTop: 8 } },
6921
+ h("a", { className: "dru-ia-btn is-primary", href: auth.url, target: "_blank", rel: "noopener" }, "在浏览器打开授权页"),
6922
+ h("button", { type: "button", className: "dru-ia-btn", onClick: function () { setImDingAuth(null); setImPlatMsg({ kind: "ok", text: "已取消本次扫码授权" }); } }, "取消授权"))))
6923
+ : null,
6924
+ (auth && auth.phase === "failed")
6925
+ ? h("div", { className: "dru-ia-field" },
6926
+ h("div", { className: "dru-ia-state is-err", role: "alert" }, auth.error || "授权失败"),
6927
+ h("button", { type: "button", className: "dru-ia-btn", onClick: function () { startDingAuth(); } }, "重试扫码授权"))
6928
+ : null,
6929
+ // 能力:主动推送 / 卡片(缺 Robot Code 时置灰,提示补 Robot Code)
6930
+ h("div", { className: "dru-ia-field" },
6931
+ h("div", { className: "dru-ia-label" }, "机器人能力"),
6932
+ dingCapabilityRow("主动推送(任务完成/出错通知)", robotCodeOk && (!caps || caps.openapi !== false), "补 Robot Code 可开启"),
6933
+ dingCapabilityRow("卡片交互(审批卡片,点按钮放行/拒绝)", robotCodeOk && (!caps || caps.card !== false), "补 Robot Code 可开启")),
6934
+ // 高级:手动创建(7 步 + 三个坑 + 手动填凭据;默认折叠)
6935
+ h("div", { className: "dru-ia-field" },
6936
+ h("button", {
6937
+ type: "button", className: "dru-ia-disclose", "aria-expanded": imDingManual ? "true" : "false",
6938
+ onClick: function () { setImDingManual(!imDingManual); }
6939
+ }, "高级:去开发者后台手动创建(或手动填凭据)", druIcon(imDingManual ? "left" : "right")),
6940
+ imDingManual
6941
+ ? h("div", null,
6942
+ h("ol", { className: "dru-ia-steps" }, DINGTALK_STEPS.map(function (stp, i) {
6943
+ return h("li", { key: "dt" + i }, stp.t,
6944
+ stp.url
6945
+ ? h("a", { className: "dru-ia-tier-link", href: stp.url, target: "_blank", rel: "noopener", style: { marginLeft: 6 } }, "官方文档")
6946
+ : null);
6947
+ })),
6948
+ // 三个高频坑(警示)
6949
+ h("div", { className: "dru-ia-state is-warn", role: "alert" },
6950
+ h("div", null, "三个高频坑:"),
6951
+ h("ol", { className: "dru-ia-steps" }, DINGTALK_PITFALLS.map(function (t, i) {
6952
+ return h("li", { key: "pf" + i }, t);
6953
+ }))),
6954
+ // 手动填三层字段(已有应用的兜底)
6955
+ h("div", { className: "dru-ia-hint" }, "已有钉钉应用的话可以手填:Client ID/Secret 就是 AppKey/AppSecret;Robot Code 在机器人页复制。"),
6956
+ iaField("Client ID(= AppKey)", "dru-ding-client", h("input", {
6957
+ id: "dru-ding-client", className: "dru-ia-input", type: "text", value: imDingId, autoComplete: "off", spellCheck: false,
6958
+ placeholder: "钉钉应用 AppKey", "aria-invalid": err.clientId ? "true" : "false",
6959
+ onChange: function (e) { setImDingId(e.target.value); }
6960
+ }), err.clientId || ""),
6961
+ iaField("Client Secret(= AppSecret)", "dru-ding-secret", h("input", {
6962
+ id: "dru-ding-secret", className: "dru-ia-input", type: "password", value: imDingSecret, autoComplete: "new-password", spellCheck: false,
6963
+ placeholder: d.hasSecret ? "已保存(留空表示不修改)" : "钉钉应用 AppSecret", "aria-invalid": err.clientSecret ? "true" : "false",
6964
+ onChange: function (e) { setImDingSecret(e.target.value); }
6965
+ }), err.clientSecret || "", "只保存在本机;已保存的 Secret 永远不回显。"),
6966
+ iaField("Robot Code", "dru-ding-robot", h("input", {
6967
+ id: "dru-ding-robot", className: "dru-ia-input", type: "text", value: imDingRobot, autoComplete: "off", spellCheck: false,
6968
+ placeholder: "机器人编码(开放平台 → 机器人页复制)", "aria-invalid": err.robotCode ? "true" : "false",
6969
+ onChange: function (e) { setImDingRobot(e.target.value); }
6970
+ }), err.robotCode || "", "三个字段都要填。"),
6971
+ h("div", { className: "dru-actions", style: { marginTop: 8 } },
6972
+ h("button", { type: "button", className: "dru-ia-btn is-primary", disabled: imPlatBusy !== "", onClick: function () { saveImPlatform("dingtalk", "test"); } },
6973
+ imPlatBusy === "test" ? "连接中…" : "保存并连接"),
6974
+ h("button", { type: "button", className: "dru-ia-btn", disabled: imPlatBusy !== "", onClick: function () { saveImPlatform("dingtalk", "save"); } },
6975
+ imPlatBusy === "save" ? "保存中…" : "仅保存")))
6976
+ : null),
6977
+ // 平台级消息(中文人话;绝不裸渲染 ok/code)
6978
+ imPlatMsg
6979
+ ? h("div", { className: "dru-ia-state " + (imPlatMsg.kind === "ok" ? "is-ok" : imPlatMsg.kind === "warn" ? "is-warn" : "is-err"), role: "status" },
6980
+ imPlatMsg.text)
6981
+ : null
6765
6982
  ] }));
6766
6983
  }
6767
6984
 
@@ -6787,16 +7004,29 @@ window.__ModuleLoader__.load({
6787
7004
  ] }));
6788
7005
  }
6789
7006
 
6790
- /** 飞书配置页(依赖已装):官方文档 + 8 步向导 + 三个字段。 */
7007
+ /** 飞书配置页(依赖已装):官方文档 + 3 步主路径(细步骤折叠)+ 只有 App ID / App Secret。 */
6791
7008
  function renderFeishuStep(close) {
6792
7009
  var deps = imDeps || {};
6793
7010
  if (deps.installed !== true) return renderFeishuDepsGate(close);
6794
7011
  var f = (imBots && imBots.feishu) || {};
6795
- var st = imBots && imBots.status && imBots.status.feishu;
7012
+ var st = (imBots && imBots.status && imBots.status.feishu) || {};
6796
7013
  var err = imPlatErr || {};
7014
+ // 自动绑定状态(业主:接收人不需要用户填,由插件自动发现)
7015
+ var bindLine = (function () {
7016
+ var bound = String(st.boundChatId || "").trim();
7017
+ if (!bound) return h("div", { className: "dru-ia-state" }, druIcon("chat"),
7018
+ "连接后会自动绑定你的机器人所在群/你与机器人的私聊(不需要操作)");
7019
+ var type = String(st.boundChatType || "").toLowerCase();
7020
+ var src = String(st.receiveIdSource || "").toLowerCase();
7021
+ var isP2p = type === "p2p" || src === "p2p" || src === "private";
7022
+ var name = isP2p ? "私聊" : (/^oc_/.test(bound) || /^ou_/.test(bound) ? ("群 " + iaShortId(bound)) : bound);
7023
+ return h("div", { className: "dru-ia-state is-ok" }, druIcon("check"),
7024
+ "已自动绑定:" + name,
7025
+ src ? h("span", { className: "dru-ia-hint", style: { marginLeft: 6 } }, "(自动发现,无需配置)") : null);
7026
+ })();
6797
7027
  return iaDialog(Object.assign({}, {
6798
7028
  title: "飞书机器人",
6799
- sub: "依赖已就绪(v" + (deps.version || f.sdkVersion || "?") + ");填 App ID / Secret / 接收人即可",
7029
+ sub: "依赖已就绪(v" + (deps.version || f.sdkVersion || "?") + ");只需填 App ID 与 App Secret",
6800
7030
  onClose: close,
6801
7031
  foot: [
6802
7032
  h("button", { type: "button", className: "dru-ia-btn is-primary", disabled: imPlatBusy !== "", onClick: function () { saveImPlatform("feishu", "test"); } },
@@ -6813,29 +7043,33 @@ window.__ModuleLoader__.load({
6813
7043
  } }, "测试")
6814
7044
  ]
6815
7045
  }, { children: [
6816
- // 官方文档固定放顶部(两个外链)
7046
+ // 官方入口固定顶部(业主给的两个链接)
6817
7047
  h("div", { className: "dru-ia-hint" },
6818
- "官方文档:",
6819
- h("a", { className: "dru-ia-tier-link", href: FEISHU_DOCS.createApp, target: "_blank", rel: "noopener" }, "飞书怎么创建应用"),
7048
+ "官方入口:",
7049
+ h("a", { className: "dru-ia-tier-link", href: FEISHU_DOCS.createApp, target: "_blank", rel: "noopener" }, "创建企业自建应用"),
6820
7050
  " · ",
6821
- h("a", { className: "dru-ia-tier-link", href: FEISHU_DOCS.longConn, target: "_blank", rel: "noopener" }, "长连接事件订阅")),
6822
- // 8 步向导(折叠;默认收起,避免一屏文字堆砌)
7051
+ h("a", { className: "dru-ia-tier-link", href: FEISHU_DOCS.cardBot, target: "_blank", rel: "noopener" }, "卡片交互机器人文档")),
7052
+ // 主路径只有 3 件事;细步骤折叠(业主:别让用户读 8 步才敢动手)
6823
7053
  h("div", { className: "dru-ia-field" },
7054
+ h("div", { className: "dru-ia-hint" }, "三步就能用:"),
7055
+ h("ol", { className: "dru-ia-steps" }, FEISHU_MAIN_STEPS.map(function (t, i) {
7056
+ return h("li", { key: "m" + i }, t);
7057
+ })),
6824
7058
  h("button", {
6825
7059
  type: "button", className: "dru-ia-disclose", "aria-expanded": imFeishuWizard ? "true" : "false",
6826
7060
  onClick: function () { setImFeishuWizard(!imFeishuWizard); }
6827
- }, "配置步骤(8 步)", druIcon(imFeishuWizard ? "left" : "right")),
7061
+ }, "细步骤(机器人能力 / 权限 / 长连接 / 发布)", druIcon(imFeishuWizard ? "left" : "right")),
6828
7062
  imFeishuWizard
6829
7063
  ? h("div", null,
6830
- h("ol", { className: "dru-ia-steps" }, FEISHU_STEPS.map(function (t, i) {
6831
- return h("li", { key: String(i) }, t);
7064
+ h("ol", { className: "dru-ia-steps" }, FEISHU_DETAIL_STEPS.map(function (t, i) {
7065
+ return h("li", { key: "d" + i }, t);
6832
7066
  })),
6833
- // 顺序坑(必须写明):事件配置保存时「长连接必须已在线」
6834
7067
  h("div", { className: "dru-ia-state is-warn", role: "note" },
6835
7068
  "顺序很重要:保存事件/回调配置时飞书的「长连接必须已在线」——",
6836
- "所以请**先在第 8 步填凭据让插件连上**,再回飞书后台保存事件配置;顺序反了会保存失败。"))
7069
+ "请**先在上面填凭据点「保存并连接」让插件连上**,再回飞书后台保存事件配置;顺序反了会保存失败。"))
6837
7070
  : null),
6838
- h("div", { className: "dru-ia-state" }, druIcon("send"), f.hasSecret ? "已保存凭据(Secret 不回显)" : "还没有保存凭据"),
7071
+ // 自动绑定状态行(不再让用户填接收人)
7072
+ bindLine,
6839
7073
  iaField("App ID", "dru-fs-appid", h("input", {
6840
7074
  id: "dru-fs-appid", className: "dru-ia-input", type: "text", value: imFeishuAppId, autoComplete: "off", spellCheck: false,
6841
7075
  placeholder: "cli_xxxxxxxx", "aria-invalid": err.appId ? "true" : "false",
@@ -6847,19 +7081,6 @@ window.__ModuleLoader__.load({
6847
7081
  "aria-invalid": err.appSecret ? "true" : "false",
6848
7082
  onChange: function (e) { setImFeishuSecret(e.target.value); }
6849
7083
  }), 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
7084
  imPlatStateLine(st)
6864
7085
  ] }));
6865
7086
  }