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

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,46 +6729,202 @@ window.__ModuleLoader__.load({
6721
6729
  return null;
6722
6730
  }
6723
6731
 
6724
- /** 钉钉配置页:Client ID / Client Secret / Robot Code(三个都要)。 */
6732
+ /** 钉钉扫码授权:开始(POST begin)→ 拿授权链接;deviceCode 只在服务端,不落浏览器。 */
6733
+ function startDingAuth() {
6734
+ if (imPlatBusy) return;
6735
+ setImPlatBusy("auth");
6736
+ setImPlatMsg(null);
6737
+ setImPlatErr({});
6738
+ post("/dsh-remote/im-dingtalk-auth/begin", {}).then(function (b) {
6739
+ if (!b || b.ok !== true || !b.url) {
6740
+ setImDingAuth(null);
6741
+ setImPlatMsg({ kind: "err", text: "发起授权失败:" + ((b && b.message) || "请稍后重试") });
6742
+ return;
6743
+ }
6744
+ setImDingAuth({
6745
+ authId: String(b.authId || ""),
6746
+ url: String(b.url || ""),
6747
+ interval: Math.max(1, Math.min(10, Number(b.interval) || 2)),
6748
+ expiresAt: Date.now() + Math.max(10, Math.min(600, Number(b.expiresIn) || 120)) * 1000,
6749
+ phase: "waiting",
6750
+ error: ""
6751
+ });
6752
+ }).catch(function (e) {
6753
+ setImDingAuth(null);
6754
+ var body = (e && e.body) || {};
6755
+ setImPlatMsg({ kind: "err", text: "发起授权失败:" + (body.message || (e && e.message) || "网络错误") });
6756
+ }).finally(function () { setImPlatBusy(""); });
6757
+ }
6758
+ /** 查询一次授权状态;成功后凭据已由服务端写盘 → 刷新面板即"已连接"。 */
6759
+ function pollDingAuth() {
6760
+ var a = imDingAuth;
6761
+ if (!a || !a.authId || a.phase !== "waiting") return;
6762
+ api("/dsh-remote/im-dingtalk-auth/status?id=" + encodeURIComponent(a.authId)).then(function (b) {
6763
+ if (b && b.status === "success") {
6764
+ setImDingAuth(null);
6765
+ setImPlatMsg({ kind: "ok", text: "已创建机器人并建立连接" + (b.clientIdTail ? "(Client ID 尾号 " + b.clientIdTail + ")" : "") + ",去钉钉里发条消息试试" });
6766
+ loadImBots();
6767
+ return;
6768
+ }
6769
+ if (b && (b.status === "failed" || b.status === "expired")) {
6770
+ setImDingAuth({ authId: a.authId, url: a.url, interval: a.interval, expiresAt: a.expiresAt, phase: "failed", error: String(b.message || "授权失败,请重试") });
6771
+ return;
6772
+ }
6773
+ if (Date.now() > a.expiresAt) {
6774
+ setImDingAuth({ authId: a.authId, url: a.url, interval: a.interval, expiresAt: a.expiresAt, phase: "failed", error: "授权已超时(2 分钟内未确认),请重新发起" });
6775
+ return;
6776
+ }
6777
+ // waiting:**换一个新对象** → 轮询 effect 依赖变化 → 继续排下一次(否则只会轮询一次)。
6778
+ setImDingAuth({ authId: a.authId, url: a.url, interval: a.interval, expiresAt: a.expiresAt, phase: "waiting", error: "", polledAt: Date.now() });
6779
+ }).catch(function () { /* 网络抖动:下一轮再试(轮询期内不打断用户) */ });
6780
+ }
6781
+ /** 等待确认中:每 ~2s 轮询一次 status(最长到 expiresAt)。 */
6782
+ useEffect(function () {
6783
+ if (iaModal !== "channel" || iaChStep !== "dingtalk") return undefined;
6784
+ if (!imDingAuth || imDingAuth.phase !== "waiting") return undefined;
6785
+ var t = null;
6786
+ try { t = setTimeout(pollDingAuth, Math.max(1000, (imDingAuth.interval || 2) * 1000)); } catch (e) { /* 忽略 */ }
6787
+ return function () { try { if (t) clearTimeout(t); } catch (e) { /* 忽略 */ } };
6788
+ }, [iaModal, iaChStep, imDingAuth]);
6789
+
6790
+ /** 钉钉手动创建 7 步(2026-11-08 业主给的官方链接;每步一句 + 链接)。 */
6791
+ var DINGTALK_STEPS = [
6792
+ { t: "用钉钉账号登录开发者后台", url: "https://open-dev.dingtalk.com/" },
6793
+ { t: "「应用开发 → 企业内部应用 → 钉钉应用 → 创建应用」,起个名字保存", url: "https://open.dingtalk.com/document/orgapp/create-an-application" },
6794
+ { t: "「基础信息 → 凭证与基础信息」复制 Client ID / Client Secret(Secret 只显示一次,丢了要重置)", url: "" },
6795
+ { t: "「应用能力 → 机器人」开启机器人配置(名称/图标/简介/预览图)", url: "https://open.dingtalk.com/document/orgapp/add-application-capabilities" },
6796
+ { t: "「消息接收模式」必须选 Stream 模式(不要选 HTTP、不要填地址),保存并发布机器人", url: "https://open.dingtalk.com/document/orgapp/configure-the-robot-application" },
6797
+ { t: "同一页找到 Robot Code(一般与 Client ID 相同)一起填", url: "https://open.dingtalk.com/document/orgapp/faq-robot" },
6798
+ { t: "「应用发布 → 版本管理与发布 → 创建新版本 → 发布」,回来点「测试连接」", url: "https://open.dingtalk.com/document/orgapp/robot-receive-message" }
6799
+ ];
6800
+ /** 三个高频坑(折叠底部,警示样式)。 */
6801
+ var DINGTALK_PITFALLS = [
6802
+ "建错机器人:群设置里加的「自定义机器人」只有 webhook 地址,没有 Client ID/Secret,也不可能长连接",
6803
+ "只保存没发布:机器人发布 + 应用版本发布是两步,漏任一步会 400 或群里搜不到机器人",
6804
+ "模式选错/凭证脏:消息接收模式留 HTTP、Secret 复制带空格、开了 IP 白名单 → 都会「连不上但报错含糊」;建议「连接成功才放行」"
6805
+ ];
6806
+
6807
+ /** 钉钉能力行:缺 Robot Code 时置灰并提示(业主:补 Robot Code 可开启)。 */
6808
+ function dingCapabilityRow(label, enabled, hint) {
6809
+ return h("div", {
6810
+ className: "dru-ia-state" + (enabled ? "" : " is-disabled"),
6811
+ "aria-disabled": enabled ? "false" : "true",
6812
+ title: enabled ? "" : hint
6813
+ },
6814
+ druIcon(enabled ? "check" : "alert"),
6815
+ label,
6816
+ enabled ? null : h("span", { className: "dru-ia-badge" }, hint));
6817
+ }
6818
+
6819
+ /** 钉钉配置页:主路径 = 扫码一键创建;手动创建 7 步 + 三个坑折叠在「高级」。 */
6725
6820
  function renderDingtalkStep(close) {
6726
6821
  var d = (imBots && imBots.dingtalk) || {};
6727
- var st = imBots && imBots.status && imBots.status.dingtalk;
6822
+ var st = (imBots && imBots.status && imBots.status.dingtalk) || {};
6728
6823
  var err = imPlatErr || {};
6824
+ var auth = imDingAuth;
6825
+ var sourceText = (st.credentialSource || d.credentialSource) === "scan" ? "扫码创建"
6826
+ : (st.credentialSource || d.credentialSource) === "manual" ? "手动填写" : "";
6827
+ var clientTail = String(d.clientId || "").length > 4 ? String(d.clientId).slice(-4) : String(d.clientId || "");
6828
+ // Robot Code 是否已配置:优先用桥状态;旧桥没这个字段时退回配置里的 robotCode。
6829
+ var robotCodeOk = st.robotCodeConfigured === true || !!String(d.robotCode || "").trim();
6830
+ var caps = st.capabilities || null;
6729
6831
  return iaDialog(Object.assign({}, {
6730
6832
  title: "钉钉机器人",
6731
- sub: "Client ID / Secret 就是钉钉应用的 AppKey / AppSecret;Robot Code 是机器人编码",
6833
+ sub: "不用去开发者后台创建应用:扫码点一下就会自动创建机器人",
6732
6834
  onClose: close,
6733
6835
  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
- } }, "测试")
6836
+ h("button", {
6837
+ type: "button", className: "dru-ia-btn is-primary", disabled: imPlatBusy !== "",
6838
+ onClick: function () { if (!auth || auth.phase !== "waiting") startDingAuth(); }
6839
+ }, imPlatBusy === "auth" ? "正在生成授权链接…" : (auth && auth.phase === "waiting" ? "重新发起扫码" : "用钉钉扫码,一键创建机器人")),
6840
+ h("button", { type: "button", className: "dru-ia-btn is-ghost", onClick: function () { close(); } }, "关闭")
6746
6841
  ]
6747
6842
  }, { 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 在机器人页。"),
6843
+ // 判断口诀(醒目):帮用户尽早排除"群 webhook 机器人"这个错误方向
6844
+ h("div", { className: "dru-ia-state is-warn", role: "note" },
6845
+ "要你填「消息接收地址」的,就是群 webhook 机器人——那种不能长连接,请退出重来"),
6846
+ h("div", { className: "dru-ia-hint" },
6847
+ "官方入口:",
6848
+ h("a", { className: "dru-ia-tier-link", href: "https://open.dingtalk.com/", target: "_blank", rel: "noopener" }, "钉钉开放平台"),
6849
+ " · ",
6850
+ h("a", { className: "dru-ia-tier-link", href: "https://open-dev.dingtalk.com/", target: "_blank", rel: "noopener" }, "开发者后台")),
6851
+ // 已连接状态:clientId 尾号 + 来源
6852
+ (st.connected || d.hasSecret)
6853
+ ? h("div", { className: "dru-ia-state is-ok" }, druIcon("check"),
6854
+ (st.connected ? "已连接" : "已保存凭据") + (clientTail ? "(Client ID 尾号 " + clientTail + ")" : "")
6855
+ + (sourceText ? " · 来源:" + sourceText : ""))
6856
+ : h("div", { className: "dru-ia-state" }, druIcon("chat"), "还没有连接:点下方按钮扫码创建机器人"),
6857
+ // 扫码授权中:链接 + 提示 + 复制/打开(无 QR 库 → 降级,明确告知用手机钉钉扫码)
6858
+ (auth && auth.phase === "waiting")
6859
+ ? h("div", { className: "dru-ia-field" },
6860
+ h("div", { className: "dru-ia-state", role: "status" }, h("span", { className: "dru-spin" }),
6861
+ "等待你在钉钉里确认…(用手机钉钉扫下方链接,或复制到手机打开)"),
6862
+ h("div", { className: "dru-copy-row" },
6863
+ h("div", { className: "dru-copy-box" },
6864
+ h("span", { className: "dru-link-val" }, auth.url),
6865
+ h("button", {
6866
+ type: "button", className: "dru-copy-btn", "aria-label": "复制授权链接",
6867
+ onClick: function () {
6868
+ copyText(auth.url).then(function (ok) { if (!ok) setImPlatMsg({ kind: "err", text: "复制失败:请手动选中链接复制" }); });
6869
+ }
6870
+ }, "复制授权链接")),
6871
+ h("div", { className: "dru-actions", style: { marginTop: 8 } },
6872
+ h("a", { className: "dru-ia-btn is-primary", href: auth.url, target: "_blank", rel: "noopener" }, "在浏览器打开授权页"),
6873
+ h("button", { type: "button", className: "dru-ia-btn", onClick: function () { setImDingAuth(null); setImPlatMsg({ kind: "ok", text: "已取消本次扫码授权" }); } }, "取消授权"))))
6874
+ : null,
6875
+ (auth && auth.phase === "failed")
6876
+ ? h("div", { className: "dru-ia-field" },
6877
+ h("div", { className: "dru-ia-state is-err", role: "alert" }, auth.error || "授权失败"),
6878
+ h("button", { type: "button", className: "dru-ia-btn", onClick: function () { startDingAuth(); } }, "重试扫码授权"))
6879
+ : null,
6880
+ // 能力:主动推送 / 卡片(缺 Robot Code 时置灰,提示补 Robot Code)
6881
+ h("div", { className: "dru-ia-field" },
6882
+ h("div", { className: "dru-ia-label" }, "机器人能力"),
6883
+ dingCapabilityRow("主动推送(任务完成/出错通知)", robotCodeOk && (!caps || caps.openapi !== false), "补 Robot Code 可开启"),
6884
+ dingCapabilityRow("卡片交互(审批卡片,点按钮放行/拒绝)", robotCodeOk && (!caps || caps.card !== false), "补 Robot Code 可开启")),
6885
+ // 高级:手动创建(7 步 + 三个坑 + 手动填凭据;默认折叠)
6886
+ h("div", { className: "dru-ia-field" },
6887
+ h("button", {
6888
+ type: "button", className: "dru-ia-disclose", "aria-expanded": imDingManual ? "true" : "false",
6889
+ onClick: function () { setImDingManual(!imDingManual); }
6890
+ }, "高级:去开发者后台手动创建(或手动填凭据)", druIcon(imDingManual ? "left" : "right")),
6891
+ imDingManual
6892
+ ? h("div", null,
6893
+ h("ol", { className: "dru-ia-steps" }, DINGTALK_STEPS.map(function (stp, i) {
6894
+ return h("li", { key: "dt" + i }, stp.t,
6895
+ stp.url
6896
+ ? h("a", { className: "dru-ia-tier-link", href: stp.url, target: "_blank", rel: "noopener", style: { marginLeft: 6 } }, "官方文档")
6897
+ : null);
6898
+ })),
6899
+ // 三个高频坑(警示)
6900
+ h("div", { className: "dru-ia-state is-warn", role: "alert" },
6901
+ h("div", null, "三个高频坑:"),
6902
+ h("ol", { className: "dru-ia-steps" }, DINGTALK_PITFALLS.map(function (t, i) {
6903
+ return h("li", { key: "pf" + i }, t);
6904
+ }))),
6905
+ // 手动填三层字段(已有应用的兜底)
6906
+ h("div", { className: "dru-ia-hint" }, "已有钉钉应用的话可以手填:Client ID/Secret 就是 AppKey/AppSecret;Robot Code 在机器人页复制。"),
6907
+ iaField("Client ID(= AppKey)", "dru-ding-client", h("input", {
6908
+ id: "dru-ding-client", className: "dru-ia-input", type: "text", value: imDingId, autoComplete: "off", spellCheck: false,
6909
+ placeholder: "钉钉应用 AppKey", "aria-invalid": err.clientId ? "true" : "false",
6910
+ onChange: function (e) { setImDingId(e.target.value); }
6911
+ }), err.clientId || ""),
6912
+ iaField("Client Secret(= AppSecret)", "dru-ding-secret", h("input", {
6913
+ id: "dru-ding-secret", className: "dru-ia-input", type: "password", value: imDingSecret, autoComplete: "new-password", spellCheck: false,
6914
+ placeholder: d.hasSecret ? "已保存(留空表示不修改)" : "钉钉应用 AppSecret", "aria-invalid": err.clientSecret ? "true" : "false",
6915
+ onChange: function (e) { setImDingSecret(e.target.value); }
6916
+ }), err.clientSecret || "", "只保存在本机;已保存的 Secret 永远不回显。"),
6917
+ iaField("Robot Code", "dru-ding-robot", h("input", {
6918
+ id: "dru-ding-robot", className: "dru-ia-input", type: "text", value: imDingRobot, autoComplete: "off", spellCheck: false,
6919
+ placeholder: "机器人编码(开放平台 → 机器人页复制)", "aria-invalid": err.robotCode ? "true" : "false",
6920
+ onChange: function (e) { setImDingRobot(e.target.value); }
6921
+ }), err.robotCode || "", "三个字段都要填。"),
6922
+ h("div", { className: "dru-actions", style: { marginTop: 8 } },
6923
+ h("button", { type: "button", className: "dru-ia-btn is-primary", disabled: imPlatBusy !== "", onClick: function () { saveImPlatform("dingtalk", "test"); } },
6924
+ imPlatBusy === "test" ? "连接中…" : "保存并连接"),
6925
+ h("button", { type: "button", className: "dru-ia-btn", disabled: imPlatBusy !== "", onClick: function () { saveImPlatform("dingtalk", "save"); } },
6926
+ imPlatBusy === "save" ? "保存中…" : "仅保存")))
6927
+ : null),
6764
6928
  imPlatStateLine(st)
6765
6929
  ] }));
6766
6930
  }
@@ -6787,16 +6951,29 @@ window.__ModuleLoader__.load({
6787
6951
  ] }));
6788
6952
  }
6789
6953
 
6790
- /** 飞书配置页(依赖已装):官方文档 + 8 步向导 + 三个字段。 */
6954
+ /** 飞书配置页(依赖已装):官方文档 + 3 步主路径(细步骤折叠)+ 只有 App ID / App Secret。 */
6791
6955
  function renderFeishuStep(close) {
6792
6956
  var deps = imDeps || {};
6793
6957
  if (deps.installed !== true) return renderFeishuDepsGate(close);
6794
6958
  var f = (imBots && imBots.feishu) || {};
6795
- var st = imBots && imBots.status && imBots.status.feishu;
6959
+ var st = (imBots && imBots.status && imBots.status.feishu) || {};
6796
6960
  var err = imPlatErr || {};
6961
+ // 自动绑定状态(业主:接收人不需要用户填,由插件自动发现)
6962
+ var bindLine = (function () {
6963
+ var bound = String(st.boundChatId || "").trim();
6964
+ if (!bound) return h("div", { className: "dru-ia-state" }, druIcon("chat"),
6965
+ "连接后会自动绑定你的机器人所在群/你与机器人的私聊(不需要操作)");
6966
+ var type = String(st.boundChatType || "").toLowerCase();
6967
+ var src = String(st.receiveIdSource || "").toLowerCase();
6968
+ var isP2p = type === "p2p" || src === "p2p" || src === "private";
6969
+ var name = isP2p ? "私聊" : (/^oc_/.test(bound) || /^ou_/.test(bound) ? ("群 " + iaShortId(bound)) : bound);
6970
+ return h("div", { className: "dru-ia-state is-ok" }, druIcon("check"),
6971
+ "已自动绑定:" + name,
6972
+ src ? h("span", { className: "dru-ia-hint", style: { marginLeft: 6 } }, "(自动发现,无需配置)") : null);
6973
+ })();
6797
6974
  return iaDialog(Object.assign({}, {
6798
6975
  title: "飞书机器人",
6799
- sub: "依赖已就绪(v" + (deps.version || f.sdkVersion || "?") + ");填 App ID / Secret / 接收人即可",
6976
+ sub: "依赖已就绪(v" + (deps.version || f.sdkVersion || "?") + ");只需填 App ID 与 App Secret",
6800
6977
  onClose: close,
6801
6978
  foot: [
6802
6979
  h("button", { type: "button", className: "dru-ia-btn is-primary", disabled: imPlatBusy !== "", onClick: function () { saveImPlatform("feishu", "test"); } },
@@ -6813,29 +6990,33 @@ window.__ModuleLoader__.load({
6813
6990
  } }, "测试")
6814
6991
  ]
6815
6992
  }, { children: [
6816
- // 官方文档固定放顶部(两个外链)
6993
+ // 官方入口固定顶部(业主给的两个链接)
6817
6994
  h("div", { className: "dru-ia-hint" },
6818
- "官方文档:",
6819
- h("a", { className: "dru-ia-tier-link", href: FEISHU_DOCS.createApp, target: "_blank", rel: "noopener" }, "飞书怎么创建应用"),
6995
+ "官方入口:",
6996
+ h("a", { className: "dru-ia-tier-link", href: FEISHU_DOCS.createApp, target: "_blank", rel: "noopener" }, "创建企业自建应用"),
6820
6997
  " · ",
6821
- h("a", { className: "dru-ia-tier-link", href: FEISHU_DOCS.longConn, target: "_blank", rel: "noopener" }, "长连接事件订阅")),
6822
- // 8 步向导(折叠;默认收起,避免一屏文字堆砌)
6998
+ h("a", { className: "dru-ia-tier-link", href: FEISHU_DOCS.cardBot, target: "_blank", rel: "noopener" }, "卡片交互机器人文档")),
6999
+ // 主路径只有 3 件事;细步骤折叠(业主:别让用户读 8 步才敢动手)
6823
7000
  h("div", { className: "dru-ia-field" },
7001
+ h("div", { className: "dru-ia-hint" }, "三步就能用:"),
7002
+ h("ol", { className: "dru-ia-steps" }, FEISHU_MAIN_STEPS.map(function (t, i) {
7003
+ return h("li", { key: "m" + i }, t);
7004
+ })),
6824
7005
  h("button", {
6825
7006
  type: "button", className: "dru-ia-disclose", "aria-expanded": imFeishuWizard ? "true" : "false",
6826
7007
  onClick: function () { setImFeishuWizard(!imFeishuWizard); }
6827
- }, "配置步骤(8 步)", druIcon(imFeishuWizard ? "left" : "right")),
7008
+ }, "细步骤(机器人能力 / 权限 / 长连接 / 发布)", druIcon(imFeishuWizard ? "left" : "right")),
6828
7009
  imFeishuWizard
6829
7010
  ? h("div", null,
6830
- h("ol", { className: "dru-ia-steps" }, FEISHU_STEPS.map(function (t, i) {
6831
- return h("li", { key: String(i) }, t);
7011
+ h("ol", { className: "dru-ia-steps" }, FEISHU_DETAIL_STEPS.map(function (t, i) {
7012
+ return h("li", { key: "d" + i }, t);
6832
7013
  })),
6833
- // 顺序坑(必须写明):事件配置保存时「长连接必须已在线」
6834
7014
  h("div", { className: "dru-ia-state is-warn", role: "note" },
6835
7015
  "顺序很重要:保存事件/回调配置时飞书的「长连接必须已在线」——",
6836
- "所以请**先在第 8 步填凭据让插件连上**,再回飞书后台保存事件配置;顺序反了会保存失败。"))
7016
+ "请**先在上面填凭据点「保存并连接」让插件连上**,再回飞书后台保存事件配置;顺序反了会保存失败。"))
6837
7017
  : null),
6838
- h("div", { className: "dru-ia-state" }, druIcon("send"), f.hasSecret ? "已保存凭据(Secret 不回显)" : "还没有保存凭据"),
7018
+ // 自动绑定状态行(不再让用户填接收人)
7019
+ bindLine,
6839
7020
  iaField("App ID", "dru-fs-appid", h("input", {
6840
7021
  id: "dru-fs-appid", className: "dru-ia-input", type: "text", value: imFeishuAppId, autoComplete: "off", spellCheck: false,
6841
7022
  placeholder: "cli_xxxxxxxx", "aria-invalid": err.appId ? "true" : "false",
@@ -6847,19 +7028,6 @@ window.__ModuleLoader__.load({
6847
7028
  "aria-invalid": err.appSecret ? "true" : "false",
6848
7029
  onChange: function (e) { setImFeishuSecret(e.target.value); }
6849
7030
  }), 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
7031
  imPlatStateLine(st)
6864
7032
  ] }));
6865
7033
  }