dsh-task-ledger 0.0.47 → 0.0.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.js +211 -143
- package/dist/client.js.map +1 -1
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -184,7 +184,25 @@ window.__ModuleLoader__.load({
|
|
|
184
184
|
if (identity === null || identity === "") return false;
|
|
185
185
|
return task.owner === identity && (task.status === "doing" || task.status === "blocked");
|
|
186
186
|
}
|
|
187
|
-
|
|
187
|
+
function resolveActions(task, board) {
|
|
188
|
+
const mine = isMine(task, board);
|
|
189
|
+
return {
|
|
190
|
+
view: true,
|
|
191
|
+
claim: !mine && task.status === "open",
|
|
192
|
+
resume: mine,
|
|
193
|
+
finish: mine,
|
|
194
|
+
release: mine
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/** 已结案行(统计 tab):只有查看,没有在办动作。 */
|
|
198
|
+
const CLOSED_ACTIONS = {
|
|
199
|
+
view: true,
|
|
200
|
+
claim: false,
|
|
201
|
+
resume: false,
|
|
202
|
+
finish: false,
|
|
203
|
+
release: false
|
|
204
|
+
};
|
|
205
|
+
/** 状态档命中(停滞由 host 标记,前端不判阈值)——只作用于第二区。 */
|
|
188
206
|
function statusHit(task, status) {
|
|
189
207
|
if (status === "all") return true;
|
|
190
208
|
if (status === "active") return task.status === "doing" || task.status === "blocked";
|
|
@@ -252,7 +270,7 @@ window.__ModuleLoader__.load({
|
|
|
252
270
|
kind: "task",
|
|
253
271
|
key: `${task.workspace.id}:${task.id}`,
|
|
254
272
|
task,
|
|
255
|
-
mine:
|
|
273
|
+
mine: isMine(task, board)
|
|
256
274
|
});
|
|
257
275
|
byWorkspace.set(task.workspace.id, entry);
|
|
258
276
|
}
|
|
@@ -295,123 +313,6 @@ window.__ModuleLoader__.load({
|
|
|
295
313
|
return opts?.seconds === true ? `${base}:${pad2(d.getSeconds())}` : base;
|
|
296
314
|
}
|
|
297
315
|
//#endregion
|
|
298
|
-
//#region src/client/task-row.ts
|
|
299
|
-
/**
|
|
300
|
-
* 任务行(宿主**无表格件** ⇒ 自绘),**两种形态**(用户 2026-10-17 定稿:两区视觉必须不同,
|
|
301
|
-
* 且**互不重用**):
|
|
302
|
-
*
|
|
303
|
-
* A. `mine`(我的待办)= 整行一张卡,样式照抄 dispatch-table 任务配置卡:
|
|
304
|
-
* 左缘 6×36 状态条 + 标题行 + 第二行工作区/标签;右侧灰字时长;悬停出
|
|
305
|
-
* 「继续 / 结束 / 释放」;最右常驻「查看」放大镜钮。
|
|
306
|
-
*
|
|
307
|
-
* B. `others`(其他待办)= 连体长表里的一行(用户 2026-10-18 定稿):**无卡片、无外框**,
|
|
308
|
-
* 行 = 一条淡分隔线格:上下居中的状态圆点 + 任务(标题行 + 标签行;标签行最前是执行人
|
|
309
|
-
* `@名字`,未认领则不摆)+ 状态列(阻塞/停滞 pill,待认领/进行中文字;「待认领」只出现一次)
|
|
310
|
-
* + 最右「@创建人 创建于 年-月-日 时:分:秒」(悬停掉时分秒并让位给操作钮:没人在做 ⇒ 认领)
|
|
311
|
-
* + 查看。圆点颜色:灰=待认领 / 浅蓝=进行中(比「我的」蓝灰一档)/ 黄=阻塞 / 红=停滞;
|
|
312
|
-
* 停滞 pill 悬停给**配置出来的**阈值解释(staleDays 由 host 下发)。
|
|
313
|
-
*
|
|
314
|
-
* 按钮点击行为(含查看)归「任务按钮与会话交接」任务,本文件只做样子与提示。
|
|
315
|
-
*/
|
|
316
|
-
/** 行标题:在办 = 任务名(缺则任务号);已结案 = 任务包名(缺则「完成了什么」首句)。 */
|
|
317
|
-
function titleOf(row) {
|
|
318
|
-
if (row.kind === "task") return row.task.title ?? row.task.id;
|
|
319
|
-
const title = row.record.workpackage ?? row.record.summary;
|
|
320
|
-
return title !== void 0 && title !== "" ? title : row.record.src ?? "—";
|
|
321
|
-
}
|
|
322
|
-
/** 状态语义(异常优先:stale 盖过 blocked)。 */
|
|
323
|
-
function toneOf(row) {
|
|
324
|
-
if (row.kind !== "task") return "closed";
|
|
325
|
-
if (row.task.stale === true) return "stale";
|
|
326
|
-
if (row.task.status === "blocked") return "blocked";
|
|
327
|
-
if (row.task.status === "open") return "open";
|
|
328
|
-
return "doing";
|
|
329
|
-
}
|
|
330
|
-
function TaskRowView(props) {
|
|
331
|
-
const { t, row, variant, staleDays } = props;
|
|
332
|
-
const tone = toneOf(row);
|
|
333
|
-
const isTask = row.kind === "task";
|
|
334
|
-
const tags = (row.kind === "task" ? row.task.tags : row.record.tags) ?? [];
|
|
335
|
-
const created = row.kind === "task" ? row.task.created ?? null : row.record.created ?? null;
|
|
336
|
-
const createdDay = created !== null && created !== void 0 && Date.parse(created) >= 0 ? formatDateTime(created).slice(0, 10) : null;
|
|
337
|
-
const staleTip = staleDays === null || staleDays === void 0 ? void 0 : t("statStaleTitle", { n: staleDays });
|
|
338
|
-
const viewButton = (0, react.createElement)("button", {
|
|
339
|
-
type: "button",
|
|
340
|
-
className: "dtl-viewbtn",
|
|
341
|
-
"data-tip": t("viewDetail"),
|
|
342
|
-
title: t("viewDetail")
|
|
343
|
-
}, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutlineRegular, { size: 14 }));
|
|
344
|
-
const blockedPill = tone === "blocked" ? (0, react.createElement)("span", { className: "dtl-pill dtl-pill--blocked" }, t("statusBlocked")) : null;
|
|
345
|
-
const stalePill = tone === "stale" ? (0, react.createElement)("span", {
|
|
346
|
-
className: "dtl-pill dtl-pill--stale",
|
|
347
|
-
title: staleTip
|
|
348
|
-
}, t("statusStale")) : null;
|
|
349
|
-
const titleLine = (0, react.createElement)("div", { className: "dtl-taskrow__l1" }, (0, react.createElement)("span", {
|
|
350
|
-
className: "dtl-taskrow__title dtl-ellipsis",
|
|
351
|
-
title: titleOf(row)
|
|
352
|
-
}, titleOf(row)), createdDay === null ? null : (0, react.createElement)("span", { className: "dtl-taskrow__stamp" }, `[${t("fieldCreatedStamp", { day: createdDay })}]`), blockedPill, stalePill);
|
|
353
|
-
const orowTitleLine = (0, react.createElement)("div", { className: "dtl-orow__title" }, (0, react.createElement)("span", {
|
|
354
|
-
className: "dtl-orow__name dtl-ellipsis",
|
|
355
|
-
title: titleOf(row)
|
|
356
|
-
}, titleOf(row)));
|
|
357
|
-
if (variant === "others") {
|
|
358
|
-
const creator = isTask ? row.task.creator : row.record.owner;
|
|
359
|
-
const owner = isTask ? row.task.owner : null;
|
|
360
|
-
const hasOwner = !(owner === null || owner === void 0 || owner === "");
|
|
361
|
-
const who = creator === null || creator === void 0 || creator === "" ? "—" : `@${creator}`;
|
|
362
|
-
const createdOk = created !== null && created !== void 0 && Date.parse(created) >= 0;
|
|
363
|
-
const ownerChip = hasOwner ? (0, react.createElement)("span", {
|
|
364
|
-
className: "dtl-orow__owner",
|
|
365
|
-
title: t("fieldOwner")
|
|
366
|
-
}, `@${String(owner)}`) : null;
|
|
367
|
-
const tagLine = hasOwner || tags.length > 0 ? (0, react.createElement)("div", { className: "dtl-orow__tags" }, ownerChip, tags.map((tag) => (0, react.createElement)("span", {
|
|
368
|
-
key: tag,
|
|
369
|
-
className: "dtl-minitag",
|
|
370
|
-
title: `#${tag}`
|
|
371
|
-
}, `#${tag}`))) : null;
|
|
372
|
-
return (0, react.createElement)("div", { className: "dtl-orow" }, (0, react.createElement)("div", { className: "dtl-orow__row" }, (0, react.createElement)("span", { className: `dtl-dot dtl-dot--${tone}` }), (0, react.createElement)("div", { className: "dtl-tcell dtl-tcell--title" }, orowTitleLine, tagLine), (0, react.createElement)("span", { className: "dtl-tcell dtl-tcell--status" }, tone === "blocked" ? blockedPill : tone === "stale" ? stalePill : null, tone === "doing" || tone === "open" ? (0, react.createElement)("span", { className: `dtl-statustext dtl-statustext--${tone}` }, tone === "open" ? t("statusOpen") : t("statusActive")) : null), (0, react.createElement)("span", { className: "dtl-tcell dtl-tcell--created" }, (0, react.createElement)("span", { className: "dtl-orow__created-long" }, t("createdAt", {
|
|
373
|
-
who,
|
|
374
|
-
when: createdOk ? formatDateTime(String(created), { seconds: true }) : t("timeUnknown")
|
|
375
|
-
})), (0, react.createElement)("span", { className: "dtl-orow__created-short" }, t("createdAt", {
|
|
376
|
-
who,
|
|
377
|
-
when: createdOk ? formatDateTime(String(created)) : t("timeUnknown")
|
|
378
|
-
}))), (0, react.createElement)("span", { className: "dtl-orow__actions" }, hasOwner ? null : (0, react.createElement)("button", {
|
|
379
|
-
type: "button",
|
|
380
|
-
className: "dtl-minibtn",
|
|
381
|
-
"data-tip": t("tipClaim")
|
|
382
|
-
}, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutlineRegular, { size: 14 }), t("actionClaim")), viewButton)));
|
|
383
|
-
}
|
|
384
|
-
const parts = [];
|
|
385
|
-
if (isTask) {
|
|
386
|
-
const startRaw = row.task.started ?? row.task.created;
|
|
387
|
-
if (startRaw !== void 0 && startRaw !== null && startRaw !== "" && Date.parse(startRaw) >= 0) parts.push(`${formatDateTime(startRaw).slice(0, 10)} ${t("fieldStarted")}`);
|
|
388
|
-
if (row.task.ageDays !== null && row.task.ageDays !== void 0) parts.push(row.task.ageDays === 0 ? t("ageToday") : t("ageDaysText", { n: row.task.ageDays }));
|
|
389
|
-
} else parts.push(`${t("fieldFinished")} ${formatDateTime(row.record.time ?? row.record.created ?? "", { fallback: t("timeUnknown") })}`);
|
|
390
|
-
return (0, react.createElement)("div", { className: "dtl-taskrow" }, (0, react.createElement)("div", { className: "dtl-taskrow__row" }, (0, react.createElement)("span", { className: `dtl-rail dtl-rail--${tone}` }), (0, react.createElement)("div", { style: {
|
|
391
|
-
flex: "1 1 auto",
|
|
392
|
-
minWidth: 0
|
|
393
|
-
} }, titleLine, (0, react.createElement)("div", { className: "dtl-taskrow__l2" }, (0, react.createElement)("span", {
|
|
394
|
-
className: "dtl-wstag dtl-ellipsis",
|
|
395
|
-
title: row.kind === "task" ? row.task.workspace.title : ""
|
|
396
|
-
}, row.kind === "task" ? row.task.workspace.title : ""), tags.map((tag) => (0, react.createElement)("span", {
|
|
397
|
-
key: tag,
|
|
398
|
-
className: "dtl-minitag",
|
|
399
|
-
title: `#${tag}`
|
|
400
|
-
}, `#${tag}`)))), (0, react.createElement)("span", { className: "dtl-taskrow__when" }, parts.length === 0 ? t("timeUnknown") : parts.join("丨")), (0, react.createElement)("span", { className: "dtl-taskrow__actions" }, isTask ? (0, react.createElement)("button", {
|
|
401
|
-
type: "button",
|
|
402
|
-
className: "dtl-minibtn",
|
|
403
|
-
"data-tip": t("tipResume")
|
|
404
|
-
}, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, { size: 14 }), t("actionResume")) : null, isTask ? (0, react.createElement)("button", {
|
|
405
|
-
type: "button",
|
|
406
|
-
className: "dtl-minibtn",
|
|
407
|
-
"data-tip": t("tipFinish")
|
|
408
|
-
}, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, { size: 14 }), t("actionFinish")) : null, isTask ? (0, react.createElement)("button", {
|
|
409
|
-
type: "button",
|
|
410
|
-
className: "dtl-minibtn",
|
|
411
|
-
"data-tip": t("tipRelease")
|
|
412
|
-
}, t("actionRelease")) : null, viewButton)));
|
|
413
|
-
}
|
|
414
|
-
//#endregion
|
|
415
316
|
//#region src/client/tokens.ts
|
|
416
317
|
/**
|
|
417
318
|
* 语义 token 层:`--dtl-*`(dtl = dsh-task-ledger)。
|
|
@@ -736,15 +637,16 @@ body[data-ds-dark-theme]{
|
|
|
736
637
|
.dtl-orow__row{display:flex;align-items:center;gap:12px;}
|
|
737
638
|
.dtl-orow__title{display:flex;align-items:baseline;gap:6px;min-width:0;}
|
|
738
639
|
.dtl-orow__name{font-size:var(--dtl-font-md);font-weight:500;line-height:var(--dtl-line-md);min-width:0;}
|
|
739
|
-
/*
|
|
640
|
+
/* 第二行:状态标签 + #标签(与标题行间距拉开,用户 2026-10-18;状态位与执行人**互换**后,
|
|
641
|
+
状态搬到这里、且永远有 ⇒ 这一行不再有「空一排」的情况) */
|
|
740
642
|
.dtl-orow__tags{display:flex;align-items:center;gap:var(--dtl-space-2);margin-top:7px;min-width:0;}
|
|
741
|
-
/*
|
|
742
|
-
.dtl-orow__owner{flex:none;padding:0 6px;border-radius:999px;background:var(--dtl-open-bg-soft);
|
|
743
|
-
color:var(--dtl-fg-2);font-size:10px;line-height:16px;white-space:nowrap;}
|
|
744
|
-
/* 列:任务(弹性)/ 状态 / 创建信息 / 操作 / 查看 */
|
|
643
|
+
/* 列:任务(弹性)/ 执行人 / 创建信息 / 操作 / 查看 */
|
|
745
644
|
.dtl-tcell{min-width:0;}
|
|
746
645
|
.dtl-tcell--title{flex:1 1 auto;}
|
|
747
|
-
|
|
646
|
+
/* 执行人列(原状态列,用户 2026-10-18 互换):标签最宽 20ch ⇒ 列宽按它给,标签再自己收到 100%
|
|
647
|
+
(名字过长时由标签自己的省略号兜住,不许溢出压到创建信息列) */
|
|
648
|
+
.dtl-tcell--status{flex:none;width:110px;}
|
|
649
|
+
.dtl-tcell--status .dtl-tag{max-width:100%;}
|
|
748
650
|
.dtl-tcell--created{flex:none;display:flex;align-items:center;justify-content:flex-end;
|
|
749
651
|
font-size:var(--dtl-font-xs);color:var(--dtl-fg-3);white-space:nowrap;font-variant-numeric:tabular-nums;}
|
|
750
652
|
/* 悬停:时分秒让位给操作钮(与「我的待办」的 时间⇄按钮 同款互换) */
|
|
@@ -753,11 +655,6 @@ body[data-ds-dark-theme]{
|
|
|
753
655
|
.dtl-orow:hover .dtl-orow__created-short{display:inline;}
|
|
754
656
|
.dtl-orow__actions{display:none;flex:none;gap:6px;}
|
|
755
657
|
.dtl-orow:hover .dtl-orow__actions{display:inline-flex;}
|
|
756
|
-
/* 状态列文字(待认领灰 / 进行中浅蓝;阻塞停滞仍是 pill) */
|
|
757
|
-
.dtl-statustext{font-size:var(--dtl-font-xs);white-space:nowrap;}
|
|
758
|
-
.dtl-statustext--doing{color:var(--dtl-business-dim);}
|
|
759
|
-
.dtl-statustext--open{color:var(--dtl-fg-3);}
|
|
760
|
-
|
|
761
658
|
/* 行首状态圆点:灰=待认领 / 浅蓝=进行中(比「我的」蓝灰一档)/ 黄=阻塞 / 红=停滞;
|
|
762
659
|
6px(用户 2026-10-18:8px 太大) */
|
|
763
660
|
.dtl-dot{display:inline-block;width:6px;height:6px;flex:none;border-radius:50%;}
|
|
@@ -810,15 +707,25 @@ body[data-ds-dark-theme]{
|
|
|
810
707
|
.dtl-taskrow__row:hover .dtl-taskrow__when{display:none;}
|
|
811
708
|
.dtl-taskrow__row:hover .dtl-taskrow__actions{display:inline-flex;}
|
|
812
709
|
|
|
813
|
-
/*
|
|
814
|
-
|
|
815
|
-
.dtl-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
/*
|
|
819
|
-
.dtl-
|
|
820
|
-
|
|
821
|
-
|
|
710
|
+
/* 标签(用户 2026-10-18):状态 / 执行人 / #标签 **共用一枚件**,只换色调;
|
|
711
|
+
最大宽度 20ch(≈10 个中文或 20 个字符),超出省略号、悬停跑马灯把全文滚出来。 */
|
|
712
|
+
.dtl-tag{display:inline-block;flex:none;max-width:20ch;padding:0 6px;border-radius:999px;
|
|
713
|
+
background:var(--dtl-chip-bg);color:var(--dtl-fg-3);font-size:10px;line-height:16px;
|
|
714
|
+
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;vertical-align:middle;}
|
|
715
|
+
/* 状态四色:待认领灰 / 进行中蓝 / 阻塞黄 / 停滞红(与行首圆点同色系) */
|
|
716
|
+
.dtl-tag--open{color:var(--dtl-fg-2);font-weight:600;}
|
|
717
|
+
.dtl-tag--doing{background:var(--dtl-business-soft);color:var(--dtl-business);font-weight:600;}
|
|
718
|
+
.dtl-tag--blocked{background:var(--dtl-warning-soft);color:var(--dtl-warning);font-weight:600;}
|
|
719
|
+
.dtl-tag--stale{background:var(--dtl-danger-soft);color:var(--dtl-danger);font-weight:600;}
|
|
720
|
+
/* 执行人:浅蓝底(与状态色区分:状态是「事怎么样」,人是「谁在做」) */
|
|
721
|
+
.dtl-tag--person{background:var(--dtl-open-bg-soft);color:var(--dtl-fg-2);}
|
|
722
|
+
/* 内层撑满真实宽度(inline-block ⇒ transform 才生效),外层负责裁切与省略号 */
|
|
723
|
+
.dtl-tag__text{display:inline-block;}
|
|
724
|
+
/* 跑马灯只挂在**真溢出**的标签上(短的挂上去会白飘一段) */
|
|
725
|
+
.dtl-tag--long:hover{text-overflow:clip;}
|
|
726
|
+
.dtl-tag--long:hover .dtl-tag__text{animation:dtl-marquee 6s linear infinite;}
|
|
727
|
+
@keyframes dtl-marquee{0%,12%{transform:translateX(0);}
|
|
728
|
+
88%,100%{transform:translateX(calc(-100% + 20ch - 12px));}}
|
|
822
729
|
|
|
823
730
|
/* 分区标题里的计数:与日期徽同款中括号小字(用户 2026-10-17);0 条不渲染 */
|
|
824
731
|
.dtl-count{font-size:var(--dtl-font-xs);color:var(--dtl-fg-3);font-variant-numeric:tabular-nums;}
|
|
@@ -1011,6 +918,27 @@ body[data-ds-dark-theme]{
|
|
|
1011
918
|
color: "var(--dtl-fg-2)"
|
|
1012
919
|
} }, props.label), (0, react.createElement)("span", { className: "dtl-stat__value" }, shown), props.hint === void 0 || props.hint === null || props.hint === "" ? null : (0, react.createElement)("span", { className: "dtl-stat__hint" }, props.hint));
|
|
1013
920
|
}
|
|
921
|
+
/** 标签上限宽度(字符数):1 个中文按 2 计 ⇒ 10 个中文 = 20 个字符(与 CSS 的 20ch 对齐)。 */
|
|
922
|
+
const TAG_MAX_WIDTH = 20;
|
|
923
|
+
/** 显示宽度:全角(中 / 日 / 韩 / 全角符号)一个顶两个英文字符。 */
|
|
924
|
+
function displayWidth(text) {
|
|
925
|
+
let width = 0;
|
|
926
|
+
for (const char of text) width += (char.codePointAt(0) ?? 0) > 11904 ? 2 : 1;
|
|
927
|
+
return width;
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* 标签(**唯一**一枚件,用户 2026-10-18):状态 / 执行人 / #标签 都走它,只换 tone。
|
|
931
|
+
* 超宽 ⇒ 省略号;悬停 ⇒ 跑马灯滚出全文(溢出时不挂 title,免得原生提示盖住滚动)。
|
|
932
|
+
*/
|
|
933
|
+
function Tag(props) {
|
|
934
|
+
ensureUiBase();
|
|
935
|
+
const { text, tone = "neutral", title } = props;
|
|
936
|
+
const long = displayWidth(text) > TAG_MAX_WIDTH;
|
|
937
|
+
return (0, react.createElement)("span", {
|
|
938
|
+
className: `dtl-tag dtl-tag--${tone}${long ? " dtl-tag--long" : ""}`,
|
|
939
|
+
title: long ? void 0 : title ?? text
|
|
940
|
+
}, (0, react.createElement)("span", { className: "dtl-tag__text" }, text));
|
|
941
|
+
}
|
|
1014
942
|
/** 空态:`guide` = 真空引导卡(虚线),`filtered` = 筛选空小字。 */
|
|
1015
943
|
function EmptyState(props) {
|
|
1016
944
|
ensureUiBase();
|
|
@@ -1138,6 +1066,143 @@ body[data-ds-dark-theme]{
|
|
|
1138
1066
|
return (0, react.createElement)("div", { className: "dtl-gchart-legend" }, (0, react.createElement)("span", null, (0, react.createElement)("i", { style: { background: "var(--dtl-business)" } }), props.doneLabel), (0, react.createElement)("span", null, (0, react.createElement)("i", { style: { background: "var(--dtl-chart-remaining)" } }), props.remainingLabel), (0, react.createElement)("span", null, (0, react.createElement)("i", { style: { background: "var(--dtl-chart-created)" } }), props.createdLabel));
|
|
1139
1067
|
}
|
|
1140
1068
|
//#endregion
|
|
1069
|
+
//#region src/client/task-row.ts
|
|
1070
|
+
/**
|
|
1071
|
+
* 任务行(宿主**无表格件** ⇒ 自绘),**两种形态**(用户 2026-10-17 定稿:两区视觉必须不同,
|
|
1072
|
+
* 且**互不重用**):
|
|
1073
|
+
*
|
|
1074
|
+
* A. `mine`(我的待办)= 整行一张卡,样式照抄 dispatch-table 任务配置卡:
|
|
1075
|
+
* 左缘 6×36 状态条 + 标题行 + 第二行工作区/标签;右侧灰字时长;悬停出
|
|
1076
|
+
* 「继续 / 结束 / 释放」;最右常驻「查看」放大镜钮。
|
|
1077
|
+
*
|
|
1078
|
+
* B. `others`(全部待办)= 连体长表里的一行(用户 2026-10-18 定稿):**无卡片、无外框**,
|
|
1079
|
+
* 行 = 一条淡分隔线格:上下居中的状态圆点 + 任务(标题行 + 标签行;**标签行最前是状态标签**)
|
|
1080
|
+
* + **执行人列**(原状态位,有执行人才摆 `@名字` 标签)+ 最右「@创建人 创建于 年-月-日 时:分:秒」
|
|
1081
|
+
* (悬停掉时分秒并让位给操作钮)+ 查看。
|
|
1082
|
+
* 状态一律**标签型、四色**(用户 2026-10-18:原来是一排素字 / pill,现在与人员**位置互换**):
|
|
1083
|
+
* 灰=待认领 / 蓝=进行中 / 黄=阻塞 / 红=停滞;停滞标签悬停给**配置出来的**阈值解释
|
|
1084
|
+
* (staleDays 由 host 下发)。圆点颜色同色系。
|
|
1085
|
+
*
|
|
1086
|
+
* 按钮(含查看)**不各判各的**:权限一律问 `resolveActions()`(rows.ts),本文件只按返回值画。
|
|
1087
|
+
*
|
|
1088
|
+
* 按钮点击行为(含查看)归「任务按钮与会话交接」任务,本文件只做样子与提示。
|
|
1089
|
+
*/
|
|
1090
|
+
/** 行标题:在办 = 任务名(缺则任务号);已结案 = 任务包名(缺则「完成了什么」首句)。 */
|
|
1091
|
+
function titleOf(row) {
|
|
1092
|
+
if (row.kind === "task") return row.task.title ?? row.task.id;
|
|
1093
|
+
const title = row.record.workpackage ?? row.record.summary;
|
|
1094
|
+
return title !== void 0 && title !== "" ? title : row.record.src ?? "—";
|
|
1095
|
+
}
|
|
1096
|
+
/** 状态语义(异常优先:stale 盖过 blocked)。 */
|
|
1097
|
+
function toneOf(row) {
|
|
1098
|
+
if (row.kind !== "task") return "closed";
|
|
1099
|
+
if (row.task.stale === true) return "stale";
|
|
1100
|
+
if (row.task.status === "blocked") return "blocked";
|
|
1101
|
+
if (row.task.status === "open") return "open";
|
|
1102
|
+
return "doing";
|
|
1103
|
+
}
|
|
1104
|
+
/** 状态文案(已结案没有在办状态 ⇒ 不给标签)。 */
|
|
1105
|
+
function statusLabel(tone, t) {
|
|
1106
|
+
if (tone === "stale") return t("statusStale");
|
|
1107
|
+
if (tone === "blocked") return t("statusBlocked");
|
|
1108
|
+
if (tone === "open") return t("statusOpen");
|
|
1109
|
+
if (tone === "doing") return t("statusActive");
|
|
1110
|
+
return null;
|
|
1111
|
+
}
|
|
1112
|
+
function TaskRowView(props) {
|
|
1113
|
+
const { t, row, variant, staleDays, board } = props;
|
|
1114
|
+
const tone = toneOf(row);
|
|
1115
|
+
const isTask = row.kind === "task";
|
|
1116
|
+
const tags = (row.kind === "task" ? row.task.tags : row.record.tags) ?? [];
|
|
1117
|
+
const created = row.kind === "task" ? row.task.created ?? null : row.record.created ?? null;
|
|
1118
|
+
const createdDay = created !== null && created !== void 0 && Date.parse(created) >= 0 ? formatDateTime(created).slice(0, 10) : null;
|
|
1119
|
+
const staleTip = staleDays === null || staleDays === void 0 ? void 0 : t("statStaleTitle", { n: staleDays });
|
|
1120
|
+
const actions = isTask ? resolveActions(row.task, board) : CLOSED_ACTIONS;
|
|
1121
|
+
const viewButton = (0, react.createElement)("button", {
|
|
1122
|
+
type: "button",
|
|
1123
|
+
className: "dtl-viewbtn",
|
|
1124
|
+
"data-tip": t("viewDetail"),
|
|
1125
|
+
title: t("viewDetail")
|
|
1126
|
+
}, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutlineRegular, { size: 14 }));
|
|
1127
|
+
const claimButton = (0, react.createElement)("button", {
|
|
1128
|
+
type: "button",
|
|
1129
|
+
className: "dtl-minibtn",
|
|
1130
|
+
"data-tip": t("tipClaim")
|
|
1131
|
+
}, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutlineRegular, { size: 14 }), t("actionClaim"));
|
|
1132
|
+
const resumeButton = (0, react.createElement)("button", {
|
|
1133
|
+
type: "button",
|
|
1134
|
+
className: "dtl-minibtn",
|
|
1135
|
+
"data-tip": t("tipResume")
|
|
1136
|
+
}, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, { size: 14 }), t("actionResume"));
|
|
1137
|
+
const finishButton = (0, react.createElement)("button", {
|
|
1138
|
+
type: "button",
|
|
1139
|
+
className: "dtl-minibtn",
|
|
1140
|
+
"data-tip": t("tipFinish")
|
|
1141
|
+
}, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, { size: 14 }), t("actionFinish"));
|
|
1142
|
+
const releaseButton = (0, react.createElement)("button", {
|
|
1143
|
+
type: "button",
|
|
1144
|
+
className: "dtl-minibtn",
|
|
1145
|
+
"data-tip": t("tipRelease")
|
|
1146
|
+
}, t("actionRelease"));
|
|
1147
|
+
const statusText = statusLabel(tone, t);
|
|
1148
|
+
const statusTag = statusText === null || tone === "closed" ? null : (0, react.createElement)(Tag, {
|
|
1149
|
+
text: statusText,
|
|
1150
|
+
tone,
|
|
1151
|
+
title: tone === "stale" ? staleTip ?? statusText : statusText
|
|
1152
|
+
});
|
|
1153
|
+
const alarmTag = tone === "blocked" || tone === "stale" ? (0, react.createElement)(Tag, {
|
|
1154
|
+
text: statusLabel(tone, t) ?? "",
|
|
1155
|
+
tone,
|
|
1156
|
+
title: tone === "stale" ? staleTip : void 0
|
|
1157
|
+
}) : null;
|
|
1158
|
+
const titleLine = (0, react.createElement)("div", { className: "dtl-taskrow__l1" }, (0, react.createElement)("span", {
|
|
1159
|
+
className: "dtl-taskrow__title dtl-ellipsis",
|
|
1160
|
+
title: titleOf(row)
|
|
1161
|
+
}, titleOf(row)), createdDay === null ? null : (0, react.createElement)("span", { className: "dtl-taskrow__stamp" }, `[${t("fieldCreatedStamp", { day: createdDay })}]`), alarmTag);
|
|
1162
|
+
const orowTitleLine = (0, react.createElement)("div", { className: "dtl-orow__title" }, (0, react.createElement)("span", {
|
|
1163
|
+
className: "dtl-orow__name dtl-ellipsis",
|
|
1164
|
+
title: titleOf(row)
|
|
1165
|
+
}, titleOf(row)));
|
|
1166
|
+
if (variant === "others") {
|
|
1167
|
+
const creator = isTask ? row.task.creator : row.record.owner;
|
|
1168
|
+
const owner = isTask ? row.task.owner : null;
|
|
1169
|
+
const hasOwner = !(owner === null || owner === void 0 || owner === "");
|
|
1170
|
+
const who = creator === null || creator === void 0 || creator === "" ? "—" : `@${creator}`;
|
|
1171
|
+
const createdOk = created !== null && created !== void 0 && Date.parse(created) >= 0;
|
|
1172
|
+
const tagLine = (0, react.createElement)("div", { className: "dtl-orow__tags" }, statusTag, tags.map((tag) => (0, react.createElement)(Tag, {
|
|
1173
|
+
key: tag,
|
|
1174
|
+
text: `#${tag}`
|
|
1175
|
+
})));
|
|
1176
|
+
return (0, react.createElement)("div", { className: "dtl-orow" }, (0, react.createElement)("div", { className: "dtl-orow__row" }, (0, react.createElement)("span", { className: `dtl-dot dtl-dot--${tone}` }), (0, react.createElement)("div", { className: "dtl-tcell dtl-tcell--title" }, orowTitleLine, tagLine), (0, react.createElement)("span", { className: "dtl-tcell dtl-tcell--status" }, hasOwner ? (0, react.createElement)(Tag, {
|
|
1177
|
+
text: `@${String(owner)}`,
|
|
1178
|
+
tone: "person",
|
|
1179
|
+
title: t("fieldOwner")
|
|
1180
|
+
}) : null), (0, react.createElement)("span", { className: "dtl-tcell dtl-tcell--created" }, (0, react.createElement)("span", { className: "dtl-orow__created-long" }, t("createdAt", {
|
|
1181
|
+
who,
|
|
1182
|
+
when: createdOk ? formatDateTime(String(created), { seconds: true }) : t("timeUnknown")
|
|
1183
|
+
})), (0, react.createElement)("span", { className: "dtl-orow__created-short" }, t("createdAt", {
|
|
1184
|
+
who,
|
|
1185
|
+
when: createdOk ? formatDateTime(String(created)) : t("timeUnknown")
|
|
1186
|
+
}))), (0, react.createElement)("span", { className: "dtl-orow__actions" }, actions.claim ? claimButton : null, actions.resume ? resumeButton : null, actions.finish ? finishButton : null, actions.release ? releaseButton : null, actions.view ? viewButton : null)));
|
|
1187
|
+
}
|
|
1188
|
+
const parts = [];
|
|
1189
|
+
if (isTask) {
|
|
1190
|
+
const startRaw = row.task.started ?? row.task.created;
|
|
1191
|
+
if (startRaw !== void 0 && startRaw !== null && startRaw !== "" && Date.parse(startRaw) >= 0) parts.push(`${formatDateTime(startRaw).slice(0, 10)} ${t("fieldStarted")}`);
|
|
1192
|
+
if (row.task.ageDays !== null && row.task.ageDays !== void 0) parts.push(row.task.ageDays === 0 ? t("ageToday") : t("ageDaysText", { n: row.task.ageDays }));
|
|
1193
|
+
} else parts.push(`${t("fieldFinished")} ${formatDateTime(row.record.time ?? row.record.created ?? "", { fallback: t("timeUnknown") })}`);
|
|
1194
|
+
return (0, react.createElement)("div", { className: "dtl-taskrow" }, (0, react.createElement)("div", { className: "dtl-taskrow__row" }, (0, react.createElement)("span", { className: `dtl-rail dtl-rail--${tone}` }), (0, react.createElement)("div", { style: {
|
|
1195
|
+
flex: "1 1 auto",
|
|
1196
|
+
minWidth: 0
|
|
1197
|
+
} }, titleLine, (0, react.createElement)("div", { className: "dtl-taskrow__l2" }, (0, react.createElement)("span", {
|
|
1198
|
+
className: "dtl-wstag dtl-ellipsis",
|
|
1199
|
+
title: row.kind === "task" ? row.task.workspace.title : ""
|
|
1200
|
+
}, row.kind === "task" ? row.task.workspace.title : ""), tags.map((tag) => (0, react.createElement)(Tag, {
|
|
1201
|
+
key: tag,
|
|
1202
|
+
text: `#${tag}`
|
|
1203
|
+
})))), (0, react.createElement)("span", { className: "dtl-taskrow__when" }, parts.length === 0 ? t("timeUnknown") : parts.join("丨")), (0, react.createElement)("span", { className: "dtl-taskrow__actions" }, actions.resume ? resumeButton : null, actions.finish ? finishButton : null, actions.release ? releaseButton : null, actions.view ? viewButton : null)));
|
|
1204
|
+
}
|
|
1205
|
+
//#endregion
|
|
1141
1206
|
//#region src/client/board-home.ts
|
|
1142
1207
|
/**
|
|
1143
1208
|
* 看板首页(`main` 槽整页内容):6 统计卡 + 近 8 周趋势图 + 两区任务列表。
|
|
@@ -1412,7 +1477,8 @@ body[data-ds-dark-theme]{
|
|
|
1412
1477
|
t,
|
|
1413
1478
|
row,
|
|
1414
1479
|
variant: "mine",
|
|
1415
|
-
staleDays: board?.stats.staleDays ?? null
|
|
1480
|
+
staleDays: board?.stats.staleDays ?? null,
|
|
1481
|
+
board
|
|
1416
1482
|
})), (0, react.createElement)("div", null, (0, react.createElement)("div", { style: regionHeadRowStyle }, (0, react.createElement)("span", { style: {
|
|
1417
1483
|
color: "var(--dtl-fg-3)",
|
|
1418
1484
|
display: "inline-flex",
|
|
@@ -1483,7 +1549,8 @@ body[data-ds-dark-theme]{
|
|
|
1483
1549
|
key: group.workspaceId,
|
|
1484
1550
|
t,
|
|
1485
1551
|
group,
|
|
1486
|
-
staleDays: board?.stats.staleDays ?? null
|
|
1552
|
+
staleDays: board?.stats.staleDays ?? null,
|
|
1553
|
+
board
|
|
1487
1554
|
})))))))));
|
|
1488
1555
|
}
|
|
1489
1556
|
/** 角标:>0 才给数字,0 = undefined(不标)。 */
|
|
@@ -1494,7 +1561,7 @@ body[data-ds-dark-theme]{
|
|
|
1494
1561
|
* 统计口径(用户 2026-10-18):共 N|待办 N|进行中 N 常显(0 也显);
|
|
1495
1562
|
* 阻塞 / 停滞**只在 >0 时出现**。 */
|
|
1496
1563
|
function WorkspaceGroupView(props) {
|
|
1497
|
-
const { t, group, staleDays } = props;
|
|
1564
|
+
const { t, group, staleDays, board } = props;
|
|
1498
1565
|
const [open, setOpen] = (0, react.useState)(group.rows.length <= GROUP_COLLAPSE_OVER);
|
|
1499
1566
|
const stats = [
|
|
1500
1567
|
t("groupTotal", { n: group.total }),
|
|
@@ -1520,7 +1587,8 @@ body[data-ds-dark-theme]{
|
|
|
1520
1587
|
t,
|
|
1521
1588
|
row,
|
|
1522
1589
|
variant: "others",
|
|
1523
|
-
staleDays
|
|
1590
|
+
staleDays,
|
|
1591
|
+
board
|
|
1524
1592
|
}))) : null);
|
|
1525
1593
|
}
|
|
1526
1594
|
//#endregion
|