diff --git a/lib/client.js b/lib/client.js index 9bc4b15..65ebb88 100644 --- a/lib/client.js +++ b/lib/client.js @@ -298,7 +298,7 @@ var CSS = String.raw` .ddt-heading h2 { font-size: 20px; line-height: 28px; font-weight: 680; } .ddt-heading p { margin-top: 5px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 20px; white-space: nowrap; } .ddt-tools, .ddt-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; } -.ddt-tools { justify-content: flex-end; } +.ddt-tools { width: 100%; justify-content: space-between; flex-wrap: nowrap; } .ddt-badge { min-height: 30px; display: inline-flex; align-items: center; gap: 7px; padding: 0 11px; border-radius: 999px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-fill-secondary, #f2f3f5); font-size: 12px; white-space: nowrap; } .ddt-dot { width: 8px; height: 8px; flex: none; border-radius: 50%; background: #aeb3bb; } .ddt-dot[data-tone="success"] { background: var(--ddt-success); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ddt-success) 14%, transparent); } @@ -364,6 +364,8 @@ var CSS = String.raw` .ddt-metric dt { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; } .ddt-metric dd { overflow: hidden; margin: 5px 0 0; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; } .ddt-accountFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-line-divider, #eef0f3); } +.ddt-accountFooter .ddt-actions { flex: none; flex-wrap: nowrap; gap: 8px; margin-top: 0; } +.ddt-accountFooter .ddt-button { flex: none; white-space: nowrap; } .ddt-summary { color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; } .ddt-confirm { padding: 18px 24px; border-top: 1px solid color-mix(in srgb, var(--ddt-error) 25%, transparent); background: color-mix(in srgb, var(--ddt-error) 5%, transparent); } .ddt-confirm strong { display: block; margin-bottom: 6px; font-size: 14px; } @@ -385,9 +387,8 @@ var CSS = String.raw` .ddt-metric { padding: 10px; } } @media (max-width: 720px) { - .ddt-heading, .ddt-accountTop, .ddt-accountFooter { flex-direction: column; align-items: stretch; } + .ddt-heading, .ddt-accountTop { flex-direction: column; align-items: stretch; } .ddt-heading p { white-space: normal; } - .ddt-tools { justify-content: flex-start; } .ddt-empty { grid-template-columns: minmax(0, 1fr); } .ddt-brandMark { display: none; } .ddt-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; } @@ -452,57 +453,50 @@ function Heading({ totals, adding, busy, onAdd, addButtonRef }) { h2( "div", { className: "ddt-tools" }, - totals.configured > 0 ? h2( - "div", - { className: "ddt-badge" }, - h2("span", { - className: "ddt-dot", - "data-tone": totals.connected > 0 ? "success" : "warning" - }), - h2("span", null, `${totals.connected} / ${totals.configured} \u5728\u7EBF`) - ) : null, - h2("div", { - className: "ddt-badge", - title: "\u5E94\u7528\u5BC6\u94A5\u53EA\u5199\u5165 Harness Host \u51ED\u636E\u670D\u52A1\uFF0C\u4E0D\u4F1A\u53D1\u9001\u5230\u6D4F\u89C8\u5668" - }, "\u51ED\u636E\u4EC5\u4FDD\u5B58\u5728\u672C\u673A"), h2(Button, { kind: "primary", + className: "dim-scanButton", onClick: onAdd, disabled: adding || busy, ref: addButtonRef - }, adding ? "\u6B63\u5728\u63A5\u5165" : "\u626B\u7801\u63A5\u5165\u9489\u9489") + }, adding ? "\u6B63\u5728\u63A5\u5165" : "\u626B\u7801\u63A5\u5165\u9489\u9489"), + totals.configured > 0 ? h2( + "div", + { className: "ddt-badge dim-onlineBadge" }, + h2("span", null, `${totals.connected} / ${totals.configured} \u5728\u7EBF`) + ) : null ) ); } function LoadingView() { return h2( "div", - { className: "ddt-card ddt-loading", "aria-busy": "true" }, - h2("div", { className: "ddt-spinner" }), + { className: "ddt-card ddt-loading dim-surfaceCard dim-loadingView", "aria-busy": "true" }, + h2("div", { className: "ddt-spinner dim-spinner" }), h2("span", null, "\u6B63\u5728\u8BFB\u53D6\u9489\u9489\u8FDE\u63A5\u72B6\u6001\u2026") ); } function EmptyView({ busy, onStart }) { return h2( "div", - { className: "ddt-card" }, + { className: "ddt-card dim-surfaceCard" }, h2( "div", - { className: "ddt-cardBody ddt-empty" }, + { className: "ddt-cardBody ddt-empty dim-surfaceBody dim-emptyView" }, h2( "div", - null, + { className: "dim-emptyCopy" }, h2( "div", - { className: "ddt-stateLabel" }, - h2("span", { className: "ddt-dot" }), + { className: "ddt-stateLabel dim-stateLabel" }, + h2("span", { className: "ddt-dot dim-stateDot" }), h2("span", null, "\u5C1A\u672A\u63A5\u5165\u9489\u9489\u673A\u5668\u4EBA") ), h2("h3", null, "\u626B\u4E00\u6B21\u7801\uFF0C\u81EA\u52A8\u521B\u5EFA\u5E76\u8FDE\u63A5\u673A\u5668\u4EBA"), h2("p", null, "\u6388\u6743\u7531\u9489\u9489\u5B98\u65B9\u9875\u9762\u5B8C\u6210\u3002\u626B\u7801\u8D26\u53F7\u5FC5\u987B\u5DF2\u52A0\u5165\u4E00\u4E2A\u4F01\u4E1A/\u7EC4\u7EC7\u5E76\u6709\u6743\u521B\u5EFA\u673A\u5668\u4EBA\uFF1B\u521B\u5EFA\u6210\u529F\u540E\uFF0C\u5E94\u7528\u51ED\u636E\u4F1A\u76F4\u63A5\u5199\u5165 Harness Host\u3002"), h2( "div", - { className: "ddt-actions" }, + { className: "ddt-actions dim-viewActions" }, h2( Button, { kind: "primary", onClick: onStart, disabled: busy }, @@ -512,7 +506,7 @@ function EmptyView({ busy, onStart }) { ), h2( "div", - { className: "ddt-brandMark", "aria-hidden": "true" }, + { className: "ddt-brandMark dim-emptyBrand", "aria-hidden": "true" }, h2(DingtalkIcon, { size: 68 }) ) ) @@ -528,60 +522,60 @@ function QrPanel({ provision, now, busy, onRefresh, onCancel }) { React2.useEffect(() => setImageFailed(false), [source]); return h2( "div", - { className: "ddt-card" }, + { className: "ddt-card dim-surfaceCard" }, h2( "div", - { className: "ddt-cardBody ddt-qrLayout" }, + { className: "ddt-cardBody ddt-qrLayout dim-surfaceBody dim-qrLayout" }, h2( "div", - { className: "ddt-qrColumn" }, + { className: "ddt-qrColumn dim-qrColumn" }, h2( "div", - { className: "ddt-qrFrame" }, + { className: "ddt-qrFrame dim-qrFrame" }, source && !imageFailed ? h2("img", { src: source, alt: "\u7528\u4E8E\u628A\u9489\u9489\u673A\u5668\u4EBA\u63A5\u5165 DeepSeek Harness \u7684\u4E00\u6B21\u6027\u4E8C\u7EF4\u7801", onError: () => setImageFailed(true) - }) : h2("div", { className: "ddt-qrFallback" }, "\u4E8C\u7EF4\u7801\u56FE\u7247\u672A\u5C31\u7EEA\uFF0C\u8BF7\u91CD\u65B0\u751F\u6210\u3002"), - expired ? h2("div", { className: "ddt-expired" }, "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\n\u8BF7\u91CD\u65B0\u751F\u6210") : null + }) : h2("div", { className: "ddt-qrFallback dim-qrFallback" }, "\u4E8C\u7EF4\u7801\u56FE\u7247\u672A\u5C31\u7EEA\uFF0C\u8BF7\u91CD\u65B0\u751F\u6210\u3002"), + expired ? h2("div", { className: "ddt-expired dim-qrExpired" }, "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\n\u8BF7\u91CD\u65B0\u751F\u6210") : null ), h2( "div", - { className: "ddt-countdown" }, + { className: "ddt-countdown dim-countdown" }, h2( "div", - { className: "ddt-countdownTop" }, + { className: "ddt-countdownTop dim-countdownTop" }, h2("span", null, "\u4E8C\u7EF4\u7801\u6709\u6548\u65F6\u95F4"), h2("strong", null, formatRemaining(remaining)) ), h2( "div", - { className: "ddt-progress", "aria-hidden": "true" }, + { className: "ddt-progress dim-progress", "aria-hidden": "true" }, h2("span", { style: { "--ddt-progress": `${progress}%` } }) ) ) ), h2( "div", - { className: "ddt-qrCopy" }, + { className: "ddt-qrCopy dim-qrCopy" }, h2( "div", - { className: "ddt-stateLabel" }, - h2("span", { className: "ddt-dot", "data-tone": expired ? "error" : "warning" }), + { className: "ddt-stateLabel dim-stateLabel" }, + h2("span", { className: "ddt-dot dim-stateDot", "data-tone": expired ? "error" : "warning" }), h2("span", null, expired ? "\u4E8C\u7EF4\u7801\u5DF2\u5931\u6548" : "\u7B49\u5F85\u9489\u9489\u626B\u7801\u6388\u6743") ), h2("h3", null, expired ? "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801\u540E\u7EE7\u7EED" : "\u4F7F\u7528\u9489\u9489 App \u5B8C\u6210\u673A\u5668\u4EBA\u6388\u6743"), h2("p", null, "\u626B\u7801\u8D26\u53F7\u5FC5\u987B\u5DF2\u52A0\u5165\u4F01\u4E1A/\u7EC4\u7EC7\u3002\u5982\u679C\u9489\u9489\u63D0\u793A\u5C1A\u672A\u52A0\u5165\u7EC4\u7EC7\uFF0C\u8BF7\u5728\u63D0\u793A\u9875\u521B\u5EFA\u7EC4\u7EC7\uFF0C\u6216\u6362\u7528\u5DF2\u52A0\u5165\u7EC4\u7EC7\u7684\u8D26\u53F7\u3002"), h2( "ol", - { className: "ddt-steps" }, + { className: "ddt-steps dim-steps" }, h2("li", null, "\u4F7F\u7528\u5DF2\u52A0\u5165\u4F01\u4E1A/\u7EC4\u7EC7\u7684\u9489\u9489\u8D26\u53F7\u626B\u63CF\u5DE6\u4FA7\u4E8C\u7EF4\u7801"), h2("li", null, "\u5728\u6388\u6743\u9875\u70B9\u51FB\u201C\u4E00\u952E\u521B\u5EFA\u65B0\u673A\u5668\u4EBA\u201D"), h2("li", null, "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u673A\u5668\u4EBA\u81EA\u52A8\u8FDE\u63A5") ), h2( "div", - { className: "ddt-actions" }, + { className: "ddt-actions dim-viewActions" }, expired ? h2(Button, { kind: "primary", onClick: onRefresh, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801") : null, !expired ? h2(Button, { onClick: onRefresh, disabled: busy }, "\u6362\u4E00\u4E2A\u4E8C\u7EF4\u7801") : null, h2(Button, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88") @@ -595,13 +589,13 @@ function ProgressPanel({ status, busy, onCancel }) { const creating = status === "creating"; return h2( "div", - { className: "ddt-card ddt-loading", "aria-busy": "true" }, - h2("div", { className: "ddt-spinner" }), + { className: "ddt-card ddt-loading dim-surfaceCard dim-loadingView", "aria-busy": "true" }, + h2("div", { className: "ddt-spinner dim-spinner" }), h2("h3", null, connecting ? "\u673A\u5668\u4EBA\u5DF2\u521B\u5EFA\uFF0C\u6B63\u5728\u5EFA\u7ACB\u6D88\u606F\u8FDE\u63A5" : creating ? "\u6388\u6743\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u521B\u5EFA\u9489\u9489\u673A\u5668\u4EBA" : "\u6B63\u5728\u786E\u8BA4\u9489\u9489\u6388\u6743"), h2("p", null, connecting ? "\u6B63\u5728\u68C0\u67E5\u9489\u9489 Stream \u957F\u8FDE\u63A5\uFF0C\u6210\u529F\u540E\u4F1A\u81EA\u52A8\u663E\u793A\u4E3A\u5728\u7EBF\u3002" : "\u8BF7\u52FF\u5173\u95ED\u672C\u9875\uFF0C\u9489\u9489\u5B8C\u6210\u6388\u6743\u540E\u5C06\u81EA\u52A8\u7EE7\u7EED\u3002"), h2( "div", - { className: "ddt-actions", style: { justifyContent: "center", marginTop: 14 } }, + { className: "ddt-actions dim-viewActions", style: { justifyContent: "center", marginTop: 14 } }, h2(Button, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u63A5\u5165") ) ); @@ -613,16 +607,16 @@ function ProvisionError({ provision, busy, onRetry, onClose }) { }; return h2( "div", - { className: "ddt-card" }, + { className: "ddt-card dim-surfaceCard" }, h2( "div", - { className: "ddt-inlineError", role: "alert" }, + { className: "ddt-inlineError dim-inlineError", role: "alert" }, h2("h3", null, provision.status === "expired" ? "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F" : "\u9489\u9489\u673A\u5668\u4EBA\u6CA1\u6709\u63A5\u5165\u5B8C\u6210"), h2("p", null, error.message), h2("span", { className: "ddt-errorCode" }, error.code), h2( "div", - { className: "ddt-actions" }, + { className: "ddt-actions dim-viewActions" }, h2(Button, { kind: "primary", onClick: onRetry, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801"), h2(Button, { onClick: onClose, disabled: busy }, "\u5173\u95ED") ) @@ -647,7 +641,7 @@ function RemoveConfirmation({ account, busy, onConfirm, onCancel }) { return h2( "div", { - className: "ddt-confirm", + className: "ddt-confirm dim-confirm", role: "alertdialog", "aria-label": `\u79FB\u9664${account.bot.name}`, onKeyDown: (event) => { @@ -658,7 +652,7 @@ function RemoveConfirmation({ account, busy, onConfirm, onCancel }) { h2("p", null, "\u8FD9\u4F1A\u505C\u6B62\u6D88\u606F\u8FDE\u63A5\uFF0C\u5E76\u5220\u9664\u672C\u673A\u4FDD\u5B58\u7684\u5E94\u7528\u51ED\u636E\u3001\u673A\u5668\u4EBA\u914D\u7F6E\u53CA\u4F1A\u8BDD\u6620\u5C04\u3002\u9489\u9489\u5F00\u653E\u5E73\u53F0\u4E2D\u7684\u673A\u5668\u4EBA\u4E0D\u4F1A\u88AB\u81EA\u52A8\u5220\u9664\u3002"), h2( "div", - { className: "ddt-actions" }, + { className: "ddt-actions dim-viewActions" }, h2(Button, { ref: cancelRef, onClick: onCancel, disabled: busy }, "\u4FDD\u7559\u673A\u5668\u4EBA"), h2( Button, @@ -682,62 +676,62 @@ function AccountCard({ const stateLabel = account.connected ? "\u8FD0\u884C\u6B63\u5E38" : state === "connecting" ? "\u6B63\u5728\u8FDE\u63A5" : "\u8FDE\u63A5\u672A\u5C31\u7EEA"; return h2( "article", - { className: "ddt-card", tabIndex: -1, "data-bot-id": account.botId }, + { className: "ddt-card dim-botCard", tabIndex: -1, "data-bot-id": account.botId }, h2( "div", - { className: "ddt-cardBody" }, + { className: "ddt-cardBody dim-botCardBody" }, h2( "div", - { className: "ddt-accountTop" }, + { className: "ddt-accountTop dim-botCardTop" }, h2( "div", - { className: "ddt-accountIdentity" }, - h2("div", { className: "ddt-avatar", "aria-hidden": "true" }, h2(DingtalkIcon, { size: 29 })), + { className: "ddt-accountIdentity dim-botIdentity" }, + h2("div", { className: "ddt-avatar dim-botAvatar", "aria-hidden": "true" }, h2(DingtalkIcon, { size: 29 })), h2( "div", - null, + { className: "dim-botName" }, h2("h3", { title: account.bot.name }, account.bot.name), h2("p", { title: account.bot.clientIdMasked }, account.bot.clientIdMasked) ) ), h2( "div", - { className: "ddt-health" }, - h2("span", { className: "ddt-dot", "data-tone": tone }), + { className: "ddt-health dim-botHealth" }, + h2("span", { className: "ddt-dot dim-healthDot", "data-tone": tone }), h2("span", null, stateLabel) ) ), h2( "dl", - { className: "ddt-metrics" }, + { className: "ddt-metrics dim-botMetrics" }, h2( "div", - { className: "ddt-metric" }, + { className: "ddt-metric dim-botMetric" }, h2("dt", null, "\u6D88\u606F\u901A\u9053"), h2("dd", null, account.connected ? "Stream \u957F\u8FDE\u63A5" : "\u79BB\u7EBF") ), h2( "div", - { className: "ddt-metric" }, + { className: "ddt-metric dim-botMetric" }, h2("dt", null, "\u6700\u8FD1\u68C0\u67E5"), h2("dd", null, checkedTime(account.health.lastCheckedAt)) ) ), h2( "div", - { className: "ddt-accountFooter" }, - h2("div", { className: "ddt-summary" }, account.error?.message ?? account.health.summary), + { className: "ddt-accountFooter dim-cardFooter" }, + h2("div", { className: "ddt-summary dim-cardSummary" }, account.error?.message ?? account.health.summary), h2( "div", - { className: "ddt-actions" }, + { className: "ddt-actions dim-cardActions" }, h2( Button, - { onClick: onReconnect, disabled: Boolean(busy) }, + { className: "dim-cardAction", onClick: onReconnect, disabled: Boolean(busy) }, busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5" ), h2( Button, - { kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) }, + { className: "dim-cardAction", kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) }, "\u79FB\u9664\u63A5\u5165" ) ) @@ -754,14 +748,14 @@ function AccountCard({ function AccountList(props) { return h2( "section", - null, + { className: "dim-listSection" }, h2( "div", - { className: "ddt-listHeading" }, + { className: "ddt-listHeading dim-listHeading" }, h2("h3", null, "\u5DF2\u63A5\u5165\u7684\u9489\u9489\u673A\u5668\u4EBA"), h2("span", null, `${props.bots.length} \u4E2A`) ), - h2("ul", { className: "ddt-list" }, props.bots.map((account) => h2( + h2("ul", { className: "ddt-list dim-botList" }, props.bots.map((account) => h2( "li", { key: account.botId }, h2(AccountCard, { @@ -1098,7 +1092,7 @@ function DingtalkSettingsTab({ rpcCall }) { } return h2( "section", - { className: "ddt-page", "aria-label": "\u9489\u9489\u8BBE\u7F6E" }, + { className: "ddt-page dim-channelPage", "aria-label": "\u9489\u9489\u8BBE\u7F6E" }, h2(Heading, { totals: model.totals, adding: Boolean(provision), @@ -1107,13 +1101,13 @@ function DingtalkSettingsTab({ rpcCall }) { addButtonRef }), h2("div", { className: "ddt-visuallyHidden", role: "status", "aria-live": "polite" }, notice), - model.error && model.phase === "ready" ? h2("div", { className: "ddt-statusNotice", role: "alert" }, `\u72B6\u6001\u5237\u65B0\u5931\u8D25\uFF1A${model.error.message}`) : null, + model.error && model.phase === "ready" ? h2("div", { className: "ddt-statusNotice dim-statusNotice", role: "alert" }, `\u72B6\u6001\u5237\u65B0\u5931\u8D25\uFF1A${model.error.message}`) : null, model.phase === "loading" ? h2(LoadingView) : model.phase === "error" ? h2( "div", - { className: "ddt-card" }, + { className: "ddt-card dim-surfaceCard" }, h2( "div", - { className: "ddt-inlineError", role: "alert" }, + { className: "ddt-inlineError dim-inlineError", role: "alert" }, h2("h3", null, "\u65E0\u6CD5\u8BFB\u53D6\u9489\u9489\u673A\u5668\u4EBA\u72B6\u6001"), h2("p", null, model.error?.message ?? "\u8BF7\u7A0D\u540E\u91CD\u8BD5"), h2(Button, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6") @@ -1496,7 +1490,7 @@ var CSS2 = String.raw` flex: none; display: flex; align-items: center; - justify-content: flex-end; + justify-content: space-between; flex-wrap: nowrap; gap: 8px; } @@ -1517,22 +1511,6 @@ var CSS2 = String.raw` .bxf-totalBadge strong { color: var(--bxf-success); font-size: 13px; } -.bxf-localBadge { - flex: none; - display: inline-flex; - align-items: center; - gap: 6px; - min-height: 28px; - border: 1px solid var(--dsw-alias-border-l2, #dee0e3); - border-radius: 999px; - padding: 4px 10px; - color: var(--dsw-alias-label-secondary, #646a73); - background: var(--dsw-alias-bg-layer-1, #fff); - font-size: 11px; - line-height: 16px; - white-space: nowrap; -} - .bxf-card { position: relative; overflow: hidden; @@ -1860,12 +1838,11 @@ var CSS2 = String.raw` .bxf-metric dt { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; line-height: 17px; } .bxf-metric dd { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-primary, #1f2329); font-size: 12px; line-height: 18px; font-weight: 550; margin: 3px 0 0; } -.bxf-divider { height: 1px; background: var(--dsw-alias-border-l2, #dee0e3); margin: 22px 0 0; } -.bxf-connectedFooter { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-top: 17px; } +.bxf-connectedFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-line-divider, #eef0f3); } .bxf-healthSummary { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; } -.bxf-healthSummary strong { color: var(--dsw-alias-label-primary, #1f2329); font-weight: 600; } .bxf-healthSummary[data-error="true"] { color: var(--bxf-error); } -.bxf-botActions { margin-top: 0; justify-content: flex-end; } +.bxf-botActions { flex: none; flex-wrap: nowrap; gap: 8px; margin-top: 0; justify-content: flex-end; } +.bxf-botActions .bxf-button { flex: none; white-space: nowrap; } .bxf-confirm { border-top: 1px solid var(--dsw-alias-border-l2, #dee0e3); @@ -1914,8 +1891,7 @@ var CSS2 = String.raw` @container (max-width: 620px) { .bxf-headingTools { gap: 6px; } - .bxf-headingTools .bxf-totalBadge, .bxf-headingTools .bxf-localBadge { padding-inline: 8px; } - .bxf-headingTools .bxf-localBadge { gap: 4px; } + .bxf-headingTools .bxf-totalBadge { padding-inline: 8px; } .bxf-headingTools .bxf-bindButton { padding-inline: 10px; } } @@ -1925,9 +1901,7 @@ var CSS2 = String.raw` .bxf-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; } .bxf-qrCopy { width: 100%; } .bxf-statusGrid { grid-template-columns: minmax(0, 1fr); } - .bxf-connectedTop, .bxf-connectedFooter { align-items: flex-start; flex-direction: column; } - .bxf-botActions { width: 100%; justify-content: flex-start; } - .bxf-botActions .bxf-button { min-height: 44px; } + .bxf-connectedTop { align-items: flex-start; flex-direction: column; } .bxf-inlineError { grid-template-columns: minmax(0, 1fr); padding: 20px; } .bxf-statusNotice { align-items: flex-start; flex-wrap: wrap; } .bxf-cardBody { padding: 20px; } @@ -1973,25 +1947,6 @@ function SvgIcon({ children, size = 18, className, viewBox = "0 0 24 24" }) { className }, children); } -function ShieldIcon({ size = 18 }) { - return h3( - SvgIcon, - { size }, - h3("path", { - d: "M12 3 5.5 5.8v5.1c0 4.25 2.72 7.87 6.5 9.1 3.78-1.23 6.5-4.85 6.5-9.1V5.8L12 3Z", - stroke: "currentColor", - strokeWidth: "1.7", - strokeLinejoin: "round" - }), - h3("path", { - d: "m9.3 11.8 1.7 1.7 3.8-4", - stroke: "currentColor", - strokeWidth: "1.7", - strokeLinecap: "round", - strokeLinejoin: "round" - }) - ); -} function RobotIcon({ size = 26 }) { return h3( SvgIcon, @@ -2013,40 +1968,6 @@ function RobotIcon({ size = 26 }) { }) ); } -function SparkIcon({ size = 18 }) { - return h3( - SvgIcon, - { size }, - h3("path", { - d: "M12 2.8c.75 3.67 2.7 5.62 6.4 6.4-3.7.77-5.65 2.72-6.4 6.4-.75-3.68-2.7-5.63-6.4-6.4 3.7-.78 5.65-2.73 6.4-6.4Z", - stroke: "currentColor", - strokeWidth: "1.55", - strokeLinejoin: "round" - }), - h3("path", { - d: "M5.2 15.8c.35 1.7 1.28 2.63 3 3-1.72.36-2.65 1.29-3 3-.36-1.71-1.29-2.64-3-3 1.71-.37 2.64-1.3 3-3ZM18.7 2.7c.22 1.06.79 1.63 1.85 1.85-1.06.22-1.63.79-1.85 1.85-.22-1.06-.79-1.63-1.85-1.85 1.06-.22 1.63-.79 1.85-1.85Z", - fill: "currentColor" - }) - ); -} -function RefreshIcon({ size = 16 }) { - return h3(SvgIcon, { size }, h3("path", { - d: "M19 7.5V4m0 0h-3.5M19 4l-2.1 2.1A7 7 0 1 0 19 13", - stroke: "currentColor", - strokeWidth: "1.8", - strokeLinecap: "round", - strokeLinejoin: "round" - })); -} -function ExternalIcon({ size = 15 }) { - return h3(SvgIcon, { size }, h3("path", { - d: "M13 5h6v6M19 5l-8.5 8.5M18 13.5V18a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4.5", - stroke: "currentColor", - strokeWidth: "1.7", - strokeLinecap: "round", - strokeLinejoin: "round" - })); -} function AlertIcon({ size = 22 }) { return h3( SvgIcon, @@ -2092,78 +2013,64 @@ function Heading2({ totals, onAdd, adding, busy, addButtonRef }) { h3( "div", { className: "bxf-headingTools" }, - hasBots ? h3("div", { - className: "bxf-totalBadge", - "aria-label": `\u5DF2\u63A5\u5165 ${totals.configured} \u4E2A\u673A\u5668\u4EBA\uFF0C\u5176\u4E2D ${totals.connected} \u4E2A\u5728\u7EBF` - }, h3("strong", null, totals.connected), h3("span", null, `/ ${totals.configured} \u5728\u7EBF`)) : null, - h3("div", { - className: "bxf-localBadge", - title: "\u6BCF\u4E2A\u5E94\u7528\u7684\u51ED\u636E\u5747\u7531 Host \u72EC\u7ACB\u4FDD\u5B58\uFF0C\u4E0D\u4F1A\u53D1\u9001\u5230\u6D4F\u89C8\u5668" - }, h3(ShieldIcon, { size: 14 }), h3("span", null, "\u51ED\u636E\u4EC5\u4FDD\u5B58\u5728\u672C\u673A")), h3(Button3, { kind: "primary", size: "small", - className: "bxf-bindButton", + className: "bxf-bindButton dim-scanButton", onClick: onAdd, disabled: adding || busy, ref: addButtonRef, "aria-busy": busy ? "true" : void 0 - }, adding ? "\u6B63\u5728\u7ED1\u5B9A" : "\u626B\u7801\u7ED1\u5B9A\u673A\u5668\u4EBA") + }, adding ? "\u6B63\u5728\u7ED1\u5B9A" : "\u626B\u7801\u7ED1\u5B9A\u673A\u5668\u4EBA"), + hasBots ? h3("div", { + className: "bxf-totalBadge dim-onlineBadge", + "aria-label": `\u5DF2\u63A5\u5165 ${totals.configured} \u4E2A\u673A\u5668\u4EBA\uFF0C\u5176\u4E2D ${totals.connected} \u4E2A\u5728\u7EBF` + }, h3("span", null, `${totals.connected} / ${totals.configured} \u5728\u7EBF`)) : null ) ); } function LoadingView2() { - return h3("div", { - className: "bxf-card", - "aria-busy": "true", - "aria-label": "\u6B63\u5728\u8BFB\u53D6\u98DE\u4E66\u673A\u5668\u4EBA\u5217\u8868" - }, h3( + return h3( "div", - { className: "bxf-skeleton" }, - h3("div", { className: "bxf-skeletonLine" }), - h3("div", { className: "bxf-skeletonLine" }), - h3("div", { className: "bxf-skeletonLine" }), - h3("div", { className: "bxf-skeletonLine" }), - h3("div", { className: "bxf-skeletonBox" }) - )); + { + className: "bxf-card dim-surfaceCard dim-loadingView", + "aria-busy": "true", + "aria-label": "\u6B63\u5728\u8BFB\u53D6\u98DE\u4E66\u673A\u5668\u4EBA\u5217\u8868" + }, + h3("div", { className: "dim-spinner", "aria-hidden": "true" }), + h3("span", null, "\u6B63\u5728\u8BFB\u53D6\u98DE\u4E66\u8FDE\u63A5\u72B6\u6001\u2026") + ); } function EmptyView2({ onStart, busy }) { return h3( "div", - { className: "bxf-card" }, + { className: "bxf-card dim-surfaceCard" }, h3( "div", - { className: "bxf-cardBody bxf-intro" }, + { className: "bxf-cardBody bxf-intro dim-surfaceBody dim-emptyView" }, h3( "div", - { className: "bxf-introCopy" }, + { className: "bxf-introCopy dim-emptyCopy" }, h3( "div", - { className: "bxf-stateLabel" }, - h3("span", { className: "bxf-dot" }), + { className: "bxf-stateLabel dim-stateLabel" }, + h3("span", { className: "bxf-dot dim-stateDot" }), h3("span", null, "\u5C1A\u672A\u63A5\u5165\u673A\u5668\u4EBA") ), h3("h3", null, "\u626B\u7801\uFF0C\u521B\u5EFA\u7B2C\u4E00\u4E2A\u98DE\u4E66\u5165\u53E3"), h3("p", null, "\u65E0\u9700\u624B\u52A8\u586B\u5199 App ID\u3002\u4EE5\u540E\u8FD8\u53EF\u4EE5\u7EE7\u7EED\u6DFB\u52A0\u673A\u5668\u4EBA\uFF0C\u5206\u522B\u670D\u52A1\u4E0D\u540C\u56E2\u961F\u6216\u98DE\u4E66\u79DF\u6237\u3002"), h3( "div", - { className: "bxf-actions" }, + { className: "bxf-actions dim-viewActions" }, h3(Button3, { kind: "primary", - icon: h3(SparkIcon), onClick: onStart, disabled: busy, "aria-busy": busy ? "true" : void 0 - }, busy ? "\u6B63\u5728\u521B\u5EFA\u2026" : "\u4E00\u952E\u521B\u5EFA\u98DE\u4E66\u673A\u5668\u4EBA") - ), - h3( - "div", - { className: "bxf-note" }, - h3(ShieldIcon, { size: 16 }), - h3("span", null, "\u6BCF\u4E2A App Secret \u90FD\u53EA\u5199\u5165 Host \u51ED\u636E\u5B58\u50A8\uFF0C\u6D4F\u89C8\u5668\u4E0D\u4F1A\u6536\u5230 Secret\u3002") + }, busy ? "\u6B63\u5728\u751F\u6210\u4E8C\u7EF4\u7801\u2026" : "\u751F\u6210\u98DE\u4E66\u4E8C\u7EF4\u7801") ) ), - h3("div", { className: "bxf-markStage", "aria-hidden": "true" }, h3(BrandMark)) + h3("div", { className: "bxf-markStage dim-emptyBrand", "aria-hidden": "true" }, h3(BrandMark)) ) ); } @@ -2190,74 +2097,73 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) { React4.useEffect(() => setImageFailed(false), [qrSource]); return h3( "div", - { className: "bxf-card bxf-provisionCard" }, + { className: "bxf-card bxf-provisionCard dim-surfaceCard" }, h3( "div", - { className: "bxf-cardBody bxf-qrLayout" }, + { className: "bxf-cardBody bxf-qrLayout dim-surfaceBody dim-qrLayout" }, h3( "div", - { className: "bxf-qrColumn" }, + { className: "bxf-qrColumn dim-qrColumn" }, h3( "div", - { className: "bxf-qrFrame" }, + { className: "bxf-qrFrame dim-qrFrame" }, qrSource && !imageFailed ? h3("img", { src: qrSource, alt: "\u7528\u4E8E\u65B0\u589E DeepSeek Harness \u98DE\u4E66\u673A\u5668\u4EBA\u7684\u4E00\u6B21\u6027\u6388\u6743\u4E8C\u7EF4\u7801", onError: () => setImageFailed(true) }) : h3( "div", - { className: "bxf-qrFallback" }, + { className: "bxf-qrFallback dim-qrFallback" }, h3("div", null, h3(QrIcon), h3("span", null, "\u4E8C\u7EF4\u7801\u672A\u5C31\u7EEA\uFF0C\u8BF7\u6253\u5F00\u6388\u6743\u94FE\u63A5")) ), expired ? h3( "div", - { className: "bxf-expiredOverlay", role: "status" }, + { className: "bxf-expiredOverlay dim-qrExpired", role: "status" }, h3("div", null, "\u4E8C\u7EF4\u7801\u5DF2\u5931\u6548", h3("br"), "\u8BF7\u5237\u65B0\u540E\u91CD\u65B0\u626B\u7801") ) : null ), h3( "div", { - className: "bxf-countdown", + className: "bxf-countdown dim-countdown", "aria-label": expired ? "\u4E8C\u7EF4\u7801\u5DF2\u5931\u6548" : `\u4E8C\u7EF4\u7801\u5269\u4F59 ${formatRemaining2(remaining)}` }, h3( "div", - { className: "bxf-countdownTop", "aria-hidden": "true" }, + { className: "bxf-countdownTop dim-countdownTop", "aria-hidden": "true" }, h3("span", null, expired ? "\u7B49\u5F85\u5237\u65B0" : "\u4E8C\u7EF4\u7801\u6709\u6548\u65F6\u95F4"), h3("strong", null, formatRemaining2(remaining)) ), h3( "div", - { className: "bxf-progress", "aria-hidden": "true" }, + { className: "bxf-progress dim-progress", "aria-hidden": "true" }, h3("span", { style: { "--bxf-progress": `${Math.round(progress * 100)}%` } }) ) ) ), h3( "div", - { className: "bxf-qrCopy" }, + { className: "bxf-qrCopy dim-qrCopy" }, h3( "div", - { className: "bxf-stateLabel" }, - h3("span", { className: "bxf-dot", "data-tone": "warning" }), + { className: "bxf-stateLabel dim-stateLabel" }, + h3("span", { className: "bxf-dot dim-stateDot", "data-tone": "warning" }), h3("span", null, "\u6B63\u5728\u6DFB\u52A0\u65B0\u673A\u5668\u4EBA") ), h3("h3", null, expired ? "\u5237\u65B0\u4E8C\u7EF4\u7801\u540E\u7EE7\u7EED" : "\u4F7F\u7528\u98DE\u4E66\u626B\u7801\u521B\u5EFA\u673A\u5668\u4EBA"), h3("p", null, "\u626B\u7801\u53EA\u4F1A\u65B0\u589E\u4E00\u4E2A\u673A\u5668\u4EBA\uFF0C\u5DF2\u63A5\u5165\u7684\u673A\u5668\u4EBA\u4F1A\u7EE7\u7EED\u6B63\u5E38\u6536\u53D1\u6D88\u606F\u3002"), h3( "ol", - { className: "bxf-steps" }, + { className: "bxf-steps dim-steps" }, h3("li", null, "\u6253\u5F00\u98DE\u4E66\u79FB\u52A8\u7AEF\uFF0C\u4F7F\u7528\u626B\u4E00\u626B\u8BFB\u53D6\u4E8C\u7EF4\u7801"), h3("li", null, "\u6838\u5BF9\u5E94\u7528\u540D\u79F0\u4E0E\u6743\u9650\u8303\u56F4\uFF0C\u5E76\u786E\u8BA4\u521B\u5EFA"), h3("li", null, "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u65B0\u673A\u5668\u4EBA\u7684\u957F\u8FDE\u63A5\u5C31\u7EEA") ), h3( "div", - { className: "bxf-actions" }, + { className: "bxf-actions dim-viewActions" }, expired ? h3(Button3, { kind: "primary", - icon: h3(RefreshIcon), onClick: onRefresh, disabled: busy }, busy ? "\u5237\u65B0\u4E2D\u2026" : "\u5237\u65B0\u4E8C\u7EF4\u7801") : href ? h3("a", { @@ -2266,8 +2172,8 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) { href, target: "_blank", rel: "noopener noreferrer" - }, h3(ExternalIcon), h3("span", null, "\u5728\u98DE\u4E66\u4E2D\u6253\u5F00")) : null, - !expired ? h3(Button3, { icon: h3(RefreshIcon), onClick: onRefresh, disabled: busy }, "\u6362\u4E00\u4E2A\u4E8C\u7EF4\u7801") : null, + }, h3("span", null, "\u5728\u98DE\u4E66\u4E2D\u6253\u5F00")) : null, + !expired ? h3(Button3, { onClick: onRefresh, disabled: busy }, "\u6362\u4E00\u4E2A\u4E8C\u7EF4\u7801") : null, h3(Button3, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u6DFB\u52A0") ) ) @@ -2278,41 +2184,27 @@ function ProvisionProgress({ phase, onCancel, busy }) { const connecting = phase === "connecting"; return h3( "div", - { className: "bxf-card bxf-provisionCard", "aria-busy": "true" }, - h3( + { + className: "bxf-card bxf-provisionCard dim-surfaceCard dim-loadingView", + "aria-busy": "true" + }, + h3("div", { className: "dim-spinner", "aria-hidden": "true" }), + h3("h3", null, connecting ? "\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u8FDE\u63A5\u65B0\u673A\u5668\u4EBA" : "\u6B63\u5728\u51C6\u5907\u6388\u6743\u4E8C\u7EF4\u7801"), + h3("p", null, connecting ? "\u6B63\u5728\u5B89\u5168\u4FDD\u5B58\u51ED\u636E\u5E76\u68C0\u67E5\u65B0\u673A\u5668\u4EBA\u7684\u6D88\u606F\u901A\u9053\uFF0C\u5176\u4ED6\u673A\u5668\u4EBA\u4E0D\u4F1A\u4E2D\u65AD\u3002" : "\u6B63\u5728\u5411\u98DE\u4E66\u7533\u8BF7\u4E00\u6B21\u6027\u6388\u6743\u4E8C\u7EF4\u7801\uFF0C\u8BF7\u7A0D\u5019\u3002"), + connecting ? h3( "div", - { className: "bxf-connecting bxf-connectingCompact" }, - h3( - "div", - { className: "bxf-connectingCopy" }, - h3( - "div", - { className: "bxf-orbit" }, - h3( - "div", - { className: "bxf-orbitCore" }, - connecting ? h3(RobotIcon, { size: 24 }) : h3(SparkIcon, { size: 24 }) - ) - ), - h3("h3", null, connecting ? "\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u8FDE\u63A5\u65B0\u673A\u5668\u4EBA" : "\u6B63\u5728\u51C6\u5907\u6388\u6743\u4E8C\u7EF4\u7801"), - h3("p", null, connecting ? "\u6B63\u5728\u5B89\u5168\u4FDD\u5B58\u51ED\u636E\u5E76\u68C0\u67E5\u65B0\u673A\u5668\u4EBA\u7684\u6D88\u606F\u901A\u9053\uFF0C\u5176\u4ED6\u673A\u5668\u4EBA\u4E0D\u4F1A\u4E2D\u65AD\u3002" : "\u6B63\u5728\u5411\u98DE\u4E66\u7533\u8BF7\u4E00\u6B21\u6027\u6388\u6743\u4E8C\u7EF4\u7801\uFF0C\u8BF7\u7A0D\u5019\u3002"), - connecting ? h3( - "div", - { className: "bxf-actions", style: { justifyContent: "center" } }, - h3(Button3, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u6DFB\u52A0") - ) : null - ) - ) + { className: "bxf-actions dim-viewActions", style: { justifyContent: "center" } }, + h3(Button3, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88\u6DFB\u52A0") + ) : null ); } function ProvisionError2({ error, onRetry, onCancel, busy }) { return h3( "div", - { className: "bxf-card bxf-provisionCard" }, + { className: "bxf-card bxf-provisionCard dim-surfaceCard" }, h3( "div", - { className: "bxf-inlineError", role: "alert" }, - h3("div", { className: "bxf-errorIcon" }, h3(AlertIcon)), + { className: "bxf-inlineError dim-inlineError", role: "alert" }, h3( "div", null, @@ -2321,10 +2213,10 @@ function ProvisionError2({ error, onRetry, onCancel, busy }) { error.code ? h3("span", { className: "bxf-errorCode" }, error.code) : null, h3( "div", - { className: "bxf-actions" }, + { className: "bxf-actions dim-viewActions" }, h3( Button3, - { kind: "primary", icon: h3(RefreshIcon), onClick: onRetry, disabled: busy }, + { kind: "primary", onClick: onRetry, disabled: busy }, busy ? "\u91CD\u8BD5\u4E2D\u2026" : "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801" ), h3(Button3, { onClick: onCancel, disabled: busy }, "\u5173\u95ED") @@ -2360,7 +2252,7 @@ function RemoveConfirmation2({ bot, busy, onConfirm, onCancel }) { return h3( "div", { - className: "bxf-confirm", + className: "bxf-confirm dim-confirm", role: "alertdialog", "aria-labelledby": titleId, "aria-describedby": descriptionId, @@ -2379,7 +2271,7 @@ function RemoveConfirmation2({ bot, busy, onConfirm, onCancel }) { ), h3( "div", - { className: "bxf-actions" }, + { className: "bxf-actions dim-viewActions" }, h3(Button3, { ref: cancelRef, onClick: onCancel, disabled: busy }, "\u4FDD\u7559\u673A\u5668\u4EBA"), h3( Button3, @@ -2409,7 +2301,7 @@ function BotCard({ return h3( "article", { - className: "bxf-card bxf-botCard", + className: "bxf-card bxf-botCard dim-botCard", "aria-labelledby": titleId, "data-bot-id": connection.botId, tabIndex: -1, @@ -2417,71 +2309,68 @@ function BotCard({ }, h3( "div", - { className: "bxf-cardBody" }, + { className: "bxf-cardBody dim-botCardBody" }, h3( "div", - { className: "bxf-connectedTop" }, + { className: "bxf-connectedTop dim-botCardTop" }, h3( "div", - { className: "bxf-botIdentity" }, + { className: "bxf-botIdentity dim-botIdentity" }, h3( "div", - { className: "bxf-avatar", "aria-hidden": "true" }, + { className: "bxf-avatar dim-botAvatar", "aria-hidden": "true" }, h3(FeishuLogoGlyph, { size: 34 }) ), h3( "div", - { className: "bxf-botName" }, + { className: "bxf-botName dim-botName" }, h3("h3", { id: titleId, title: bot.name }, bot.name), h3("p", { title: bot.appIdMasked }, bot.appIdMasked ?? "\u5E94\u7528\u6807\u8BC6\u5DF2\u5B89\u5168\u4FDD\u5B58") ) ), h3( "div", - { className: "bxf-healthPill", "data-health": stateForDisplay }, - h3("span", { className: "bxf-dot", "data-tone": tone }), + { className: "bxf-healthPill dim-botHealth", "data-health": stateForDisplay }, + h3("span", { className: "bxf-dot dim-healthDot", "data-tone": tone }), h3("span", null, HEALTH_LABELS[stateForDisplay] ?? "\u72B6\u6001\u672A\u77E5") ) ), h3( "dl", - { className: "bxf-statusGrid" }, + { className: "bxf-statusGrid dim-botMetrics" }, h3( "div", - { className: "bxf-metric" }, + { className: "bxf-metric dim-botMetric" }, h3("dt", null, "\u6D88\u606F\u901A\u9053"), h3("dd", null, connected ? "\u957F\u8FDE\u63A5" : stateForDisplay === "connecting" ? "\u8FDE\u63A5\u4E2D" : "\u5DF2\u65AD\u5F00") ), h3( "div", - { className: "bxf-metric" }, + { className: "bxf-metric dim-botMetric" }, h3("dt", null, "\u6700\u8FD1\u68C0\u67E5"), h3("dd", null, formatCheckedTime(health.lastCheckedAt)) ) ), - h3("div", { className: "bxf-divider" }), h3( "div", - { className: "bxf-connectedFooter" }, + { className: "bxf-connectedFooter dim-cardFooter" }, h3( "div", - { className: "bxf-healthSummary", "data-error": actionError || connection.error ? "true" : void 0 }, - h3("strong", null, "\u8FDE\u63A5\u72B6\u6001\uFF1A"), - h3("span", null, summary) + { className: "bxf-healthSummary dim-cardSummary", "data-error": actionError || connection.error ? "true" : void 0 }, + summary ), h3( "div", - { className: "bxf-actions bxf-botActions" }, + { className: "bxf-actions bxf-botActions dim-cardActions" }, h3(Button3, { - size: "small", - icon: h3(RefreshIcon), + className: "dim-cardAction", onClick: onReconnect, disabled: Boolean(busy), "aria-busy": busy === "reconnect" ? "true" : void 0, "aria-label": `${connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"}${bot.name}` }, busy === "reconnect" ? connected ? "\u68C0\u67E5\u4E2D\u2026" : "\u6B63\u5728\u8FDE\u63A5\u2026" : connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"), h3(Button3, { - size: "small", + className: "dim-cardAction", kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy), @@ -2502,16 +2391,16 @@ function BotCard({ function BotList(props) { return h3( "section", - { className: "bxf-listSection", "aria-labelledby": "bxf-bot-list-title" }, + { className: "bxf-listSection dim-listSection", "aria-labelledby": "bxf-bot-list-title" }, h3( "div", - { className: "bxf-listHeading" }, + { className: "bxf-listHeading dim-listHeading" }, h3("h3", { id: "bxf-bot-list-title" }, "\u5DF2\u63A5\u5165\u7684\u673A\u5668\u4EBA"), h3("span", null, `${props.bots.length} \u4E2A`) ), h3( "ul", - { className: "bxf-botList", role: "list" }, + { className: "bxf-botList dim-botList", role: "list" }, props.bots.map((bot) => h3( "li", { key: bot.botId }, @@ -2534,11 +2423,10 @@ function BotList(props) { function PageError({ error, onRetry, busy }) { return h3( "div", - { className: "bxf-card" }, + { className: "bxf-card dim-surfaceCard" }, h3( "div", - { className: "bxf-error", role: "alert" }, - h3("div", { className: "bxf-errorIcon" }, h3(AlertIcon)), + { className: "bxf-error dim-inlineError", role: "alert" }, h3( "div", null, @@ -2547,10 +2435,10 @@ function PageError({ error, onRetry, busy }) { error.code ? h3("span", { className: "bxf-errorCode" }, error.code) : null, h3( "div", - { className: "bxf-actions" }, + { className: "bxf-actions dim-viewActions" }, h3( Button3, - { kind: "primary", icon: h3(RefreshIcon), onClick: onRetry, disabled: busy }, + { kind: "primary", onClick: onRetry, disabled: busy }, busy ? "\u91CD\u8BD5\u4E2D\u2026" : "\u91CD\u65B0\u8BFB\u53D6" ) ) @@ -2916,7 +2804,7 @@ function FeishuSettingsTab({ rpcCall }) { }, []); return h3( "section", - { className: "bxf-page", "aria-label": "\u98DE\u4E66\u673A\u5668\u4EBA\u8BBE\u7F6E" }, + { className: "bxf-page dim-channelPage", "aria-label": "\u98DE\u4E66\u673A\u5668\u4EBA\u8BBE\u7F6E" }, h3(Heading2, { totals: model.totals, onAdd: () => void startProvisioning(), @@ -2932,7 +2820,7 @@ function FeishuSettingsTab({ rpcCall }) { }, announcement), model.statusError ? h3( "div", - { className: "bxf-statusNotice", role: "status" }, + { className: "bxf-statusNotice dim-statusNotice", role: "status" }, h3(AlertIcon, { size: 16 }), h3("span", null, `\u72B6\u6001\u81EA\u52A8\u5237\u65B0\u5931\u8D25\uFF1A${model.statusError.message}`), h3(Button3, { size: "small", onClick: () => void loadStatus({ silent: true }), disabled: pageBusy }, "\u7ACB\u5373\u91CD\u8BD5") @@ -3128,7 +3016,7 @@ var CSS3 = String.raw` .dxw-heading h2 { font-size: 20px; line-height: 28px; font-weight: 680; } .dxw-heading p { color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 20px; margin-top: 5px; white-space: nowrap; } .dxw-tools, .dxw-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; } -.dxw-tools { justify-content: flex-end; } +.dxw-tools { width: 100%; justify-content: space-between; flex-wrap: nowrap; } .dxw-badge { display: inline-flex; align-items: center; gap: 7px; min-height: 30px; padding: 0 11px; border-radius: 999px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-fill-secondary, #f2f3f5); font-size: 12px; white-space: nowrap; } .dxw-dot { width: 8px; height: 8px; border-radius: 50%; background: #aeb3bb; flex: none; } .dxw-dot[data-tone="success"] { background: var(--dxw-success); box-shadow: 0 0 0 3px color-mix(in srgb, var(--dxw-success) 14%, transparent); } @@ -3188,6 +3076,8 @@ var CSS3 = String.raw` .dxw-metric dt { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; } .dxw-metric dd { overflow: hidden; margin: 5px 0 0; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; } .dxw-accountFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-line-divider, #eef0f3); } +.dxw-accountFooter .dxw-actions { flex: none; flex-wrap: nowrap; gap: 8px; margin-top: 0; } +.dxw-accountFooter .dxw-button { flex: none; white-space: nowrap; } .dxw-summary { color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; } .dxw-confirm { padding: 18px 24px; border-top: 1px solid color-mix(in srgb, var(--dxw-error) 25%, transparent); background: color-mix(in srgb, var(--dxw-error) 5%, transparent); } .dxw-confirm strong { display: block; font-size: 14px; margin-bottom: 6px; } @@ -3198,8 +3088,7 @@ var CSS3 = String.raw` .dxw-visuallyHidden { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; } @keyframes dxw-spin { to { transform: rotate(360deg); } } @media (max-width: 720px) { - .dxw-heading, .dxw-accountTop, .dxw-accountFooter { flex-direction: column; align-items: stretch; } - .dxw-tools { justify-content: flex-start; } + .dxw-heading, .dxw-accountTop { flex-direction: column; align-items: stretch; } .dxw-empty { grid-template-columns: minmax(0, 1fr); } .dxw-logo { display: none; } .dxw-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; } @@ -3243,55 +3132,50 @@ function Heading3({ totals, adding, busy, onAdd, addButtonRef }) { h4( "div", { className: "dxw-tools" }, - totals.configured > 0 ? h4( - "div", - { className: "dxw-badge" }, - h4("span", { className: "dxw-dot", "data-tone": totals.connected > 0 ? "success" : "warning" }), - h4("span", null, `${totals.connected} / ${totals.configured} \u5728\u7EBF`) - ) : null, - h4( - "div", - { className: "dxw-badge", title: "bot_token \u4EC5\u4FDD\u5B58\u5728 Harness Host \u51ED\u636E\u670D\u52A1\u4E2D" }, - h4("span", null, "\u51ED\u636E\u4EC5\u4FDD\u5B58\u5728\u672C\u673A") - ), h4(Button5, { kind: "primary", + className: "dim-scanButton", onClick: onAdd, disabled: adding || busy, ref: addButtonRef - }, adding ? "\u6B63\u5728\u7ED1\u5B9A" : "\u626B\u7801\u7ED1\u5B9A\u5FAE\u4FE1") + }, adding ? "\u6B63\u5728\u7ED1\u5B9A" : "\u626B\u7801\u7ED1\u5B9A\u5FAE\u4FE1"), + totals.configured > 0 ? h4( + "div", + { className: "dxw-badge dim-onlineBadge" }, + h4("span", null, `${totals.connected} / ${totals.configured} \u5728\u7EBF`) + ) : null ) ); } function LoadingView3() { return h4( "div", - { className: "dxw-card dxw-loading", "aria-busy": "true" }, - h4("div", { className: "dxw-spinner" }), + { className: "dxw-card dxw-loading dim-surfaceCard dim-loadingView", "aria-busy": "true" }, + h4("div", { className: "dxw-spinner dim-spinner" }), h4("span", null, "\u6B63\u5728\u8BFB\u53D6\u5FAE\u4FE1\u8FDE\u63A5\u72B6\u6001\u2026") ); } function EmptyView3({ onStart, busy }) { return h4( "div", - { className: "dxw-card" }, + { className: "dxw-card dim-surfaceCard" }, h4( "div", - { className: "dxw-cardBody dxw-empty" }, + { className: "dxw-cardBody dxw-empty dim-surfaceBody dim-emptyView" }, h4( "div", - null, + { className: "dim-emptyCopy" }, h4( "div", - { className: "dxw-stateLabel" }, - h4("span", { className: "dxw-dot" }), + { className: "dxw-stateLabel dim-stateLabel" }, + h4("span", { className: "dxw-dot dim-stateDot" }), h4("span", null, "\u5C1A\u672A\u7ED1\u5B9A\u5FAE\u4FE1") ), h4("h3", null, "\u626B\u4E00\u6B21\u7801\uFF0C\u5C31\u80FD\u5728\u5FAE\u4FE1\u91CC\u4F7F\u7528 Harness"), h4("p", null, "\u4E8C\u7EF4\u7801\u7531\u817E\u8BAF\u5FAE\u4FE1 iLink \u670D\u52A1\u7B7E\u53D1\u3002\u7528\u624B\u673A\u5FAE\u4FE1\u626B\u63CF\u5E76\u786E\u8BA4\u540E\uFF0C\u8D26\u53F7\u51ED\u636E\u4F1A\u76F4\u63A5\u5199\u5165 Harness Host\uFF0C\u6D4F\u89C8\u5668\u4E0D\u4F1A\u6536\u5230 bot_token\u3002"), h4( "div", - { className: "dxw-actions" }, + { className: "dxw-actions dim-viewActions" }, h4( Button5, { kind: "primary", onClick: onStart, disabled: busy }, @@ -3299,7 +3183,7 @@ function EmptyView3({ onStart, busy }) { ) ) ), - h4("div", { className: "dxw-logo", "aria-hidden": "true" }, h4(WeixinLogoGlyph, { size: 64 })) + h4("div", { className: "dxw-logo dim-emptyBrand", "aria-hidden": "true" }, h4(WeixinLogoGlyph, { size: 64 })) ) ); } @@ -3314,55 +3198,55 @@ function QrPanel2({ provision, now, busy, onRefresh, onCancel }) { React5.useEffect(() => setImageFailed(false), [source]); return h4( "div", - { className: "dxw-card" }, + { className: "dxw-card dim-surfaceCard" }, h4( "div", - { className: "dxw-cardBody dxw-qrLayout" }, + { className: "dxw-cardBody dxw-qrLayout dim-surfaceBody dim-qrLayout" }, h4( "div", - { className: "dxw-qrColumn" }, + { className: "dxw-qrColumn dim-qrColumn" }, h4( "div", - { className: "dxw-qrFrame" }, + { className: "dxw-qrFrame dim-qrFrame" }, source && !imageFailed ? h4("img", { src: source, alt: "\u7528\u4E8E\u628A\u5FAE\u4FE1\u673A\u5668\u4EBA\u7ED1\u5B9A\u5230 DeepSeek Harness \u7684\u4E00\u6B21\u6027\u4E8C\u7EF4\u7801", onError: () => setImageFailed(true) - }) : h4("div", { className: "dxw-qrFallback" }, "\u4E8C\u7EF4\u7801\u56FE\u7247\u672A\u5C31\u7EEA\uFF0C\u8BF7\u4F7F\u7528\u5907\u7528\u94FE\u63A5\u3002"), - expired ? h4("div", { className: "dxw-expired" }, "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\n\u8BF7\u91CD\u65B0\u751F\u6210") : null + }) : h4("div", { className: "dxw-qrFallback dim-qrFallback" }, "\u4E8C\u7EF4\u7801\u56FE\u7247\u672A\u5C31\u7EEA\uFF0C\u8BF7\u4F7F\u7528\u5907\u7528\u94FE\u63A5\u3002"), + expired ? h4("div", { className: "dxw-expired dim-qrExpired" }, "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\n\u8BF7\u91CD\u65B0\u751F\u6210") : null ), h4( "div", - { className: "dxw-countdown" }, - h4("div", null, h4("span", null, "\u4E8C\u7EF4\u7801\u6709\u6548\u65F6\u95F4"), h4("strong", null, formatRemaining3(remaining))), + { className: "dxw-countdown dim-countdown" }, + h4("div", { className: "dim-countdownTop" }, h4("span", null, "\u4E8C\u7EF4\u7801\u6709\u6548\u65F6\u95F4"), h4("strong", null, formatRemaining3(remaining))), h4( "div", - { className: "dxw-progress", "aria-hidden": "true" }, + { className: "dxw-progress dim-progress", "aria-hidden": "true" }, h4("span", { style: { "--dxw-progress": `${progress}%` } }) ) ) ), h4( "div", - { className: "dxw-qrCopy" }, + { className: "dxw-qrCopy dim-qrCopy" }, h4( "div", - { className: "dxw-stateLabel" }, - h4("span", { className: "dxw-dot", "data-tone": provision.status === "scanned" ? "success" : "warning" }), + { className: "dxw-stateLabel dim-stateLabel" }, + h4("span", { className: "dxw-dot dim-stateDot", "data-tone": provision.status === "scanned" ? "success" : "warning" }), h4("span", null, provision.status === "scanned" ? "\u5DF2\u626B\u7801\uFF0C\u8BF7\u5728\u624B\u673A\u4E0A\u786E\u8BA4" : "\u7B49\u5F85\u5FAE\u4FE1\u626B\u7801") ), h4("h3", null, expired ? "\u4E8C\u7EF4\u7801\u5DF2\u5931\u6548" : "\u4F7F\u7528\u624B\u673A\u5FAE\u4FE1\u626B\u63CF\u4E8C\u7EF4\u7801"), h4("p", null, "\u8BF7\u5728\u624B\u673A\u4E0A\u6838\u5BF9\u5E76\u786E\u8BA4\u6388\u6743\u3002\u90E8\u5206\u8D26\u53F7\u4F1A\u989D\u5916\u663E\u793A\u4E00\u4E2A\u914D\u5BF9\u6570\u5B57\uFF0C\u9875\u9762\u4F1A\u5728\u9700\u8981\u65F6\u63D0\u793A\u8F93\u5165\u3002"), h4( "ol", - { className: "dxw-steps" }, + { className: "dxw-steps dim-steps" }, h4("li", null, "\u6253\u5F00\u624B\u673A\u5FAE\u4FE1\u5E76\u626B\u63CF\u5DE6\u4FA7\u4E8C\u7EF4\u7801"), h4("li", null, "\u5728\u5FAE\u4FE1\u4E2D\u786E\u8BA4\u8FDE\u63A5\u8BE5\u673A\u5668\u4EBA"), h4("li", null, "\u4FDD\u6301\u672C\u9875\u6253\u5F00\uFF0C\u7B49\u5F85\u6D88\u606F\u957F\u8F6E\u8BE2\u53D8\u4E3A\u5728\u7EBF") ), h4( "div", - { className: "dxw-actions" }, + { className: "dxw-actions dim-viewActions" }, expired ? h4(Button5, { kind: "primary", onClick: onRefresh, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801") : null, href ? h4("a", { className: "dxw-button", @@ -3383,11 +3267,11 @@ function VerificationPanel({ provision, busy, onSubmit, onCancel }) { React5.useEffect(() => setCode(""), [provision.attemptId]); return h4( "div", - { className: "dxw-card" }, + { className: "dxw-card dim-surfaceCard" }, h4( "form", { - className: "dxw-verify", + className: "dxw-verify dim-specialView", onSubmit: (event) => { event.preventDefault(); if (valid && !busy) onSubmit(code); @@ -3428,13 +3312,13 @@ function VerificationPanel({ provision, busy, onSubmit, onCancel }) { function ProgressPanel2({ scanned, onCancel, busy }) { return h4( "div", - { className: "dxw-card dxw-loading", "aria-busy": "true" }, - h4("div", { className: "dxw-spinner" }), + { className: "dxw-card dxw-loading dim-surfaceCard dim-loadingView", "aria-busy": "true" }, + h4("div", { className: "dxw-spinner dim-spinner" }), h4("h3", null, scanned ? "\u5FAE\u4FE1\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u542F\u52A8\u6D88\u606F\u8FDE\u63A5" : "\u6B63\u5728\u51C6\u5907\u5FAE\u4FE1\u4E8C\u7EF4\u7801"), h4("p", null, scanned ? "\u6B63\u5728\u4FDD\u5B58\u51ED\u636E\u5E76\u9A8C\u8BC1 Harness \u4E0E\u5FAE\u4FE1\u957F\u8F6E\u8BE2\u3002" : "\u6B63\u5728\u8054\u7CFB\u817E\u8BAF\u5FAE\u4FE1 iLink \u670D\u52A1\u3002"), onCancel ? h4( "div", - { className: "dxw-actions", style: { justifyContent: "center", marginTop: 14 } }, + { className: "dxw-actions dim-viewActions", style: { justifyContent: "center", marginTop: 14 } }, h4(Button5, { onClick: onCancel, disabled: busy }, "\u53D6\u6D88") ) : null ); @@ -3443,16 +3327,16 @@ function ProvisionError3({ provision, busy, onRetry, onClose }) { const error = provision.error ?? { code: "WEIXIN_PROVISION_FAILED", message: "\u5FAE\u4FE1\u7ED1\u5B9A\u6CA1\u6709\u5B8C\u6210" }; return h4( "div", - { className: "dxw-card" }, + { className: "dxw-card dim-surfaceCard" }, h4( "div", - { className: "dxw-error", role: "alert" }, + { className: "dxw-error dim-inlineError", role: "alert" }, h4("h3", null, provision.status === "expired" ? "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F" : "\u5FAE\u4FE1\u6CA1\u6709\u7ED1\u5B9A\u5B8C\u6210"), h4("p", null, error.message), h4("span", { className: "dxw-errorCode" }, error.code), h4( "div", - { className: "dxw-actions" }, + { className: "dxw-actions dim-viewActions" }, h4(Button5, { kind: "primary", onClick: onRetry, disabled: busy }, "\u91CD\u65B0\u751F\u6210\u4E8C\u7EF4\u7801"), h4(Button5, { onClick: onClose, disabled: busy }, "\u5173\u95ED") ) @@ -3476,66 +3360,66 @@ function AccountCard2({ account, busy, removing, onReconnect, onRequestRemove, o const tone = account.connected ? "success" : state === "error" ? "error" : "warning"; return h4( "article", - { className: "dxw-card", tabIndex: -1, "data-bot-id": account.botId }, + { className: "dxw-card dim-botCard", tabIndex: -1, "data-bot-id": account.botId }, h4( "div", - { className: "dxw-cardBody" }, + { className: "dxw-cardBody dim-botCardBody" }, h4( "div", - { className: "dxw-accountTop" }, + { className: "dxw-accountTop dim-botCardTop" }, h4( "div", - { className: "dxw-accountIdentity" }, - h4("div", { className: "dxw-avatar", "aria-hidden": "true" }, h4(WeixinLogoGlyph, { size: 27 })), - h4("div", null, h4("h3", null, account.bot.name), h4("p", null, account.bot.accountIdMasked)) + { className: "dxw-accountIdentity dim-botIdentity" }, + h4("div", { className: "dxw-avatar dim-botAvatar", "aria-hidden": "true" }, h4(WeixinLogoGlyph, { size: 27 })), + h4("div", { className: "dim-botName" }, h4("h3", null, account.bot.name), h4("p", null, account.bot.accountIdMasked)) ), h4( "div", - { className: "dxw-health" }, - h4("span", { className: "dxw-dot", "data-tone": tone }), + { className: "dxw-health dim-botHealth" }, + h4("span", { className: "dxw-dot dim-healthDot", "data-tone": tone }), h4("span", null, account.connected ? "\u8FD0\u884C\u6B63\u5E38" : state === "connecting" ? "\u6B63\u5728\u8FDE\u63A5" : "\u8FDE\u63A5\u672A\u5C31\u7EEA") ) ), h4( "dl", - { className: "dxw-metrics" }, + { className: "dxw-metrics dim-botMetrics" }, h4( "div", - { className: "dxw-metric" }, + { className: "dxw-metric dim-botMetric" }, h4("dt", null, "\u6D88\u606F\u901A\u9053"), h4("dd", null, account.connected ? "iLink \u957F\u8F6E\u8BE2" : "\u79BB\u7EBF") ), h4( "div", - { className: "dxw-metric" }, + { className: "dxw-metric dim-botMetric" }, h4("dt", null, "\u6700\u8FD1\u68C0\u67E5"), h4("dd", null, checkedTime2(account.health.lastCheckedAt)) ) ), h4( "div", - { className: "dxw-accountFooter" }, - h4("div", { className: "dxw-summary" }, account.error?.message ?? account.health.summary), + { className: "dxw-accountFooter dim-cardFooter" }, + h4("div", { className: "dxw-summary dim-cardSummary" }, account.error?.message ?? account.health.summary), h4( "div", - { className: "dxw-actions" }, + { className: "dxw-actions dim-cardActions" }, h4( Button5, - { onClick: onReconnect, disabled: Boolean(busy) }, + { className: "dim-cardAction", onClick: onReconnect, disabled: Boolean(busy) }, busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5" ), - h4(Button5, { kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) }, "\u79FB\u9664\u63A5\u5165") + h4(Button5, { className: "dim-cardAction", kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) }, "\u79FB\u9664\u63A5\u5165") ) ) ), removing ? h4( "div", - { className: "dxw-confirm", role: "alertdialog" }, + { className: "dxw-confirm dim-confirm", role: "alertdialog" }, h4("strong", null, "\u4ECE\u6B64 Harness \u79FB\u9664\u8FD9\u4E2A\u5FAE\u4FE1\u8D26\u53F7\uFF1F"), h4("p", null, "\u8FD9\u4F1A\u505C\u6B62\u6D88\u606F\u8FDE\u63A5\uFF0C\u5E76\u5220\u9664\u672C\u673A\u4FDD\u5B58\u7684 bot_token\u3001\u8D26\u53F7\u914D\u7F6E\u548C\u4F1A\u8BDD\u6620\u5C04\u3002\u5176\u4ED6\u5FAE\u4FE1\u8D26\u53F7\u4E0D\u53D7\u5F71\u54CD\u3002"), h4( "div", - { className: "dxw-actions" }, + { className: "dxw-actions dim-viewActions" }, h4(Button5, { onClick: onCancelRemove, disabled: busy === "delete" }, "\u4FDD\u7559\u8D26\u53F7"), h4( Button5, @@ -3549,9 +3433,9 @@ function AccountCard2({ account, busy, removing, onReconnect, onRequestRemove, o function AccountList2(props) { return h4( "section", - null, - h4("div", { className: "dxw-listHeading" }, h4("h3", null, "\u5DF2\u63A5\u5165\u7684\u5FAE\u4FE1\u8D26\u53F7"), h4("span", null, `${props.bots.length} \u4E2A`)), - h4("ul", { className: "dxw-list" }, props.bots.map((account) => h4( + { className: "dim-listSection" }, + h4("div", { className: "dxw-listHeading dim-listHeading" }, h4("h3", null, "\u5DF2\u63A5\u5165\u7684\u5FAE\u4FE1\u8D26\u53F7"), h4("span", null, `${props.bots.length} \u4E2A`)), + h4("ul", { className: "dxw-list dim-botList" }, props.bots.map((account) => h4( "li", { key: account.botId }, h4(AccountCard2, { @@ -3835,7 +3719,7 @@ function WeixinSettingsTab({ rpcCall }) { } return h4( "section", - { className: "dxw-page", "aria-label": "\u5FAE\u4FE1\u8BBE\u7F6E" }, + { className: "dxw-page dim-channelPage", "aria-label": "\u5FAE\u4FE1\u8BBE\u7F6E" }, h4(Heading3, { totals: model.totals, adding: Boolean(provision), @@ -3844,13 +3728,13 @@ function WeixinSettingsTab({ rpcCall }) { addButtonRef }), h4("div", { className: "dxw-visuallyHidden", role: "status", "aria-live": "polite" }, notice), - model.error && model.phase === "ready" ? h4("div", { className: "dxw-statusNotice" }, `\u72B6\u6001\u5237\u65B0\u5931\u8D25\uFF1A${model.error.message}`) : null, + model.error && model.phase === "ready" ? h4("div", { className: "dxw-statusNotice dim-statusNotice" }, `\u72B6\u6001\u5237\u65B0\u5931\u8D25\uFF1A${model.error.message}`) : null, model.phase === "loading" ? h4(LoadingView3) : model.phase === "error" ? h4( "div", - { className: "dxw-card" }, + { className: "dxw-card dim-surfaceCard" }, h4( "div", - { className: "dxw-error" }, + { className: "dxw-error dim-inlineError" }, h4("h3", null, "\u65E0\u6CD5\u8BFB\u53D6\u5FAE\u4FE1\u72B6\u6001"), h4("p", null, model.error?.message ?? "\u8BF7\u7A0D\u540E\u91CD\u8BD5"), h4(Button5, { onClick: () => void loadStatus() }, "\u91CD\u65B0\u8BFB\u53D6") @@ -3910,10 +3794,100 @@ var CSS4 = String.raw` .dim-panel { min-width: 0; container-type: inline-size; } .dim-panel .bxf-page, .dim-panel .dxw-page, .dim-panel .ddt-page { width: 100%; max-width: none; padding: 0 0 24px; } .dim-panel .bxf-heading, .dim-panel .dxw-heading, .dim-panel .ddt-heading { justify-content: flex-end; } -.dim-panel .bxf-headingTools, .dim-panel .dxw-tools, .dim-panel .ddt-tools { width: 100%; justify-content: flex-end; } +.dim-panel .bxf-headingTools, .dim-panel .dxw-tools, .dim-panel .ddt-tools { width: 100%; justify-content: space-between; } +.dim-panel .bxf-headingTools .dim-scanButton, .dim-panel .dxw-tools .dim-scanButton, .dim-panel .ddt-tools .dim-scanButton { flex: none; min-height: 34px; padding: 0 13px; border: 1px solid #1677ff; border-radius: 8px; color: #fff; background: #1677ff; box-shadow: none; font: inherit; font-size: 13px; font-weight: 560; white-space: nowrap; } +.dim-panel .bxf-headingTools .dim-scanButton:hover:not(:disabled), .dim-panel .dxw-tools .dim-scanButton:hover:not(:disabled), .dim-panel .ddt-tools .dim-scanButton:hover:not(:disabled) { border-color: #0958d9; background: #0958d9; } +.dim-panel .bxf-headingTools .dim-onlineBadge, .dim-panel .dxw-tools .dim-onlineBadge, .dim-panel .ddt-tools .dim-onlineBadge { flex: none; min-height: 30px; display: inline-flex; align-items: center; gap: 0; padding: 0 11px; border: 0; border-radius: 999px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-fill-secondary, #f2f3f5); font: inherit; font-size: 12px; font-weight: 400; line-height: normal; white-space: nowrap; } +.dim-panel .dim-channelPage { width: 100%; max-width: none; display: flex; flex-direction: column; gap: 18px; padding: 0 0 24px; color: var(--dsw-alias-label-primary, #1f2329); box-sizing: border-box; } +.dim-panel .dim-surfaceCard { position: relative; overflow: hidden; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-body, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 3%); } +.dim-panel .dim-surfaceCard::before { display: none; } +.dim-panel .dim-surfaceBody { padding: 24px; } +.dim-panel .dim-listSection { display: flex; flex-direction: column; gap: 0; } +.dim-panel .dim-listHeading { min-height: 0; display: flex; align-items: center; justify-content: space-between; gap: 16px; margin: 2px 0 9px; padding: 0; } +.dim-panel .dim-listHeading h3 { margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 14px; line-height: normal; font-weight: 650; } +.dim-panel .dim-listHeading span { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 12px; line-height: normal; } +.dim-panel .dim-botList { display: grid; gap: 12px; margin: 0; padding: 0; list-style: none; } +.dim-panel .dim-loadingView { padding: 38px; color: var(--dsw-alias-label-secondary, #646a73); text-align: center; } +.dim-panel .dim-loadingView h3 { margin: 0 0 7px; color: var(--dsw-alias-label-primary, #1f2329); font-size: 17px; line-height: normal; font-weight: 650; } +.dim-panel .dim-loadingView p { margin: 0; line-height: 1.6; } +.dim-panel .dim-spinner { width: 24px; height: 24px; margin: 0 auto 13px; border: 3px solid var(--dsw-alias-line-border, #e6e8eb); border-top-color: #1677ff; border-radius: 50%; animation: dim-spin .8s linear infinite; } +@keyframes dim-spin { to { transform: rotate(360deg); } } +.dim-panel .dim-emptyView { min-height: 230px; display: grid; grid-template-columns: minmax(0, 1fr) 180px; align-items: center; gap: 30px; } +.dim-panel .dim-emptyCopy { min-width: 0; } +.dim-panel .dim-emptyCopy h3 { margin: 8px 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 18px; line-height: 1.35; font-weight: 650; } +.dim-panel .dim-emptyCopy > p { max-width: 560px; margin: 0; color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.65; } +.dim-panel .dim-emptyBrand { width: 110px; height: 110px; display: grid; place-items: center; justify-self: center; border-radius: 28px; box-shadow: 0 18px 45px rgb(22 119 255 / 18%); } +.dim-panel .dim-stateLabel { display: inline-flex; align-items: center; gap: 8px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: normal; font-weight: 600; } +.dim-panel .dim-stateDot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--dsw-alias-label-tertiary, #8f959e); box-shadow: none; } +.dim-panel .dim-stateDot[data-tone="success"] { background: var(--dsw-alias-state-success-primary, #20a162); } +.dim-panel .dim-stateDot[data-tone="warning"] { background: var(--dsw-alias-state-warning-primary, #d97706); } +.dim-panel .dim-stateDot[data-tone="error"] { background: var(--dsw-alias-state-error-primary, #d54941); } +.dim-panel .dim-viewActions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-top: 20px; } +.dim-panel .dim-viewActions .bxf-button, .dim-panel .dim-viewActions .dxw-button, .dim-panel .dim-viewActions .ddt-button { min-height: 34px; padding: 0 13px; border: 1px solid var(--dsw-alias-line-border, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-body, #fff); box-shadow: none; font: inherit; font-size: 13px; font-weight: 560; line-height: normal; white-space: nowrap; } +.dim-panel .dim-viewActions .bxf-button[data-kind="primary"], .dim-panel .dim-viewActions .dxw-button[data-kind="primary"], .dim-panel .dim-viewActions .ddt-button[data-kind="primary"] { border-color: #1677ff; color: #fff; background: #1677ff; box-shadow: none; } +.dim-panel .dim-viewActions .bxf-button[data-kind="danger"], .dim-panel .dim-viewActions .dxw-button[data-kind="danger"], .dim-panel .dim-viewActions .ddt-button[data-kind="danger"] { color: var(--dsw-alias-state-error-primary, #d54941); } +.dim-panel .dim-qrLayout { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 34px; align-items: start; } +.dim-panel .dim-qrColumn { width: 100%; min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 12px; } +.dim-panel .dim-qrFrame { position: relative; width: min(270px, 100%); aspect-ratio: 1; display: grid; place-items: center; overflow: hidden; padding: 10px; border: 1px solid var(--dsw-alias-line-border, #dfe1e5); border-radius: 16px; background: #fff; } +.dim-panel .dim-qrFrame::before { content: ""; position: absolute; inset: 7px; z-index: 0; border: 1px solid color-mix(in srgb, #1677ff 16%, var(--dsw-alias-line-border, #dfe1e5)); border-radius: 12px; pointer-events: none; } +.dim-panel .dim-qrFrame::after { display: none; } +.dim-panel .dim-qrFrame img { position: relative; z-index: 1; width: 100%; height: 100%; display: block; object-fit: contain; } +.dim-panel .dim-qrFallback { position: relative; z-index: 1; display: grid; place-items: center; gap: 8px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 1.5; text-align: center; } +.dim-panel .dim-qrExpired { position: absolute; inset: 0; z-index: 2; display: grid; place-items: center; padding: 20px; color: var(--dsw-alias-label-primary, #1f2329); background: rgb(255 255 255 / 92%); font-size: 15px; line-height: 1.6; font-weight: 650; text-align: center; white-space: pre-line; backdrop-filter: blur(3px); } +.dim-panel .dim-countdown { width: min(270px, 100%); margin: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: normal; } +.dim-panel .dim-countdownTop { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 6px; } +.dim-panel .dim-countdownTop strong { color: var(--dsw-alias-label-primary, #1f2329); font-weight: 650; } +.dim-panel .dim-progress { height: 4px; overflow: hidden; margin: 0; border-radius: 99px; background: var(--dsw-alias-fill-secondary, #eef0f3); } +.dim-panel .dim-progress span { display: block; width: var(--bxf-progress, var(--dxw-progress, var(--ddt-progress, 0%))); height: 100%; border-radius: inherit; background: #1677ff; transition: width .25s linear; } +.dim-panel .dim-qrCopy { min-width: 0; overflow-wrap: anywhere; } +.dim-panel .dim-qrCopy h3 { margin: 9px 0 8px; color: var(--dsw-alias-label-primary, #1f2329); font-size: 18px; line-height: 1.35; font-weight: 650; } +.dim-panel .dim-qrCopy > p { margin: 0; color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.65; } +.dim-panel .dim-steps { margin: 18px 0 16px; padding: 0; list-style: none; counter-reset: dim-step; } +.dim-panel .dim-steps li { position: relative; min-height: 28px; display: flex; align-items: center; padding: 5px 0 5px 36px; color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.5; counter-increment: dim-step; } +.dim-panel .dim-steps li::before { content: counter(dim-step); position: absolute; left: 0; top: 4px; width: 25px; height: 25px; display: grid; place-items: center; border-radius: 8px; color: #1677ff; background: color-mix(in srgb, #1677ff 10%, white); font-size: 12px; font-weight: 650; } +.dim-panel .dim-specialView { padding: 32px; text-align: center; } +.dim-panel .dim-statusNotice { display: flex; align-items: flex-start; gap: 10px; padding: 13px 15px; border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary, #d54941) 22%, white); border-radius: 10px; color: var(--dsw-alias-state-error-primary, #d54941); background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #d54941) 6%, white); font-size: 13px; line-height: 1.5; } +.dim-panel .dim-inlineError { display: flex; align-items: flex-start; flex-direction: column; gap: 10px; padding: 22px; color: var(--dsw-alias-state-error-primary, #d54941); background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #d54941) 6%, white); } +.dim-panel .dim-inlineError > div { min-width: 0; } +.dim-panel .dim-inlineError h3 { margin: 0; color: inherit; font-size: 17px; line-height: 1.35; font-weight: 650; } +.dim-panel .dim-inlineError p { margin: 7px 0 0; color: inherit; line-height: 1.6; } +.dim-panel .dim-confirm { padding: 18px 24px; border-top: 1px solid var(--dsw-alias-line-divider, #eef0f3); background: var(--dsw-alias-fill-tertiary, #f7f8fa); } +.dim-panel .dim-confirm strong, .dim-panel .dim-confirm h4 { margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 14px; line-height: 1.4; font-weight: 650; } +.dim-panel .dim-confirm p { margin: 7px 0 0; color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.6; } +.dim-panel .dim-cardFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-line-divider, #eef0f3); } +.dim-panel .dim-cardSummary { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font: inherit; font-size: 12px; font-weight: 400; line-height: normal; } +.dim-panel .dim-cardActions { flex: none; display: flex; align-items: center; flex-wrap: nowrap; gap: 8px; margin-top: 0; } +.dim-panel .dim-cardActions .dim-cardAction { flex: none; min-height: 34px; padding: 0 13px; border: 1px solid var(--dsw-alias-line-border, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-body, #fff); font: inherit; font-size: 13px; font-weight: 560; line-height: normal; white-space: nowrap; } +.dim-panel .dim-cardActions .dim-cardAction:hover:not(:disabled) { border-color: #aeb3bb; background: var(--dsw-alias-fill-tertiary, #f7f8fa); } +.dim-panel .dim-cardActions .dim-cardAction[data-kind="danger"] { color: var(--dsw-alias-state-error-primary, #d54941); } +.dim-panel .dim-botCard { position: relative; overflow: hidden; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-body, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 3%); } +.dim-panel .dim-botCard::before { display: none; } +.dim-panel .dim-botCardBody { position: relative; padding: 24px; } +.dim-panel .dim-botCardTop { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; } +.dim-panel .dim-botIdentity { min-width: 0; display: flex; align-items: center; gap: 12px; } +.dim-panel .dim-botAvatar { flex: none; width: 42px; height: 42px; display: grid; place-items: center; overflow: hidden; border-radius: 12px; box-shadow: none; } +.dim-panel .dim-botAvatar svg { width: 29px; height: 29px; } +.dim-panel .dim-botName { min-width: 0; } +.dim-panel .dim-botName h3 { overflow: hidden; margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 15px; font-weight: 650; line-height: normal; text-overflow: ellipsis; white-space: nowrap; } +.dim-panel .dim-botName p { overflow: hidden; margin: 4px 0 0; color: var(--dsw-alias-label-secondary, #646a73); font: 12px ui-monospace, SFMono-Regular, monospace; line-height: normal; text-overflow: ellipsis; white-space: nowrap; } +.dim-panel .dim-botCard .dim-botHealth { flex: none; min-height: 0; display: inline-flex; align-items: center; gap: 7px; padding: 0; border: 0; border-radius: 0; color: var(--dsw-alias-label-secondary, #646a73); background: transparent; font: inherit; font-size: 12px; font-weight: 400; line-height: normal; white-space: nowrap; } +.dim-panel .dim-botCard .dim-healthDot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: #aeb3bb; box-shadow: none; } +.dim-panel .dim-botCard .dim-healthDot[data-tone="success"] { background: var(--dsw-alias-state-success-primary, #20a162); box-shadow: 0 0 0 3px color-mix(in srgb, var(--dsw-alias-state-success-primary, #20a162) 14%, transparent); } +.dim-panel .dim-botCard .dim-healthDot[data-tone="warning"] { background: var(--dsw-alias-state-warning-primary, #d97706); } +.dim-panel .dim-botCard .dim-healthDot[data-tone="error"] { background: var(--dsw-alias-state-error-primary, #d54941); } +.dim-panel .dim-botMetrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 20px 0; } +.dim-panel .dim-botMetric { min-width: 0; padding: 12px; border: 0; border-radius: 9px; background: var(--dsw-alias-fill-tertiary, #f7f8fa); } +.dim-panel .dim-botMetric dt { margin: 0; color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; font-weight: 400; line-height: normal; } +.dim-panel .dim-botMetric dd { overflow: hidden; margin: 5px 0 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 13px; font-weight: 400; line-height: normal; text-overflow: ellipsis; white-space: nowrap; } +.dim-panel .dim-botCard .dim-cardFooter { margin-top: 0; } .dim-panel .ddt-headingCopy { display: none; } .dim-panel .ddt-qrFrame, .dim-panel .ddt-countdown { width: min(270px, 100%); } @container (max-width: 680px) { + .dim-panel .dim-emptyView { min-height: 0; grid-template-columns: minmax(0, 1fr); } + .dim-panel .dim-emptyBrand { display: none; } + .dim-panel .dim-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; gap: 24px; } + .dim-panel .dim-qrColumn { width: 100%; min-width: 0; } + .dim-panel .dim-qrCopy { width: 100%; min-width: 0; overflow-wrap: anywhere; } .dim-panel .ddt-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; gap: 24px; } .dim-panel .ddt-qrColumn { width: 100%; min-width: 0; } .dim-panel .ddt-qrCopy { width: 100%; min-width: 0; overflow-wrap: anywhere; } @@ -3926,6 +3900,9 @@ var CSS4 = String.raw` .dim-rail { max-height: none; overflow: visible; padding-right: 1px; } .dim-channel { min-height: 48px; } } +@media (max-width: 720px) { + .dim-panel .dim-botCardTop { flex-direction: column; align-items: stretch; } +} @media (max-width: 560px) { .dim-title { flex-direction: column; gap: 10px; } .dim-title p { white-space: normal; } diff --git a/plugin-src/client/channels/dingtalk/index.js b/plugin-src/client/channels/dingtalk/index.js index 9a711b5..df15e2c 100644 --- a/plugin-src/client/channels/dingtalk/index.js +++ b/plugin-src/client/channels/dingtalk/index.js @@ -53,44 +53,37 @@ function Heading({ totals, adding, busy, onAdd, addButtonRef }) { h('h2', null, '钉钉机器人'), h('p', null, '通过扫码把钉钉机器人接入 DeepSeek Harness')), h('div', { className: 'ddt-tools' }, - totals.configured > 0 - ? h('div', { className: 'ddt-badge' }, - h('span', { - className: 'ddt-dot', - 'data-tone': totals.connected > 0 ? 'success' : 'warning', - }), - h('span', null, `${totals.connected} / ${totals.configured} 在线`)) - : null, - h('div', { - className: 'ddt-badge', - title: '应用密钥只写入 Harness Host 凭据服务,不会发送到浏览器', - }, '凭据仅保存在本机'), h(Button, { kind: 'primary', + className: 'dim-scanButton', onClick: onAdd, disabled: adding || busy, ref: addButtonRef, - }, adding ? '正在接入' : '扫码接入钉钉'))); + }, adding ? '正在接入' : '扫码接入钉钉'), + totals.configured > 0 + ? h('div', { className: 'ddt-badge dim-onlineBadge' }, + h('span', null, `${totals.connected} / ${totals.configured} 在线`)) + : null)); } function LoadingView() { - return h('div', { className: 'ddt-card ddt-loading', 'aria-busy': 'true' }, - h('div', { className: 'ddt-spinner' }), + return h('div', { className: 'ddt-card ddt-loading dim-surfaceCard dim-loadingView', 'aria-busy': 'true' }, + h('div', { className: 'ddt-spinner dim-spinner' }), h('span', null, '正在读取钉钉连接状态…')); } function EmptyView({ busy, onStart }) { - return h('div', { className: 'ddt-card' }, - h('div', { className: 'ddt-cardBody ddt-empty' }, - h('div', null, - h('div', { className: 'ddt-stateLabel' }, - h('span', { className: 'ddt-dot' }), h('span', null, '尚未接入钉钉机器人')), + return h('div', { className: 'ddt-card dim-surfaceCard' }, + h('div', { className: 'ddt-cardBody ddt-empty dim-surfaceBody dim-emptyView' }, + h('div', { className: 'dim-emptyCopy' }, + h('div', { className: 'ddt-stateLabel dim-stateLabel' }, + h('span', { className: 'ddt-dot dim-stateDot' }), h('span', null, '尚未接入钉钉机器人')), h('h3', null, '扫一次码,自动创建并连接机器人'), h('p', null, '授权由钉钉官方页面完成。扫码账号必须已加入一个企业/组织并有权创建机器人;创建成功后,应用凭据会直接写入 Harness Host。'), - h('div', { className: 'ddt-actions' }, + h('div', { className: 'ddt-actions dim-viewActions' }, h(Button, { kind: 'primary', onClick: onStart, disabled: busy }, busy ? '正在生成二维码…' : '生成钉钉二维码'))), - h('div', { className: 'ddt-brandMark', 'aria-hidden': 'true' }, + h('div', { className: 'ddt-brandMark dim-emptyBrand', 'aria-hidden': 'true' }, h(DingtalkIcon, { size: 68 })))); } @@ -104,34 +97,34 @@ function QrPanel({ provision, now, busy, onRefresh, onCancel }) { React.useEffect(() => setImageFailed(false), [source]); - return h('div', { className: 'ddt-card' }, - h('div', { className: 'ddt-cardBody ddt-qrLayout' }, - h('div', { className: 'ddt-qrColumn' }, - h('div', { className: 'ddt-qrFrame' }, + return h('div', { className: 'ddt-card dim-surfaceCard' }, + h('div', { className: 'ddt-cardBody ddt-qrLayout dim-surfaceBody dim-qrLayout' }, + h('div', { className: 'ddt-qrColumn dim-qrColumn' }, + h('div', { className: 'ddt-qrFrame dim-qrFrame' }, source && !imageFailed ? h('img', { src: source, alt: '用于把钉钉机器人接入 DeepSeek Harness 的一次性二维码', onError: () => setImageFailed(true), }) - : h('div', { className: 'ddt-qrFallback' }, '二维码图片未就绪,请重新生成。'), - expired ? h('div', { className: 'ddt-expired' }, '二维码已过期\n请重新生成') : null), - h('div', { className: 'ddt-countdown' }, - h('div', { className: 'ddt-countdownTop' }, + : h('div', { className: 'ddt-qrFallback dim-qrFallback' }, '二维码图片未就绪,请重新生成。'), + expired ? h('div', { className: 'ddt-expired dim-qrExpired' }, '二维码已过期\n请重新生成') : null), + h('div', { className: 'ddt-countdown dim-countdown' }, + h('div', { className: 'ddt-countdownTop dim-countdownTop' }, h('span', null, '二维码有效时间'), h('strong', null, formatRemaining(remaining))), - h('div', { className: 'ddt-progress', 'aria-hidden': 'true' }, + h('div', { className: 'ddt-progress dim-progress', 'aria-hidden': 'true' }, h('span', { style: { '--ddt-progress': `${progress}%` } })))), - h('div', { className: 'ddt-qrCopy' }, - h('div', { className: 'ddt-stateLabel' }, - h('span', { className: 'ddt-dot', 'data-tone': expired ? 'error' : 'warning' }), + h('div', { className: 'ddt-qrCopy dim-qrCopy' }, + h('div', { className: 'ddt-stateLabel dim-stateLabel' }, + h('span', { className: 'ddt-dot dim-stateDot', 'data-tone': expired ? 'error' : 'warning' }), h('span', null, expired ? '二维码已失效' : '等待钉钉扫码授权')), h('h3', null, expired ? '重新生成二维码后继续' : '使用钉钉 App 完成机器人授权'), h('p', null, '扫码账号必须已加入企业/组织。如果钉钉提示尚未加入组织,请在提示页创建组织,或换用已加入组织的账号。'), - h('ol', { className: 'ddt-steps' }, + h('ol', { className: 'ddt-steps dim-steps' }, h('li', null, '使用已加入企业/组织的钉钉账号扫描左侧二维码'), h('li', null, '在授权页点击“一键创建新机器人”'), h('li', null, '保持本页打开,等待机器人自动连接')), - h('div', { className: 'ddt-actions' }, + h('div', { className: 'ddt-actions dim-viewActions' }, expired ? h(Button, { kind: 'primary', onClick: onRefresh, disabled: busy }, '重新生成二维码') : null, @@ -142,15 +135,15 @@ function QrPanel({ provision, now, busy, onRefresh, onCancel }) { function ProgressPanel({ status, busy, onCancel }) { const connecting = status === 'connecting'; const creating = status === 'creating'; - return h('div', { className: 'ddt-card ddt-loading', 'aria-busy': 'true' }, - h('div', { className: 'ddt-spinner' }), + return h('div', { className: 'ddt-card ddt-loading dim-surfaceCard dim-loadingView', 'aria-busy': 'true' }, + h('div', { className: 'ddt-spinner dim-spinner' }), h('h3', null, connecting ? '机器人已创建,正在建立消息连接' : creating ? '授权已确认,正在创建钉钉机器人' : '正在确认钉钉授权'), h('p', null, connecting ? '正在检查钉钉 Stream 长连接,成功后会自动显示为在线。' : '请勿关闭本页,钉钉完成授权后将自动继续。'), - h('div', { className: 'ddt-actions', style: { justifyContent: 'center', marginTop: 14 } }, + h('div', { className: 'ddt-actions dim-viewActions', style: { justifyContent: 'center', marginTop: 14 } }, h(Button, { onClick: onCancel, disabled: busy }, '取消接入'))); } @@ -159,12 +152,12 @@ function ProvisionError({ provision, busy, onRetry, onClose }) { code: 'DINGTALK_PROVISION_FAILED', message: '钉钉机器人没有接入完成', }; - return h('div', { className: 'ddt-card' }, - h('div', { className: 'ddt-inlineError', role: 'alert' }, + return h('div', { className: 'ddt-card dim-surfaceCard' }, + h('div', { className: 'ddt-inlineError dim-inlineError', role: 'alert' }, h('h3', null, provision.status === 'expired' ? '二维码已过期' : '钉钉机器人没有接入完成'), h('p', null, error.message), h('span', { className: 'ddt-errorCode' }, error.code), - h('div', { className: 'ddt-actions' }, + h('div', { className: 'ddt-actions dim-viewActions' }, h(Button, { kind: 'primary', onClick: onRetry, disabled: busy }, '重新生成二维码'), h(Button, { onClick: onClose, disabled: busy }, '关闭')))); } @@ -184,7 +177,7 @@ function RemoveConfirmation({ account, busy, onConfirm, onCancel }) { const cancelRef = React.useRef(null); React.useEffect(() => cancelRef.current?.focus(), []); return h('div', { - className: 'ddt-confirm', + className: 'ddt-confirm dim-confirm', role: 'alertdialog', 'aria-label': `移除${account.bot.name}`, onKeyDown: (event) => { @@ -193,7 +186,7 @@ function RemoveConfirmation({ account, busy, onConfirm, onCancel }) { }, h('strong', null, `从 DeepSeek Harness 移除“${account.bot.name}”?`), h('p', null, '这会停止消息连接,并删除本机保存的应用凭据、机器人配置及会话映射。钉钉开放平台中的机器人不会被自动删除。'), - h('div', { className: 'ddt-actions' }, + h('div', { className: 'ddt-actions dim-viewActions' }, h(Button, { ref: cancelRef, onClick: onCancel, disabled: busy }, '保留机器人'), h(Button, { kind: 'danger', onClick: onConfirm, disabled: busy }, busy ? '正在移除…' : '确认移除接入'))); @@ -211,27 +204,27 @@ export function AccountCard({ const state = busy === 'reconnect' ? 'connecting' : account.state; const tone = account.connected ? 'success' : state === 'error' ? 'error' : 'warning'; const stateLabel = account.connected ? '运行正常' : state === 'connecting' ? '正在连接' : '连接未就绪'; - return h('article', { className: 'ddt-card', tabIndex: -1, 'data-bot-id': account.botId }, - h('div', { className: 'ddt-cardBody' }, - h('div', { className: 'ddt-accountTop' }, - h('div', { className: 'ddt-accountIdentity' }, - h('div', { className: 'ddt-avatar', 'aria-hidden': 'true' }, h(DingtalkIcon, { size: 29 })), - h('div', null, + return h('article', { className: 'ddt-card dim-botCard', tabIndex: -1, 'data-bot-id': account.botId }, + h('div', { className: 'ddt-cardBody dim-botCardBody' }, + h('div', { className: 'ddt-accountTop dim-botCardTop' }, + h('div', { className: 'ddt-accountIdentity dim-botIdentity' }, + h('div', { className: 'ddt-avatar dim-botAvatar', 'aria-hidden': 'true' }, h(DingtalkIcon, { size: 29 })), + h('div', { className: 'dim-botName' }, h('h3', { title: account.bot.name }, account.bot.name), h('p', { title: account.bot.clientIdMasked }, account.bot.clientIdMasked))), - h('div', { className: 'ddt-health' }, - h('span', { className: 'ddt-dot', 'data-tone': tone }), h('span', null, stateLabel))), - h('dl', { className: 'ddt-metrics' }, - h('div', { className: 'ddt-metric' }, h('dt', null, '消息通道'), + h('div', { className: 'ddt-health dim-botHealth' }, + h('span', { className: 'ddt-dot dim-healthDot', 'data-tone': tone }), h('span', null, stateLabel))), + h('dl', { className: 'ddt-metrics dim-botMetrics' }, + h('div', { className: 'ddt-metric dim-botMetric' }, h('dt', null, '消息通道'), h('dd', null, account.connected ? 'Stream 长连接' : '离线')), - h('div', { className: 'ddt-metric' }, h('dt', null, '最近检查'), + h('div', { className: 'ddt-metric dim-botMetric' }, h('dt', null, '最近检查'), h('dd', null, checkedTime(account.health.lastCheckedAt)))), - h('div', { className: 'ddt-accountFooter' }, - h('div', { className: 'ddt-summary' }, account.error?.message ?? account.health.summary), - h('div', { className: 'ddt-actions' }, - h(Button, { onClick: onReconnect, disabled: Boolean(busy) }, + h('div', { className: 'ddt-accountFooter dim-cardFooter' }, + h('div', { className: 'ddt-summary dim-cardSummary' }, account.error?.message ?? account.health.summary), + h('div', { className: 'ddt-actions dim-cardActions' }, + h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'), - h(Button, { kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, + h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')))), removing ? h(RemoveConfirmation, { account, @@ -242,10 +235,10 @@ export function AccountCard({ } function AccountList(props) { - return h('section', null, - h('div', { className: 'ddt-listHeading' }, + return h('section', { className: 'dim-listSection' }, + h('div', { className: 'ddt-listHeading dim-listHeading' }, h('h3', null, '已接入的钉钉机器人'), h('span', null, `${props.bots.length} 个`)), - h('ul', { className: 'ddt-list' }, props.bots.map((account) => h('li', { key: account.botId }, + h('ul', { className: 'ddt-list dim-botList' }, props.bots.map((account) => h('li', { key: account.botId }, h(AccountCard, { account, busy: props.busyByBot[account.botId], @@ -605,7 +598,7 @@ export function DingtalkSettingsTab({ rpcCall }) { }); } - return h('section', { className: 'ddt-page', 'aria-label': '钉钉设置' }, + return h('section', { className: 'ddt-page dim-channelPage', 'aria-label': '钉钉设置' }, h(Heading, { totals: model.totals, adding: Boolean(provision), @@ -615,13 +608,13 @@ export function DingtalkSettingsTab({ rpcCall }) { }), h('div', { className: 'ddt-visuallyHidden', role: 'status', 'aria-live': 'polite' }, notice), model.error && model.phase === 'ready' - ? h('div', { className: 'ddt-statusNotice', role: 'alert' }, `状态刷新失败:${model.error.message}`) + ? h('div', { className: 'ddt-statusNotice dim-statusNotice', role: 'alert' }, `状态刷新失败:${model.error.message}`) : null, model.phase === 'loading' ? h(LoadingView) : model.phase === 'error' - ? h('div', { className: 'ddt-card' }, - h('div', { className: 'ddt-inlineError', role: 'alert' }, + ? h('div', { className: 'ddt-card dim-surfaceCard' }, + h('div', { className: 'ddt-inlineError dim-inlineError', role: 'alert' }, h('h3', null, '无法读取钉钉机器人状态'), h('p', null, model.error?.message ?? '请稍后重试'), h(Button, { onClick: () => void loadStatus() }, '重新读取'))) diff --git a/plugin-src/client/channels/dingtalk/styles.js b/plugin-src/client/channels/dingtalk/styles.js index 8d53249..32dc0f4 100644 --- a/plugin-src/client/channels/dingtalk/styles.js +++ b/plugin-src/client/channels/dingtalk/styles.js @@ -26,7 +26,7 @@ const CSS = String.raw` .ddt-heading h2 { font-size: 20px; line-height: 28px; font-weight: 680; } .ddt-heading p { margin-top: 5px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 20px; white-space: nowrap; } .ddt-tools, .ddt-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; } -.ddt-tools { justify-content: flex-end; } +.ddt-tools { width: 100%; justify-content: space-between; flex-wrap: nowrap; } .ddt-badge { min-height: 30px; display: inline-flex; align-items: center; gap: 7px; padding: 0 11px; border-radius: 999px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-fill-secondary, #f2f3f5); font-size: 12px; white-space: nowrap; } .ddt-dot { width: 8px; height: 8px; flex: none; border-radius: 50%; background: #aeb3bb; } .ddt-dot[data-tone="success"] { background: var(--ddt-success); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ddt-success) 14%, transparent); } @@ -92,6 +92,8 @@ const CSS = String.raw` .ddt-metric dt { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; } .ddt-metric dd { overflow: hidden; margin: 5px 0 0; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; } .ddt-accountFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-line-divider, #eef0f3); } +.ddt-accountFooter .ddt-actions { flex: none; flex-wrap: nowrap; gap: 8px; margin-top: 0; } +.ddt-accountFooter .ddt-button { flex: none; white-space: nowrap; } .ddt-summary { color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; } .ddt-confirm { padding: 18px 24px; border-top: 1px solid color-mix(in srgb, var(--ddt-error) 25%, transparent); background: color-mix(in srgb, var(--ddt-error) 5%, transparent); } .ddt-confirm strong { display: block; margin-bottom: 6px; font-size: 14px; } @@ -113,9 +115,8 @@ const CSS = String.raw` .ddt-metric { padding: 10px; } } @media (max-width: 720px) { - .ddt-heading, .ddt-accountTop, .ddt-accountFooter { flex-direction: column; align-items: stretch; } + .ddt-heading, .ddt-accountTop { flex-direction: column; align-items: stretch; } .ddt-heading p { white-space: normal; } - .ddt-tools { justify-content: flex-start; } .ddt-empty { grid-template-columns: minmax(0, 1fr); } .ddt-brandMark { display: none; } .ddt-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; } diff --git a/plugin-src/client/channels/feishu/index.js b/plugin-src/client/channels/feishu/index.js index d48137a..d018432 100644 --- a/plugin-src/client/channels/feishu/index.js +++ b/plugin-src/client/channels/feishu/index.js @@ -129,58 +129,51 @@ function Heading({ totals, onAdd, adding, busy, addButtonRef }) { const hasBots = totals.configured > 0; return h("div", { className: "bxf-heading" }, h("div", { className: "bxf-headingTools" }, - hasBots - ? h("div", { - className: "bxf-totalBadge", - "aria-label": `已接入 ${totals.configured} 个机器人,其中 ${totals.connected} 个在线`, - }, h("strong", null, totals.connected), h("span", null, `/ ${totals.configured} 在线`)) - : null, - h("div", { - className: "bxf-localBadge", - title: "每个应用的凭据均由 Host 独立保存,不会发送到浏览器", - }, h(ShieldIcon, { size: 14 }), h("span", null, "凭据仅保存在本机")), h(Button, { kind: "primary", size: "small", - className: "bxf-bindButton", + className: "bxf-bindButton dim-scanButton", onClick: onAdd, disabled: adding || busy, ref: addButtonRef, "aria-busy": busy ? "true" : undefined, }, adding ? "正在绑定" : "扫码绑定机器人"), + hasBots + ? h("div", { + className: "bxf-totalBadge dim-onlineBadge", + "aria-label": `已接入 ${totals.configured} 个机器人,其中 ${totals.connected} 个在线`, + }, h("span", null, `${totals.connected} / ${totals.configured} 在线`)) + : null, ), ); } function LoadingView() { return h("div", { - className: "bxf-card", "aria-busy": "true", "aria-label": "正在读取飞书机器人列表", - }, h("div", { className: "bxf-skeleton" }, - h("div", { className: "bxf-skeletonLine" }), - h("div", { className: "bxf-skeletonLine" }), - h("div", { className: "bxf-skeletonLine" }), - h("div", { className: "bxf-skeletonLine" }), - h("div", { className: "bxf-skeletonBox" }), - )); + className: "bxf-card dim-surfaceCard dim-loadingView", + "aria-busy": "true", + "aria-label": "正在读取飞书机器人列表", + }, + h("div", { className: "dim-spinner", "aria-hidden": "true" }), + h("span", null, "正在读取飞书连接状态…"), + ); } function EmptyView({ onStart, busy }) { - return h("div", { className: "bxf-card" }, - h("div", { className: "bxf-cardBody bxf-intro" }, - h("div", { className: "bxf-introCopy" }, - h("div", { className: "bxf-stateLabel" }, - h("span", { className: "bxf-dot" }), h("span", null, "尚未接入机器人")), + return h("div", { className: "bxf-card dim-surfaceCard" }, + h("div", { className: "bxf-cardBody bxf-intro dim-surfaceBody dim-emptyView" }, + h("div", { className: "bxf-introCopy dim-emptyCopy" }, + h("div", { className: "bxf-stateLabel dim-stateLabel" }, + h("span", { className: "bxf-dot dim-stateDot" }), h("span", null, "尚未接入机器人")), h("h3", null, "扫码,创建第一个飞书入口"), h("p", null, "无需手动填写 App ID。以后还可以继续添加机器人,分别服务不同团队或飞书租户。"), - h("div", { className: "bxf-actions" }, + h("div", { className: "bxf-actions dim-viewActions" }, h(Button, { - kind: "primary", icon: h(SparkIcon), onClick: onStart, + kind: "primary", onClick: onStart, disabled: busy, "aria-busy": busy ? "true" : undefined, - }, busy ? "正在创建…" : "一键创建飞书机器人")), - h("div", { className: "bxf-note" }, h(ShieldIcon, { size: 16 }), - h("span", null, "每个 App Secret 都只写入 Host 凭据存储,浏览器不会收到 Secret。")), + }, busy ? "正在生成二维码…" : "生成飞书二维码")), ), - h("div", { className: "bxf-markStage", "aria-hidden": "true" }, h(BrandMark)), + h("div", { className: "bxf-markStage dim-emptyBrand", "aria-hidden": "true" }, h(BrandMark)), ), ); } @@ -212,57 +205,57 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) { React.useEffect(() => setImageFailed(false), [qrSource]); - return h("div", { className: "bxf-card bxf-provisionCard" }, - h("div", { className: "bxf-cardBody bxf-qrLayout" }, - h("div", { className: "bxf-qrColumn" }, - h("div", { className: "bxf-qrFrame" }, + return h("div", { className: "bxf-card bxf-provisionCard dim-surfaceCard" }, + h("div", { className: "bxf-cardBody bxf-qrLayout dim-surfaceBody dim-qrLayout" }, + h("div", { className: "bxf-qrColumn dim-qrColumn" }, + h("div", { className: "bxf-qrFrame dim-qrFrame" }, qrSource && !imageFailed ? h("img", { src: qrSource, alt: "用于新增 DeepSeek Harness 飞书机器人的一次性授权二维码", onError: () => setImageFailed(true), }) - : h("div", { className: "bxf-qrFallback" }, + : h("div", { className: "bxf-qrFallback dim-qrFallback" }, h("div", null, h(QrIcon), h("span", null, "二维码未就绪,请打开授权链接"))), expired - ? h("div", { className: "bxf-expiredOverlay", role: "status" }, + ? h("div", { className: "bxf-expiredOverlay dim-qrExpired", role: "status" }, h("div", null, "二维码已失效", h("br"), "请刷新后重新扫码")) : null, ), h("div", { - className: "bxf-countdown", + className: "bxf-countdown dim-countdown", "aria-label": expired ? "二维码已失效" : `二维码剩余 ${formatRemaining(remaining)}`, }, - h("div", { className: "bxf-countdownTop", "aria-hidden": "true" }, + h("div", { className: "bxf-countdownTop dim-countdownTop", "aria-hidden": "true" }, h("span", null, expired ? "等待刷新" : "二维码有效时间"), h("strong", null, formatRemaining(remaining))), - h("div", { className: "bxf-progress", "aria-hidden": "true" }, + h("div", { className: "bxf-progress dim-progress", "aria-hidden": "true" }, h("span", { style: { "--bxf-progress": `${Math.round(progress * 100)}%` } })), ), ), - h("div", { className: "bxf-qrCopy" }, - h("div", { className: "bxf-stateLabel" }, - h("span", { className: "bxf-dot", "data-tone": "warning" }), + h("div", { className: "bxf-qrCopy dim-qrCopy" }, + h("div", { className: "bxf-stateLabel dim-stateLabel" }, + h("span", { className: "bxf-dot dim-stateDot", "data-tone": "warning" }), h("span", null, "正在添加新机器人")), h("h3", null, expired ? "刷新二维码后继续" : "使用飞书扫码创建机器人"), h("p", null, "扫码只会新增一个机器人,已接入的机器人会继续正常收发消息。"), - h("ol", { className: "bxf-steps" }, + h("ol", { className: "bxf-steps dim-steps" }, h("li", null, "打开飞书移动端,使用扫一扫读取二维码"), h("li", null, "核对应用名称与权限范围,并确认创建"), h("li", null, "保持本页打开,等待新机器人的长连接就绪")), - h("div", { className: "bxf-actions" }, + h("div", { className: "bxf-actions dim-viewActions" }, expired ? h(Button, { - kind: "primary", icon: h(RefreshIcon), onClick: onRefresh, disabled: busy, + kind: "primary", onClick: onRefresh, disabled: busy, }, busy ? "刷新中…" : "刷新二维码") : href ? h("a", { className: "bxf-button bxf-link", "data-kind": "secondary", href, target: "_blank", rel: "noopener noreferrer", - }, h(ExternalIcon), h("span", null, "在飞书中打开")) + }, h("span", null, "在飞书中打开")) : null, !expired - ? h(Button, { icon: h(RefreshIcon), onClick: onRefresh, disabled: busy }, "换一个二维码") + ? h(Button, { onClick: onRefresh, disabled: busy }, "换一个二维码") : null, h(Button, { onClick: onCancel, disabled: busy }, "取消添加")), ), @@ -272,35 +265,31 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) { function ProvisionProgress({ phase, onCancel, busy }) { const connecting = phase === "connecting"; - return h("div", { className: "bxf-card bxf-provisionCard", "aria-busy": "true" }, - h("div", { className: "bxf-connecting bxf-connectingCompact" }, - h("div", { className: "bxf-connectingCopy" }, - h("div", { className: "bxf-orbit" }, - h("div", { className: "bxf-orbitCore" }, - connecting ? h(RobotIcon, { size: 24 }) : h(SparkIcon, { size: 24 }))), - h("h3", null, connecting ? "已确认,正在连接新机器人" : "正在准备授权二维码"), - h("p", null, connecting - ? "正在安全保存凭据并检查新机器人的消息通道,其他机器人不会中断。" - : "正在向飞书申请一次性授权二维码,请稍候。"), - connecting - ? h("div", { className: "bxf-actions", style: { justifyContent: "center" } }, - h(Button, { onClick: onCancel, disabled: busy }, "取消添加")) - : null, - ), - ), + return h("div", { + className: "bxf-card bxf-provisionCard dim-surfaceCard dim-loadingView", + "aria-busy": "true", + }, + h("div", { className: "dim-spinner", "aria-hidden": "true" }), + h("h3", null, connecting ? "已确认,正在连接新机器人" : "正在准备授权二维码"), + h("p", null, connecting + ? "正在安全保存凭据并检查新机器人的消息通道,其他机器人不会中断。" + : "正在向飞书申请一次性授权二维码,请稍候。"), + connecting + ? h("div", { className: "bxf-actions dim-viewActions", style: { justifyContent: "center" } }, + h(Button, { onClick: onCancel, disabled: busy }, "取消添加")) + : null, ); } function ProvisionError({ error, onRetry, onCancel, busy }) { - return h("div", { className: "bxf-card bxf-provisionCard" }, - h("div", { className: "bxf-inlineError", role: "alert" }, - h("div", { className: "bxf-errorIcon" }, h(AlertIcon)), + return h("div", { className: "bxf-card bxf-provisionCard dim-surfaceCard" }, + h("div", { className: "bxf-inlineError dim-inlineError", role: "alert" }, h("div", null, h("h3", null, "新机器人没有添加完成"), h("p", null, error.message), error.code ? h("span", { className: "bxf-errorCode" }, error.code) : null, - h("div", { className: "bxf-actions" }, - h(Button, { kind: "primary", icon: h(RefreshIcon), onClick: onRetry, disabled: busy }, + h("div", { className: "bxf-actions dim-viewActions" }, + h(Button, { kind: "primary", onClick: onRetry, disabled: busy }, busy ? "重试中…" : "重新生成二维码"), h(Button, { onClick: onCancel, disabled: busy }, "关闭")), ), @@ -335,7 +324,7 @@ function RemoveConfirmation({ bot, busy, onConfirm, onCancel }) { React.useEffect(() => cancelRef.current?.focus(), []); return h("div", { - className: "bxf-confirm", + className: "bxf-confirm dim-confirm", role: "alertdialog", "aria-labelledby": titleId, "aria-describedby": descriptionId, @@ -349,7 +338,7 @@ function RemoveConfirmation({ bot, busy, onConfirm, onCancel }) { h("h4", { id: titleId }, `从 DeepSeek Harness 移除“${bot.bot.name}”?`), h("p", { id: descriptionId }, "此操作会停止这个机器人的连接,并删除保存在本机的接入配置和凭据。飞书开放平台中的应用不会被自动删除,其他机器人也不受影响。"), - h("div", { className: "bxf-actions" }, + h("div", { className: "bxf-actions dim-viewActions" }, h(Button, { ref: cancelRef, onClick: onCancel, disabled: busy }, "保留机器人"), h(Button, { kind: "danger", onClick: onConfirm, disabled: busy }, busy ? "正在移除…" : "确认移除接入")), @@ -382,43 +371,42 @@ export function BotCard({ ?? health.summary; const titleId = `bxf-bot-${connection.botId.replace(/[^a-zA-Z0-9_-]/g, "-")}`; return h("article", { - className: "bxf-card bxf-botCard", + className: "bxf-card bxf-botCard dim-botCard", "aria-labelledby": titleId, "data-bot-id": connection.botId, tabIndex: -1, ref: cardRef, }, - h("div", { className: "bxf-cardBody" }, - h("div", { className: "bxf-connectedTop" }, - h("div", { className: "bxf-botIdentity" }, - h("div", { className: "bxf-avatar", "aria-hidden": "true" }, + h("div", { className: "bxf-cardBody dim-botCardBody" }, + h("div", { className: "bxf-connectedTop dim-botCardTop" }, + h("div", { className: "bxf-botIdentity dim-botIdentity" }, + h("div", { className: "bxf-avatar dim-botAvatar", "aria-hidden": "true" }, h(FeishuLogoGlyph, { size: 34 })), - h("div", { className: "bxf-botName" }, + h("div", { className: "bxf-botName dim-botName" }, h("h3", { id: titleId, title: bot.name }, bot.name), h("p", { title: bot.appIdMasked }, bot.appIdMasked ?? "应用标识已安全保存")), ), - h("div", { className: "bxf-healthPill", "data-health": stateForDisplay }, - h("span", { className: "bxf-dot", "data-tone": tone }), + h("div", { className: "bxf-healthPill dim-botHealth", "data-health": stateForDisplay }, + h("span", { className: "bxf-dot dim-healthDot", "data-tone": tone }), h("span", null, HEALTH_LABELS[stateForDisplay] ?? "状态未知")), ), - h("dl", { className: "bxf-statusGrid" }, - h("div", { className: "bxf-metric" }, h("dt", null, "消息通道"), + h("dl", { className: "bxf-statusGrid dim-botMetrics" }, + h("div", { className: "bxf-metric dim-botMetric" }, h("dt", null, "消息通道"), h("dd", null, connected ? "长连接" : stateForDisplay === "connecting" ? "连接中" : "已断开")), - h("div", { className: "bxf-metric" }, h("dt", null, "最近检查"), + h("div", { className: "bxf-metric dim-botMetric" }, h("dt", null, "最近检查"), h("dd", null, formatCheckedTime(health.lastCheckedAt))), ), - h("div", { className: "bxf-divider" }), - h("div", { className: "bxf-connectedFooter" }, - h("div", { className: "bxf-healthSummary", "data-error": actionError || connection.error ? "true" : undefined }, - h("strong", null, "连接状态:"), h("span", null, summary)), - h("div", { className: "bxf-actions bxf-botActions" }, + h("div", { className: "bxf-connectedFooter dim-cardFooter" }, + h("div", { className: "bxf-healthSummary dim-cardSummary", "data-error": actionError || connection.error ? "true" : undefined }, + summary), + h("div", { className: "bxf-actions bxf-botActions dim-cardActions" }, h(Button, { - size: "small", icon: h(RefreshIcon), onClick: onReconnect, + className: "dim-cardAction", onClick: onReconnect, disabled: Boolean(busy), "aria-busy": busy === "reconnect" ? "true" : undefined, "aria-label": `${connected ? "检查连接" : "重试连接"}${bot.name}`, }, busy === "reconnect" ? (connected ? "检查中…" : "正在连接…") : connected ? "检查连接" : "重试连接"), h(Button, { - size: "small", kind: "danger", onClick: onRequestRemove, + className: "dim-cardAction", kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy), ref: removeButtonRef, "aria-label": `从 DeepSeek Harness 移除${bot.name}`, }, "移除接入")), @@ -436,11 +424,11 @@ export function BotCard({ } function BotList(props) { - return h("section", { className: "bxf-listSection", "aria-labelledby": "bxf-bot-list-title" }, - h("div", { className: "bxf-listHeading" }, + return h("section", { className: "bxf-listSection dim-listSection", "aria-labelledby": "bxf-bot-list-title" }, + h("div", { className: "bxf-listHeading dim-listHeading" }, h("h3", { id: "bxf-bot-list-title" }, "已接入的机器人"), h("span", null, `${props.bots.length} 个`)), - h("ul", { className: "bxf-botList", role: "list" }, + h("ul", { className: "bxf-botList dim-botList", role: "list" }, props.bots.map((bot) => h("li", { key: bot.botId }, h(BotCard, { connection: bot, @@ -459,15 +447,14 @@ function BotList(props) { } function PageError({ error, onRetry, busy }) { - return h("div", { className: "bxf-card" }, - h("div", { className: "bxf-error", role: "alert" }, - h("div", { className: "bxf-errorIcon" }, h(AlertIcon)), + return h("div", { className: "bxf-card dim-surfaceCard" }, + h("div", { className: "bxf-error dim-inlineError", role: "alert" }, h("div", null, h("h3", null, "无法读取飞书机器人"), h("p", null, error.message), error.code ? h("span", { className: "bxf-errorCode" }, error.code) : null, - h("div", { className: "bxf-actions" }, - h(Button, { kind: "primary", icon: h(RefreshIcon), onClick: onRetry, disabled: busy }, + h("div", { className: "bxf-actions dim-viewActions" }, + h(Button, { kind: "primary", onClick: onRetry, disabled: busy }, busy ? "重试中…" : "重新读取"))), ), ); @@ -874,7 +861,7 @@ export function FeishuSettingsTab({ rpcCall }) { else removeButtonRefs.current.delete(botId); }, []); - return h("section", { className: "bxf-page", "aria-label": "飞书机器人设置" }, + return h("section", { className: "bxf-page dim-channelPage", "aria-label": "飞书机器人设置" }, h(Heading, { totals: model.totals, onAdd: () => void startProvisioning(), @@ -886,7 +873,7 @@ export function FeishuSettingsTab({ rpcCall }) { className: "bxf-visuallyHidden", role: "status", "aria-live": "polite", "aria-atomic": "true", }, announcement), model.statusError - ? h("div", { className: "bxf-statusNotice", role: "status" }, + ? h("div", { className: "bxf-statusNotice dim-statusNotice", role: "status" }, h(AlertIcon, { size: 16 }), h("span", null, `状态自动刷新失败:${model.statusError.message}`), h(Button, { size: "small", onClick: () => void loadStatus({ silent: true }), disabled: pageBusy }, "立即重试")) diff --git a/plugin-src/client/channels/feishu/styles.js b/plugin-src/client/channels/feishu/styles.js index f319ce5..fe9192b 100644 --- a/plugin-src/client/channels/feishu/styles.js +++ b/plugin-src/client/channels/feishu/styles.js @@ -60,7 +60,7 @@ const CSS = String.raw` flex: none; display: flex; align-items: center; - justify-content: flex-end; + justify-content: space-between; flex-wrap: nowrap; gap: 8px; } @@ -81,22 +81,6 @@ const CSS = String.raw` .bxf-totalBadge strong { color: var(--bxf-success); font-size: 13px; } -.bxf-localBadge { - flex: none; - display: inline-flex; - align-items: center; - gap: 6px; - min-height: 28px; - border: 1px solid var(--dsw-alias-border-l2, #dee0e3); - border-radius: 999px; - padding: 4px 10px; - color: var(--dsw-alias-label-secondary, #646a73); - background: var(--dsw-alias-bg-layer-1, #fff); - font-size: 11px; - line-height: 16px; - white-space: nowrap; -} - .bxf-card { position: relative; overflow: hidden; @@ -424,12 +408,11 @@ const CSS = String.raw` .bxf-metric dt { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; line-height: 17px; } .bxf-metric dd { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-primary, #1f2329); font-size: 12px; line-height: 18px; font-weight: 550; margin: 3px 0 0; } -.bxf-divider { height: 1px; background: var(--dsw-alias-border-l2, #dee0e3); margin: 22px 0 0; } -.bxf-connectedFooter { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-top: 17px; } +.bxf-connectedFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-line-divider, #eef0f3); } .bxf-healthSummary { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; } -.bxf-healthSummary strong { color: var(--dsw-alias-label-primary, #1f2329); font-weight: 600; } .bxf-healthSummary[data-error="true"] { color: var(--bxf-error); } -.bxf-botActions { margin-top: 0; justify-content: flex-end; } +.bxf-botActions { flex: none; flex-wrap: nowrap; gap: 8px; margin-top: 0; justify-content: flex-end; } +.bxf-botActions .bxf-button { flex: none; white-space: nowrap; } .bxf-confirm { border-top: 1px solid var(--dsw-alias-border-l2, #dee0e3); @@ -478,8 +461,7 @@ const CSS = String.raw` @container (max-width: 620px) { .bxf-headingTools { gap: 6px; } - .bxf-headingTools .bxf-totalBadge, .bxf-headingTools .bxf-localBadge { padding-inline: 8px; } - .bxf-headingTools .bxf-localBadge { gap: 4px; } + .bxf-headingTools .bxf-totalBadge { padding-inline: 8px; } .bxf-headingTools .bxf-bindButton { padding-inline: 10px; } } @@ -489,9 +471,7 @@ const CSS = String.raw` .bxf-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; } .bxf-qrCopy { width: 100%; } .bxf-statusGrid { grid-template-columns: minmax(0, 1fr); } - .bxf-connectedTop, .bxf-connectedFooter { align-items: flex-start; flex-direction: column; } - .bxf-botActions { width: 100%; justify-content: flex-start; } - .bxf-botActions .bxf-button { min-height: 44px; } + .bxf-connectedTop { align-items: flex-start; flex-direction: column; } .bxf-inlineError { grid-template-columns: minmax(0, 1fr); padding: 20px; } .bxf-statusNotice { align-items: flex-start; flex-wrap: wrap; } .bxf-cardBody { padding: 20px; } diff --git a/plugin-src/client/channels/weixin/index.js b/plugin-src/client/channels/weixin/index.js index d2de587..5049fb7 100644 --- a/plugin-src/client/channels/weixin/index.js +++ b/plugin-src/client/channels/weixin/index.js @@ -36,42 +36,40 @@ const Button = React.forwardRef(function Button( function Heading({ totals, adding, busy, onAdd, addButtonRef }) { return h('div', { className: 'dxw-heading' }, h('div', { className: 'dxw-tools' }, - totals.configured > 0 - ? h('div', { className: 'dxw-badge' }, - h('span', { className: 'dxw-dot', 'data-tone': totals.connected > 0 ? 'success' : 'warning' }), - h('span', null, `${totals.connected} / ${totals.configured} 在线`)) - : null, - h('div', { className: 'dxw-badge', title: 'bot_token 仅保存在 Harness Host 凭据服务中' }, - h('span', null, '凭据仅保存在本机')), h(Button, { kind: 'primary', + className: 'dim-scanButton', onClick: onAdd, disabled: adding || busy, ref: addButtonRef, }, adding ? '正在绑定' : '扫码绑定微信'), + totals.configured > 0 + ? h('div', { className: 'dxw-badge dim-onlineBadge' }, + h('span', null, `${totals.connected} / ${totals.configured} 在线`)) + : null, ), ); } function LoadingView() { - return h('div', { className: 'dxw-card dxw-loading', 'aria-busy': 'true' }, - h('div', { className: 'dxw-spinner' }), + return h('div', { className: 'dxw-card dxw-loading dim-surfaceCard dim-loadingView', 'aria-busy': 'true' }, + h('div', { className: 'dxw-spinner dim-spinner' }), h('span', null, '正在读取微信连接状态…')); } function EmptyView({ onStart, busy }) { - return h('div', { className: 'dxw-card' }, - h('div', { className: 'dxw-cardBody dxw-empty' }, - h('div', null, - h('div', { className: 'dxw-stateLabel' }, - h('span', { className: 'dxw-dot' }), h('span', null, '尚未绑定微信')), + return h('div', { className: 'dxw-card dim-surfaceCard' }, + h('div', { className: 'dxw-cardBody dxw-empty dim-surfaceBody dim-emptyView' }, + h('div', { className: 'dim-emptyCopy' }, + h('div', { className: 'dxw-stateLabel dim-stateLabel' }, + h('span', { className: 'dxw-dot dim-stateDot' }), h('span', null, '尚未绑定微信')), h('h3', null, '扫一次码,就能在微信里使用 Harness'), h('p', null, '二维码由腾讯微信 iLink 服务签发。用手机微信扫描并确认后,账号凭据会直接写入 Harness Host,浏览器不会收到 bot_token。'), - h('div', { className: 'dxw-actions' }, + h('div', { className: 'dxw-actions dim-viewActions' }, h(Button, { kind: 'primary', onClick: onStart, disabled: busy }, busy ? '正在生成二维码…' : '生成微信二维码')), ), - h('div', { className: 'dxw-logo', 'aria-hidden': 'true' }, h(WeixinLogoGlyph, { size: 64 })), + h('div', { className: 'dxw-logo dim-emptyBrand', 'aria-hidden': 'true' }, h(WeixinLogoGlyph, { size: 64 })), )); } @@ -85,35 +83,35 @@ function QrPanel({ provision, now, busy, onRefresh, onCancel }) { const progress = Math.round(Math.min(1, remaining / duration) * 100); React.useEffect(() => setImageFailed(false), [source]); - return h('div', { className: 'dxw-card' }, - h('div', { className: 'dxw-cardBody dxw-qrLayout' }, - h('div', { className: 'dxw-qrColumn' }, - h('div', { className: 'dxw-qrFrame' }, + return h('div', { className: 'dxw-card dim-surfaceCard' }, + h('div', { className: 'dxw-cardBody dxw-qrLayout dim-surfaceBody dim-qrLayout' }, + h('div', { className: 'dxw-qrColumn dim-qrColumn' }, + h('div', { className: 'dxw-qrFrame dim-qrFrame' }, source && !imageFailed ? h('img', { src: source, alt: '用于把微信机器人绑定到 DeepSeek Harness 的一次性二维码', onError: () => setImageFailed(true), }) - : h('div', { className: 'dxw-qrFallback' }, '二维码图片未就绪,请使用备用链接。'), - expired ? h('div', { className: 'dxw-expired' }, '二维码已过期\n请重新生成') : null, + : h('div', { className: 'dxw-qrFallback dim-qrFallback' }, '二维码图片未就绪,请使用备用链接。'), + expired ? h('div', { className: 'dxw-expired dim-qrExpired' }, '二维码已过期\n请重新生成') : null, ), - h('div', { className: 'dxw-countdown' }, - h('div', null, h('span', null, '二维码有效时间'), h('strong', null, formatRemaining(remaining))), - h('div', { className: 'dxw-progress', 'aria-hidden': 'true' }, + h('div', { className: 'dxw-countdown dim-countdown' }, + h('div', { className: 'dim-countdownTop' }, h('span', null, '二维码有效时间'), h('strong', null, formatRemaining(remaining))), + h('div', { className: 'dxw-progress dim-progress', 'aria-hidden': 'true' }, h('span', { style: { '--dxw-progress': `${progress}%` } })), )), - h('div', { className: 'dxw-qrCopy' }, - h('div', { className: 'dxw-stateLabel' }, - h('span', { className: 'dxw-dot', 'data-tone': provision.status === 'scanned' ? 'success' : 'warning' }), + h('div', { className: 'dxw-qrCopy dim-qrCopy' }, + h('div', { className: 'dxw-stateLabel dim-stateLabel' }, + h('span', { className: 'dxw-dot dim-stateDot', 'data-tone': provision.status === 'scanned' ? 'success' : 'warning' }), h('span', null, provision.status === 'scanned' ? '已扫码,请在手机上确认' : '等待微信扫码')), h('h3', null, expired ? '二维码已失效' : '使用手机微信扫描二维码'), h('p', null, '请在手机上核对并确认授权。部分账号会额外显示一个配对数字,页面会在需要时提示输入。'), - h('ol', { className: 'dxw-steps' }, + h('ol', { className: 'dxw-steps dim-steps' }, h('li', null, '打开手机微信并扫描左侧二维码'), h('li', null, '在微信中确认连接该机器人'), h('li', null, '保持本页打开,等待消息长轮询变为在线')), - h('div', { className: 'dxw-actions' }, + h('div', { className: 'dxw-actions dim-viewActions' }, expired ? h(Button, { kind: 'primary', onClick: onRefresh, disabled: busy }, '重新生成二维码') : null, @@ -130,9 +128,9 @@ function VerificationPanel({ provision, busy, onSubmit, onCancel }) { const [code, setCode] = React.useState(''); const valid = /^\d{4,8}$/.test(code); React.useEffect(() => setCode(''), [provision.attemptId]); - return h('div', { className: 'dxw-card' }, + return h('div', { className: 'dxw-card dim-surfaceCard' }, h('form', { - className: 'dxw-verify', + className: 'dxw-verify dim-specialView', onSubmit: (event) => { event.preventDefault(); if (valid && !busy) onSubmit(code); @@ -163,22 +161,22 @@ function VerificationPanel({ provision, busy, onSubmit, onCancel }) { } function ProgressPanel({ scanned, onCancel, busy }) { - return h('div', { className: 'dxw-card dxw-loading', 'aria-busy': 'true' }, - h('div', { className: 'dxw-spinner' }), + return h('div', { className: 'dxw-card dxw-loading dim-surfaceCard dim-loadingView', 'aria-busy': 'true' }, + h('div', { className: 'dxw-spinner dim-spinner' }), h('h3', null, scanned ? '微信已确认,正在启动消息连接' : '正在准备微信二维码'), h('p', null, scanned ? '正在保存凭据并验证 Harness 与微信长轮询。' : '正在联系腾讯微信 iLink 服务。'), - onCancel ? h('div', { className: 'dxw-actions', style: { justifyContent: 'center', marginTop: 14 } }, + onCancel ? h('div', { className: 'dxw-actions dim-viewActions', style: { justifyContent: 'center', marginTop: 14 } }, h(Button, { onClick: onCancel, disabled: busy }, '取消')) : null); } function ProvisionError({ provision, busy, onRetry, onClose }) { const error = provision.error ?? { code: 'WEIXIN_PROVISION_FAILED', message: '微信绑定没有完成' }; - return h('div', { className: 'dxw-card' }, - h('div', { className: 'dxw-error', role: 'alert' }, + return h('div', { className: 'dxw-card dim-surfaceCard' }, + h('div', { className: 'dxw-error dim-inlineError', role: 'alert' }, h('h3', null, provision.status === 'expired' ? '二维码已过期' : '微信没有绑定完成'), h('p', null, error.message), h('span', { className: 'dxw-errorCode' }, error.code), - h('div', { className: 'dxw-actions' }, + h('div', { className: 'dxw-actions dim-viewActions' }, h(Button, { kind: 'primary', onClick: onRetry, disabled: busy }, '重新生成二维码'), h(Button, { onClick: onClose, disabled: busy }, '关闭')))); } @@ -197,30 +195,30 @@ function checkedTime(timestamp) { export function AccountCard({ account, busy, removing, onReconnect, onRequestRemove, onConfirmRemove, onCancelRemove }) { const state = busy === 'reconnect' ? 'connecting' : account.state; const tone = account.connected ? 'success' : state === 'error' ? 'error' : 'warning'; - return h('article', { className: 'dxw-card', tabIndex: -1, 'data-bot-id': account.botId }, - h('div', { className: 'dxw-cardBody' }, - h('div', { className: 'dxw-accountTop' }, - h('div', { className: 'dxw-accountIdentity' }, - h('div', { className: 'dxw-avatar', 'aria-hidden': 'true' }, h(WeixinLogoGlyph, { size: 27 })), - h('div', null, h('h3', null, account.bot.name), h('p', null, account.bot.accountIdMasked))), - h('div', { className: 'dxw-health' }, - h('span', { className: 'dxw-dot', 'data-tone': tone }), + return h('article', { className: 'dxw-card dim-botCard', tabIndex: -1, 'data-bot-id': account.botId }, + h('div', { className: 'dxw-cardBody dim-botCardBody' }, + h('div', { className: 'dxw-accountTop dim-botCardTop' }, + h('div', { className: 'dxw-accountIdentity dim-botIdentity' }, + h('div', { className: 'dxw-avatar dim-botAvatar', 'aria-hidden': 'true' }, h(WeixinLogoGlyph, { size: 27 })), + h('div', { className: 'dim-botName' }, h('h3', null, account.bot.name), h('p', null, account.bot.accountIdMasked))), + h('div', { className: 'dxw-health dim-botHealth' }, + h('span', { className: 'dxw-dot dim-healthDot', 'data-tone': tone }), h('span', null, account.connected ? '运行正常' : state === 'connecting' ? '正在连接' : '连接未就绪'))), - h('dl', { className: 'dxw-metrics' }, - h('div', { className: 'dxw-metric' }, h('dt', null, '消息通道'), + h('dl', { className: 'dxw-metrics dim-botMetrics' }, + h('div', { className: 'dxw-metric dim-botMetric' }, h('dt', null, '消息通道'), h('dd', null, account.connected ? 'iLink 长轮询' : '离线')), - h('div', { className: 'dxw-metric' }, h('dt', null, '最近检查'), + h('div', { className: 'dxw-metric dim-botMetric' }, h('dt', null, '最近检查'), h('dd', null, checkedTime(account.health.lastCheckedAt)))), - h('div', { className: 'dxw-accountFooter' }, - h('div', { className: 'dxw-summary' }, account.error?.message ?? account.health.summary), - h('div', { className: 'dxw-actions' }, - h(Button, { onClick: onReconnect, disabled: Boolean(busy) }, + h('div', { className: 'dxw-accountFooter dim-cardFooter' }, + h('div', { className: 'dxw-summary dim-cardSummary' }, account.error?.message ?? account.health.summary), + h('div', { className: 'dxw-actions dim-cardActions' }, + h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'), - h(Button, { kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')))), - removing ? h('div', { className: 'dxw-confirm', role: 'alertdialog' }, + h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')))), + removing ? h('div', { className: 'dxw-confirm dim-confirm', role: 'alertdialog' }, h('strong', null, '从此 Harness 移除这个微信账号?'), h('p', null, '这会停止消息连接,并删除本机保存的 bot_token、账号配置和会话映射。其他微信账号不受影响。'), - h('div', { className: 'dxw-actions' }, + h('div', { className: 'dxw-actions dim-viewActions' }, h(Button, { onClick: onCancelRemove, disabled: busy === 'delete' }, '保留账号'), h(Button, { kind: 'danger', onClick: onConfirmRemove, disabled: busy === 'delete' }, busy === 'delete' ? '正在移除…' : '确认移除'))) @@ -228,9 +226,9 @@ export function AccountCard({ account, busy, removing, onReconnect, onRequestRem } function AccountList(props) { - return h('section', null, - h('div', { className: 'dxw-listHeading' }, h('h3', null, '已接入的微信账号'), h('span', null, `${props.bots.length} 个`)), - h('ul', { className: 'dxw-list' }, props.bots.map((account) => h('li', { key: account.botId }, + return h('section', { className: 'dim-listSection' }, + h('div', { className: 'dxw-listHeading dim-listHeading' }, h('h3', null, '已接入的微信账号'), h('span', null, `${props.bots.length} 个`)), + h('ul', { className: 'dxw-list dim-botList' }, props.bots.map((account) => h('li', { key: account.botId }, h(AccountCard, { account, busy: props.busyByBot[account.botId], @@ -523,7 +521,7 @@ export function WeixinSettingsTab({ rpcCall }) { }); } - return h('section', { className: 'dxw-page', 'aria-label': '微信设置' }, + return h('section', { className: 'dxw-page dim-channelPage', 'aria-label': '微信设置' }, h(Heading, { totals: model.totals, adding: Boolean(provision), @@ -533,13 +531,13 @@ export function WeixinSettingsTab({ rpcCall }) { }), h('div', { className: 'dxw-visuallyHidden', role: 'status', 'aria-live': 'polite' }, notice), model.error && model.phase === 'ready' - ? h('div', { className: 'dxw-statusNotice' }, `状态刷新失败:${model.error.message}`) + ? h('div', { className: 'dxw-statusNotice dim-statusNotice' }, `状态刷新失败:${model.error.message}`) : null, model.phase === 'loading' ? h(LoadingView) : model.phase === 'error' - ? h('div', { className: 'dxw-card' }, - h('div', { className: 'dxw-error' }, + ? h('div', { className: 'dxw-card dim-surfaceCard' }, + h('div', { className: 'dxw-error dim-inlineError' }, h('h3', null, '无法读取微信状态'), h('p', null, model.error?.message ?? '请稍后重试'), h(Button, { onClick: () => void loadStatus() }, '重新读取'))) diff --git a/plugin-src/client/channels/weixin/styles.js b/plugin-src/client/channels/weixin/styles.js index 05b1619..fcc94e1 100644 --- a/plugin-src/client/channels/weixin/styles.js +++ b/plugin-src/client/channels/weixin/styles.js @@ -23,7 +23,7 @@ const CSS = String.raw` .dxw-heading h2 { font-size: 20px; line-height: 28px; font-weight: 680; } .dxw-heading p { color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 20px; margin-top: 5px; white-space: nowrap; } .dxw-tools, .dxw-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; } -.dxw-tools { justify-content: flex-end; } +.dxw-tools { width: 100%; justify-content: space-between; flex-wrap: nowrap; } .dxw-badge { display: inline-flex; align-items: center; gap: 7px; min-height: 30px; padding: 0 11px; border-radius: 999px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-fill-secondary, #f2f3f5); font-size: 12px; white-space: nowrap; } .dxw-dot { width: 8px; height: 8px; border-radius: 50%; background: #aeb3bb; flex: none; } .dxw-dot[data-tone="success"] { background: var(--dxw-success); box-shadow: 0 0 0 3px color-mix(in srgb, var(--dxw-success) 14%, transparent); } @@ -83,6 +83,8 @@ const CSS = String.raw` .dxw-metric dt { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; } .dxw-metric dd { overflow: hidden; margin: 5px 0 0; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; } .dxw-accountFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-line-divider, #eef0f3); } +.dxw-accountFooter .dxw-actions { flex: none; flex-wrap: nowrap; gap: 8px; margin-top: 0; } +.dxw-accountFooter .dxw-button { flex: none; white-space: nowrap; } .dxw-summary { color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; } .dxw-confirm { padding: 18px 24px; border-top: 1px solid color-mix(in srgb, var(--dxw-error) 25%, transparent); background: color-mix(in srgb, var(--dxw-error) 5%, transparent); } .dxw-confirm strong { display: block; font-size: 14px; margin-bottom: 6px; } @@ -93,8 +95,7 @@ const CSS = String.raw` .dxw-visuallyHidden { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; } @keyframes dxw-spin { to { transform: rotate(360deg); } } @media (max-width: 720px) { - .dxw-heading, .dxw-accountTop, .dxw-accountFooter { flex-direction: column; align-items: stretch; } - .dxw-tools { justify-content: flex-start; } + .dxw-heading, .dxw-accountTop { flex-direction: column; align-items: stretch; } .dxw-empty { grid-template-columns: minmax(0, 1fr); } .dxw-logo { display: none; } .dxw-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; } diff --git a/plugin-src/client/styles.js b/plugin-src/client/styles.js index e4c5487..b6b0bed 100644 --- a/plugin-src/client/styles.js +++ b/plugin-src/client/styles.js @@ -35,10 +35,100 @@ const CSS = String.raw` .dim-panel { min-width: 0; container-type: inline-size; } .dim-panel .bxf-page, .dim-panel .dxw-page, .dim-panel .ddt-page { width: 100%; max-width: none; padding: 0 0 24px; } .dim-panel .bxf-heading, .dim-panel .dxw-heading, .dim-panel .ddt-heading { justify-content: flex-end; } -.dim-panel .bxf-headingTools, .dim-panel .dxw-tools, .dim-panel .ddt-tools { width: 100%; justify-content: flex-end; } +.dim-panel .bxf-headingTools, .dim-panel .dxw-tools, .dim-panel .ddt-tools { width: 100%; justify-content: space-between; } +.dim-panel .bxf-headingTools .dim-scanButton, .dim-panel .dxw-tools .dim-scanButton, .dim-panel .ddt-tools .dim-scanButton { flex: none; min-height: 34px; padding: 0 13px; border: 1px solid #1677ff; border-radius: 8px; color: #fff; background: #1677ff; box-shadow: none; font: inherit; font-size: 13px; font-weight: 560; white-space: nowrap; } +.dim-panel .bxf-headingTools .dim-scanButton:hover:not(:disabled), .dim-panel .dxw-tools .dim-scanButton:hover:not(:disabled), .dim-panel .ddt-tools .dim-scanButton:hover:not(:disabled) { border-color: #0958d9; background: #0958d9; } +.dim-panel .bxf-headingTools .dim-onlineBadge, .dim-panel .dxw-tools .dim-onlineBadge, .dim-panel .ddt-tools .dim-onlineBadge { flex: none; min-height: 30px; display: inline-flex; align-items: center; gap: 0; padding: 0 11px; border: 0; border-radius: 999px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-fill-secondary, #f2f3f5); font: inherit; font-size: 12px; font-weight: 400; line-height: normal; white-space: nowrap; } +.dim-panel .dim-channelPage { width: 100%; max-width: none; display: flex; flex-direction: column; gap: 18px; padding: 0 0 24px; color: var(--dsw-alias-label-primary, #1f2329); box-sizing: border-box; } +.dim-panel .dim-surfaceCard { position: relative; overflow: hidden; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-body, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 3%); } +.dim-panel .dim-surfaceCard::before { display: none; } +.dim-panel .dim-surfaceBody { padding: 24px; } +.dim-panel .dim-listSection { display: flex; flex-direction: column; gap: 0; } +.dim-panel .dim-listHeading { min-height: 0; display: flex; align-items: center; justify-content: space-between; gap: 16px; margin: 2px 0 9px; padding: 0; } +.dim-panel .dim-listHeading h3 { margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 14px; line-height: normal; font-weight: 650; } +.dim-panel .dim-listHeading span { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 12px; line-height: normal; } +.dim-panel .dim-botList { display: grid; gap: 12px; margin: 0; padding: 0; list-style: none; } +.dim-panel .dim-loadingView { padding: 38px; color: var(--dsw-alias-label-secondary, #646a73); text-align: center; } +.dim-panel .dim-loadingView h3 { margin: 0 0 7px; color: var(--dsw-alias-label-primary, #1f2329); font-size: 17px; line-height: normal; font-weight: 650; } +.dim-panel .dim-loadingView p { margin: 0; line-height: 1.6; } +.dim-panel .dim-spinner { width: 24px; height: 24px; margin: 0 auto 13px; border: 3px solid var(--dsw-alias-line-border, #e6e8eb); border-top-color: #1677ff; border-radius: 50%; animation: dim-spin .8s linear infinite; } +@keyframes dim-spin { to { transform: rotate(360deg); } } +.dim-panel .dim-emptyView { min-height: 230px; display: grid; grid-template-columns: minmax(0, 1fr) 180px; align-items: center; gap: 30px; } +.dim-panel .dim-emptyCopy { min-width: 0; } +.dim-panel .dim-emptyCopy h3 { margin: 8px 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 18px; line-height: 1.35; font-weight: 650; } +.dim-panel .dim-emptyCopy > p { max-width: 560px; margin: 0; color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.65; } +.dim-panel .dim-emptyBrand { width: 110px; height: 110px; display: grid; place-items: center; justify-self: center; border-radius: 28px; box-shadow: 0 18px 45px rgb(22 119 255 / 18%); } +.dim-panel .dim-stateLabel { display: inline-flex; align-items: center; gap: 8px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: normal; font-weight: 600; } +.dim-panel .dim-stateDot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--dsw-alias-label-tertiary, #8f959e); box-shadow: none; } +.dim-panel .dim-stateDot[data-tone="success"] { background: var(--dsw-alias-state-success-primary, #20a162); } +.dim-panel .dim-stateDot[data-tone="warning"] { background: var(--dsw-alias-state-warning-primary, #d97706); } +.dim-panel .dim-stateDot[data-tone="error"] { background: var(--dsw-alias-state-error-primary, #d54941); } +.dim-panel .dim-viewActions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-top: 20px; } +.dim-panel .dim-viewActions .bxf-button, .dim-panel .dim-viewActions .dxw-button, .dim-panel .dim-viewActions .ddt-button { min-height: 34px; padding: 0 13px; border: 1px solid var(--dsw-alias-line-border, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-body, #fff); box-shadow: none; font: inherit; font-size: 13px; font-weight: 560; line-height: normal; white-space: nowrap; } +.dim-panel .dim-viewActions .bxf-button[data-kind="primary"], .dim-panel .dim-viewActions .dxw-button[data-kind="primary"], .dim-panel .dim-viewActions .ddt-button[data-kind="primary"] { border-color: #1677ff; color: #fff; background: #1677ff; box-shadow: none; } +.dim-panel .dim-viewActions .bxf-button[data-kind="danger"], .dim-panel .dim-viewActions .dxw-button[data-kind="danger"], .dim-panel .dim-viewActions .ddt-button[data-kind="danger"] { color: var(--dsw-alias-state-error-primary, #d54941); } +.dim-panel .dim-qrLayout { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 34px; align-items: start; } +.dim-panel .dim-qrColumn { width: 100%; min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 12px; } +.dim-panel .dim-qrFrame { position: relative; width: min(270px, 100%); aspect-ratio: 1; display: grid; place-items: center; overflow: hidden; padding: 10px; border: 1px solid var(--dsw-alias-line-border, #dfe1e5); border-radius: 16px; background: #fff; } +.dim-panel .dim-qrFrame::before { content: ""; position: absolute; inset: 7px; z-index: 0; border: 1px solid color-mix(in srgb, #1677ff 16%, var(--dsw-alias-line-border, #dfe1e5)); border-radius: 12px; pointer-events: none; } +.dim-panel .dim-qrFrame::after { display: none; } +.dim-panel .dim-qrFrame img { position: relative; z-index: 1; width: 100%; height: 100%; display: block; object-fit: contain; } +.dim-panel .dim-qrFallback { position: relative; z-index: 1; display: grid; place-items: center; gap: 8px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 1.5; text-align: center; } +.dim-panel .dim-qrExpired { position: absolute; inset: 0; z-index: 2; display: grid; place-items: center; padding: 20px; color: var(--dsw-alias-label-primary, #1f2329); background: rgb(255 255 255 / 92%); font-size: 15px; line-height: 1.6; font-weight: 650; text-align: center; white-space: pre-line; backdrop-filter: blur(3px); } +.dim-panel .dim-countdown { width: min(270px, 100%); margin: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: normal; } +.dim-panel .dim-countdownTop { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 6px; } +.dim-panel .dim-countdownTop strong { color: var(--dsw-alias-label-primary, #1f2329); font-weight: 650; } +.dim-panel .dim-progress { height: 4px; overflow: hidden; margin: 0; border-radius: 99px; background: var(--dsw-alias-fill-secondary, #eef0f3); } +.dim-panel .dim-progress span { display: block; width: var(--bxf-progress, var(--dxw-progress, var(--ddt-progress, 0%))); height: 100%; border-radius: inherit; background: #1677ff; transition: width .25s linear; } +.dim-panel .dim-qrCopy { min-width: 0; overflow-wrap: anywhere; } +.dim-panel .dim-qrCopy h3 { margin: 9px 0 8px; color: var(--dsw-alias-label-primary, #1f2329); font-size: 18px; line-height: 1.35; font-weight: 650; } +.dim-panel .dim-qrCopy > p { margin: 0; color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.65; } +.dim-panel .dim-steps { margin: 18px 0 16px; padding: 0; list-style: none; counter-reset: dim-step; } +.dim-panel .dim-steps li { position: relative; min-height: 28px; display: flex; align-items: center; padding: 5px 0 5px 36px; color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.5; counter-increment: dim-step; } +.dim-panel .dim-steps li::before { content: counter(dim-step); position: absolute; left: 0; top: 4px; width: 25px; height: 25px; display: grid; place-items: center; border-radius: 8px; color: #1677ff; background: color-mix(in srgb, #1677ff 10%, white); font-size: 12px; font-weight: 650; } +.dim-panel .dim-specialView { padding: 32px; text-align: center; } +.dim-panel .dim-statusNotice { display: flex; align-items: flex-start; gap: 10px; padding: 13px 15px; border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary, #d54941) 22%, white); border-radius: 10px; color: var(--dsw-alias-state-error-primary, #d54941); background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #d54941) 6%, white); font-size: 13px; line-height: 1.5; } +.dim-panel .dim-inlineError { display: flex; align-items: flex-start; flex-direction: column; gap: 10px; padding: 22px; color: var(--dsw-alias-state-error-primary, #d54941); background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #d54941) 6%, white); } +.dim-panel .dim-inlineError > div { min-width: 0; } +.dim-panel .dim-inlineError h3 { margin: 0; color: inherit; font-size: 17px; line-height: 1.35; font-weight: 650; } +.dim-panel .dim-inlineError p { margin: 7px 0 0; color: inherit; line-height: 1.6; } +.dim-panel .dim-confirm { padding: 18px 24px; border-top: 1px solid var(--dsw-alias-line-divider, #eef0f3); background: var(--dsw-alias-fill-tertiary, #f7f8fa); } +.dim-panel .dim-confirm strong, .dim-panel .dim-confirm h4 { margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 14px; line-height: 1.4; font-weight: 650; } +.dim-panel .dim-confirm p { margin: 7px 0 0; color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.6; } +.dim-panel .dim-cardFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-line-divider, #eef0f3); } +.dim-panel .dim-cardSummary { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font: inherit; font-size: 12px; font-weight: 400; line-height: normal; } +.dim-panel .dim-cardActions { flex: none; display: flex; align-items: center; flex-wrap: nowrap; gap: 8px; margin-top: 0; } +.dim-panel .dim-cardActions .dim-cardAction { flex: none; min-height: 34px; padding: 0 13px; border: 1px solid var(--dsw-alias-line-border, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-body, #fff); font: inherit; font-size: 13px; font-weight: 560; line-height: normal; white-space: nowrap; } +.dim-panel .dim-cardActions .dim-cardAction:hover:not(:disabled) { border-color: #aeb3bb; background: var(--dsw-alias-fill-tertiary, #f7f8fa); } +.dim-panel .dim-cardActions .dim-cardAction[data-kind="danger"] { color: var(--dsw-alias-state-error-primary, #d54941); } +.dim-panel .dim-botCard { position: relative; overflow: hidden; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-body, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 3%); } +.dim-panel .dim-botCard::before { display: none; } +.dim-panel .dim-botCardBody { position: relative; padding: 24px; } +.dim-panel .dim-botCardTop { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; } +.dim-panel .dim-botIdentity { min-width: 0; display: flex; align-items: center; gap: 12px; } +.dim-panel .dim-botAvatar { flex: none; width: 42px; height: 42px; display: grid; place-items: center; overflow: hidden; border-radius: 12px; box-shadow: none; } +.dim-panel .dim-botAvatar svg { width: 29px; height: 29px; } +.dim-panel .dim-botName { min-width: 0; } +.dim-panel .dim-botName h3 { overflow: hidden; margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 15px; font-weight: 650; line-height: normal; text-overflow: ellipsis; white-space: nowrap; } +.dim-panel .dim-botName p { overflow: hidden; margin: 4px 0 0; color: var(--dsw-alias-label-secondary, #646a73); font: 12px ui-monospace, SFMono-Regular, monospace; line-height: normal; text-overflow: ellipsis; white-space: nowrap; } +.dim-panel .dim-botCard .dim-botHealth { flex: none; min-height: 0; display: inline-flex; align-items: center; gap: 7px; padding: 0; border: 0; border-radius: 0; color: var(--dsw-alias-label-secondary, #646a73); background: transparent; font: inherit; font-size: 12px; font-weight: 400; line-height: normal; white-space: nowrap; } +.dim-panel .dim-botCard .dim-healthDot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: #aeb3bb; box-shadow: none; } +.dim-panel .dim-botCard .dim-healthDot[data-tone="success"] { background: var(--dsw-alias-state-success-primary, #20a162); box-shadow: 0 0 0 3px color-mix(in srgb, var(--dsw-alias-state-success-primary, #20a162) 14%, transparent); } +.dim-panel .dim-botCard .dim-healthDot[data-tone="warning"] { background: var(--dsw-alias-state-warning-primary, #d97706); } +.dim-panel .dim-botCard .dim-healthDot[data-tone="error"] { background: var(--dsw-alias-state-error-primary, #d54941); } +.dim-panel .dim-botMetrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 20px 0; } +.dim-panel .dim-botMetric { min-width: 0; padding: 12px; border: 0; border-radius: 9px; background: var(--dsw-alias-fill-tertiary, #f7f8fa); } +.dim-panel .dim-botMetric dt { margin: 0; color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; font-weight: 400; line-height: normal; } +.dim-panel .dim-botMetric dd { overflow: hidden; margin: 5px 0 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 13px; font-weight: 400; line-height: normal; text-overflow: ellipsis; white-space: nowrap; } +.dim-panel .dim-botCard .dim-cardFooter { margin-top: 0; } .dim-panel .ddt-headingCopy { display: none; } .dim-panel .ddt-qrFrame, .dim-panel .ddt-countdown { width: min(270px, 100%); } @container (max-width: 680px) { + .dim-panel .dim-emptyView { min-height: 0; grid-template-columns: minmax(0, 1fr); } + .dim-panel .dim-emptyBrand { display: none; } + .dim-panel .dim-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; gap: 24px; } + .dim-panel .dim-qrColumn { width: 100%; min-width: 0; } + .dim-panel .dim-qrCopy { width: 100%; min-width: 0; overflow-wrap: anywhere; } .dim-panel .ddt-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; gap: 24px; } .dim-panel .ddt-qrColumn { width: 100%; min-width: 0; } .dim-panel .ddt-qrCopy { width: 100%; min-width: 0; overflow-wrap: anywhere; } @@ -51,6 +141,9 @@ const CSS = String.raw` .dim-rail { max-height: none; overflow: visible; padding-right: 1px; } .dim-channel { min-height: 48px; } } +@media (max-width: 720px) { + .dim-panel .dim-botCardTop { flex-direction: column; align-items: stretch; } +} @media (max-width: 560px) { .dim-title { flex-direction: column; gap: 10px; } .dim-title p { white-space: normal; } diff --git a/test/client-ui.test.mjs b/test/client-ui.test.mjs index de3e300..06bcda3 100644 --- a/test/client-ui.test.mjs +++ b/test/client-ui.test.mjs @@ -7,18 +7,40 @@ import { renderToStaticMarkup } from 'react-dom/server'; import { IMSettingsTab } from '../plugin-src/client/index.js'; import { DINGTALK_ENDPOINTS } from '../plugin-src/client/channels/dingtalk/api.js'; -import { AccountCard as DingtalkAccountCard } from '../plugin-src/client/channels/dingtalk/index.js'; +import { + AccountCard as DingtalkAccountCard, + DingtalkSettingsTab, +} from '../plugin-src/client/channels/dingtalk/index.js'; import { BotCard as FeishuBotCard, FeishuSettingsTab, } from '../plugin-src/client/channels/feishu/index.js'; -import { AccountCard as WeixinAccountCard } from '../plugin-src/client/channels/weixin/index.js'; +import { + AccountCard as WeixinAccountCard, + WeixinSettingsTab, +} from '../plugin-src/client/channels/weixin/index.js'; const STYLES_URL = new URL('../plugin-src/client/styles.js', import.meta.url); const FEISHU_STYLES_URL = new URL( '../plugin-src/client/channels/feishu/styles.js', import.meta.url, ); +const WEIXIN_STYLES_URL = new URL( + '../plugin-src/client/channels/weixin/styles.js', + import.meta.url, +); +const DINGTALK_STYLES_URL = new URL( + '../plugin-src/client/channels/dingtalk/styles.js', + import.meta.url, +); +const FEISHU_SOURCE_URL = new URL( + '../plugin-src/client/channels/feishu/index.js', + import.meta.url, +); +const WEIXIN_SOURCE_URL = new URL( + '../plugin-src/client/channels/weixin/index.js', + import.meta.url, +); const CLIENT_BUNDLE_URL = new URL('../lib/client.js', import.meta.url); const DINGTALK_CLIENT_SOURCE_URL = new URL( '../plugin-src/client/channels/dingtalk/index.js', @@ -86,26 +108,122 @@ test('Feishu bot cards place the application identifier under the bot name', asy assert.match(markup, /]*>今天是牢梁<\/h3>]*>cli_aaf4••••1234<\/p>/); assert.match(markup, /data-im-channel-logo="feishu"/); + assert.match(markup, /class="bxf-card bxf-botCard dim-botCard"/); + assert.match(markup, /class="bxf-healthPill dim-botHealth"/); + assert.match(markup, /]*aria-label="检查连接今天是牢梁"[^>]*>检查连接<\/span><\/button>/); + assert.match(markup, /class="bxf-connectedFooter dim-cardFooter"/); + assert.equal((markup.match(/dim-cardAction(?: |")/g) ?? []).length, 2); + assert.doesNotMatch(markup, /连接状态:|bxf-divider/); assert.doesNotMatch(markup, /custom-bot-avatar/); - assert.equal((markup.match(/class="bxf-metric"/g) ?? []).length, 2); + assert.equal((markup.match(/class="bxf-metric dim-botMetric"/g) ?? []).length, 2); assert.match(markup, />消息通道<[^]*>最近检查应用标识<|>飞书机器人 { +test('Feishu keeps its heading controls on one row without a plus icon', async () => { const styles = await readFile(FEISHU_STYLES_URL, 'utf8'); const markup = renderToStaticMarkup(React.createElement(FeishuSettingsTab, { rpcCall: async () => ({ ok: true, value: {} }), })); - assert.match(markup, /class="bxf-button bxf-bindButton"[^>]*>扫码绑定机器人<\/span><\/button>/); + assert.match(markup, /class="bxf-button bxf-bindButton dim-scanButton"[^>]*>扫码绑定机器人<\/span><\/button>/); assert.doesNotMatch(markup, />添加机器人 { + const [imStyles, feishuStyles, weixinStyles, dingtalkStyles, feishuSource, weixinSource, dingtalkSource] = await Promise.all([ + readFile(STYLES_URL, 'utf8'), + readFile(FEISHU_STYLES_URL, 'utf8'), + readFile(WEIXIN_STYLES_URL, 'utf8'), + readFile(DINGTALK_STYLES_URL, 'utf8'), + readFile(FEISHU_SOURCE_URL, 'utf8'), + readFile(WEIXIN_SOURCE_URL, 'utf8'), + readFile(DINGTALK_CLIENT_SOURCE_URL, 'utf8'), + ]); + + assert.match(imStyles, /\.dim-panel \.bxf-headingTools, \.dim-panel \.dxw-tools, \.dim-panel \.ddt-tools \{[^}]*justify-content: space-between;/); + assert.match(feishuStyles, /\.bxf-headingTools \{[^}]*justify-content: space-between;/); + assert.match(weixinStyles, /\.dxw-tools \{[^}]*justify-content: space-between;/); + assert.match(dingtalkStyles, /\.ddt-tools \{[^}]*justify-content: space-between;/); + + const headingSource = (source) => source.slice( + source.indexOf('function Heading'), + source.indexOf('function LoadingView'), + ); + const feishuHeading = headingSource(feishuSource); + const weixinHeading = headingSource(weixinSource); + const dingtalkHeading = headingSource(dingtalkSource); + assert.ok(feishuHeading.indexOf('扫码绑定机器人') < feishuHeading.indexOf('bxf-totalBadge')); + assert.ok(weixinHeading.indexOf('扫码绑定微信') < weixinHeading.indexOf('dxw-badge')); + assert.ok(dingtalkHeading.indexOf('扫码接入钉钉') < dingtalkHeading.indexOf('ddt-badge')); + + for (const heading of [feishuHeading, weixinHeading, dingtalkHeading]) { + assert.match(heading, /dim-scanButton/); + assert.match(heading, /dim-onlineBadge/); + } + assert.doesNotMatch(weixinHeading, /dxw-dot/); + assert.doesNotMatch(dingtalkHeading, /ddt-dot/); + assert.match(imStyles, /\.dim-panel \.bxf-headingTools \.dim-scanButton,[^}]*border: 1px solid #1677ff;[^}]*border-radius: 8px;[^}]*background: #1677ff;[^}]*box-shadow: none;/); + assert.match(imStyles, /\.dim-panel \.bxf-headingTools \.dim-onlineBadge,[^}]*border-radius: 999px;[^}]*background: var\(--dsw-alias-fill-secondary, #f2f3f5\);[^}]*font-size: 12px;/); +}); + +test('channel headings omit the redundant local credential badge', () => { + const components = [FeishuSettingsTab, WeixinSettingsTab, DingtalkSettingsTab]; + + for (const Component of components) { + const markup = renderToStaticMarkup(React.createElement(Component, { + rpcCall: async () => ({ ok: true, value: {} }), + })); + assert.doesNotMatch(markup, /凭据仅保存在本机/); + } +}); + +test('all channel settings states use the DingTalk page treatment', async () => { + const [styles, feishuSource, weixinSource, dingtalkSource] = await Promise.all([ + readFile(STYLES_URL, 'utf8'), + readFile(FEISHU_SOURCE_URL, 'utf8'), + readFile(WEIXIN_SOURCE_URL, 'utf8'), + readFile(DINGTALK_CLIENT_SOURCE_URL, 'utf8'), + ]); + + for (const Component of [FeishuSettingsTab, WeixinSettingsTab, DingtalkSettingsTab]) { + const markup = renderToStaticMarkup(React.createElement(Component, { + rpcCall: async () => ({ ok: true, value: {} }), + })); + assert.match(markup, /dim-channelPage/); + assert.match(markup, /dim-surfaceCard dim-loadingView/); + assert.match(markup, /dim-spinner/); + } + + for (const source of [feishuSource, weixinSource, dingtalkSource]) { + for (const className of [ + 'dim-channelPage', + 'dim-surfaceCard', + 'dim-loadingView', + 'dim-emptyView', + 'dim-qrLayout', + 'dim-inlineError', + 'dim-listHeading', + 'dim-confirm', + ]) { + assert.match(source, new RegExp(className)); + } + } + + assert.match(styles, /\.dim-panel \.dim-channelPage \{[^}]*flex-direction: column;[^}]*gap: 18px;/); + assert.match(styles, /\.dim-panel \.dim-surfaceCard \{[^}]*border-radius: 14px;[^}]*box-shadow: 0 1px 2px/); + assert.match(styles, /\.dim-panel \.dim-loadingView \{[^}]*padding: 38px;[^}]*text-align: center;/); + assert.match(styles, /\.dim-panel \.dim-emptyView \{[^}]*grid-template-columns: minmax\(0, 1fr\) 180px;[^}]*gap: 30px;/); + assert.match(styles, /\.dim-panel \.dim-qrLayout \{[^}]*grid-template-columns: 300px minmax\(0, 1fr\);[^}]*gap: 34px;[^}]*align-items: start;/); + assert.match(styles, /\.dim-panel \.dim-viewActions \.bxf-button,[^}]*min-height: 34px;[^}]*border-radius: 8px;[^}]*font-size: 13px;/); + assert.match(styles, /\.dim-panel \.dim-inlineError \{[^}]*padding: 22px;[^}]*background:/); + assert.match(styles, /\.dim-panel \.dim-confirm \{[^}]*padding: 18px 24px;[^}]*border-top: 1px solid/); +}); + test('bot cards reuse the same channel brand logos as the channel rail', () => { const railMarkup = renderToStaticMarkup(React.createElement(IMSettingsTab, { feishuRpcCall: async () => ({ ok: true, value: {} }), @@ -129,8 +247,12 @@ test('bot cards reuse the same channel brand logos as the channel rail', () => { assert.match(railMarkup, /data-im-channel-logo="weixin"/); assert.match(railMarkup, /data-im-channel-logo="feishu"/); - assert.match(accountMarkup, /class="dxw-avatar"[^]*data-im-channel-logo="weixin"/); - assert.equal((accountMarkup.match(/class="dxw-metric"/g) ?? []).length, 2); + assert.match(accountMarkup, /class="dxw-card dim-botCard"/); + assert.match(accountMarkup, /class="dxw-avatar dim-botAvatar"[^]*data-im-channel-logo="weixin"/); + assert.match(accountMarkup, /class="dxw-health dim-botHealth"/); + assert.match(accountMarkup, /class="dxw-accountFooter dim-cardFooter"/); + assert.equal((accountMarkup.match(/dim-cardAction(?: |")/g) ?? []).length, 2); + assert.equal((accountMarkup.match(/class="dxw-metric dim-botMetric"/g) ?? []).length, 2); assert.doesNotMatch(accountMarkup, /收到 \/ 回复/); }); @@ -150,11 +272,48 @@ test('DingTalk bot cards omit the redundant received and replied metric', () => onCancelRemove() {}, })); - assert.equal((markup.match(/class="ddt-metric"/g) ?? []).length, 2); + assert.match(markup, /class="ddt-card dim-botCard"/); + assert.match(markup, /class="ddt-health dim-botHealth"/); + assert.equal((markup.match(/class="ddt-metric dim-botMetric"/g) ?? []).length, 2); + assert.match(markup, /class="ddt-accountFooter dim-cardFooter"/); + assert.equal((markup.match(/dim-cardAction(?: |")/g) ?? []).length, 2); assert.match(markup, />消息通道<[^]*>最近检查 { + const [imStyles, feishuStyles, weixinStyles, dingtalkStyles] = await Promise.all([ + readFile(STYLES_URL, 'utf8'), + readFile(FEISHU_STYLES_URL, 'utf8'), + readFile(WEIXIN_STYLES_URL, 'utf8'), + readFile(DINGTALK_STYLES_URL, 'utf8'), + ]); + + assert.match(feishuStyles, /\.bxf-botActions \{[^}]*flex-wrap: nowrap;/); + assert.match(weixinStyles, /\.dxw-accountFooter \.dxw-actions \{[^}]*flex-wrap: nowrap;/); + assert.match(dingtalkStyles, /\.ddt-accountFooter \.ddt-actions \{[^}]*flex-wrap: nowrap;/); + assert.match(imStyles, /\.dim-panel \.dim-cardFooter \{[^}]*gap: 15px;[^}]*padding-top: 16px;[^}]*border-top: 1px solid/); + assert.match(imStyles, /\.dim-panel \.dim-cardActions \.dim-cardAction \{[^}]*min-height: 34px;[^}]*border-radius: 8px;[^}]*font-size: 13px;/); + assert.match(imStyles, /\.dim-panel \.dim-cardActions \.dim-cardAction\[data-kind="danger"\] \{[^}]*#d54941/); + assert.doesNotMatch(feishuStyles, /\.bxf-connectedFooter \{[^}]*flex-direction: column/); + assert.doesNotMatch(weixinStyles, /\.dxw-accountFooter \{[^}]*flex-direction: column/); + assert.doesNotMatch(dingtalkStyles, /\.ddt-accountFooter \{[^}]*flex-direction: column/); +}); + +test('all channel bot cards use the DingTalk card treatment', async () => { + const styles = await readFile(STYLES_URL, 'utf8'); + + assert.match(styles, /\.dim-panel \.dim-botCard \{[^}]*border-radius: 14px;[^}]*background: var\(--dsw-alias-bg-body, #fff\);[^}]*box-shadow: 0 1px 2px/); + assert.match(styles, /\.dim-panel \.dim-botCardBody \{[^}]*padding: 24px;/); + assert.match(styles, /\.dim-panel \.dim-botCardTop \{[^}]*align-items: flex-start;[^}]*gap: 16px;/); + assert.match(styles, /\.dim-panel \.dim-botAvatar \{[^}]*width: 42px;[^}]*height: 42px;[^}]*border-radius: 12px;/); + assert.match(styles, /\.dim-panel \.dim-botName h3 \{[^}]*font-size: 15px;/); + assert.match(styles, /\.dim-panel \.dim-botCard \.dim-botHealth \{[^}]*background: transparent;[^}]*font-size: 12px;[^}]*font-weight: 400;/); + assert.match(styles, /\.dim-panel \.dim-botMetrics \{[^}]*grid-template-columns: repeat\(2, minmax\(0, 1fr\)\);[^}]*gap: 10px;[^}]*margin: 20px 0;/); + assert.match(styles, /\.dim-panel \.dim-botMetric \{[^}]*padding: 12px;[^}]*border: 0;[^}]*border-radius: 9px;/); + assert.match(styles, /\.dim-panel \.dim-botMetric dd \{[^}]*margin: 5px 0 0;[^}]*font-size: 13px;[^}]*font-weight: 400;/); +}); + test('the bundled DingTalk channel has no local sender approval workflow', async () => { const [source, bundle] = await Promise.all([ readFile(DINGTALK_CLIENT_SOURCE_URL, 'utf8'),