端上:#47~#55 打包 —— 行程双轨、动态类型、表单全局规范 (#53)

#48 计划/实际双轨:nodeAction(start/done/cancel),完成即转实际行程,
    实际行程不再显示状态;新增 actualStartedAt/actualEndedAt
#49 类型/出行方式/车辆 chip 改内部读状态(@Builder 值传参不刷新)
#50 类型点击立即变色、出行方式平铺、参与人显示已选头像
#51 节点类型动态加载(原硬编码5种)、地址 onPick 拿详细地址与坐标
#52 标签改主题色 Bold、必填红无星号、编辑行程标题同步
#53 表单两排版式(标签独占一行解决左右对不齐);电影弹层 layoutWeight 修滚动
#54 地址联想框加时间窗抑制(600ms 内不搜)、候选限高 180

含附件回填诊断日志(DIAG-ATTACH/FILL),待真机确认后移除
This commit is contained in:
2026-11-05 12:41:04 +08:00
parent 88fa38ecf9
commit 86de59616c
35 changed files with 5818 additions and 911 deletions
@@ -18,7 +18,9 @@ import { FbSheetClose } from './FbSheetClose';
import { FbSheetRing } from './FbSheetRing';
import { FbDateTimeRow, FbDateTimePicker, fbIsoOf, fbJoinDateTime } from './FbDateTimeField';
import { FB_TOKEN_STORE } from '../data/FbTokenStore';
import { createTask, updateTask, FbTaskDraft } from '../data/FbTaskRepo';
import { createTask, updateTask, FbTaskDraft,
setTaskStatus
} from '../data/FbTaskRepo';
import { FbApiResult } from '../data/FbHttp';
@ComponentV2
@@ -38,6 +40,13 @@ export struct FbTaskEditSheet {
/** v79:分类 / 循环(编辑时回填) */
@Param editingCatId: number = 0;
@Param editingRepeat: string = '';
/**
* 编辑时的原状态(`todo` / `doing` / `done`)。
*
* 🔴 2026-11-04:没有它的话,编辑一条**已完成**的任务会显示成"待办"(默认值),
* 保存时又把 'todo' 发出去 ⇒ **静默把已完成改回待办**。比不做还糟。
*/
@Param editingStatus: string = 'todo';
@Event onClose: () => void = (): void => {};
@Event onSaved: () => void = (): void => {};
@@ -47,6 +56,18 @@ export struct FbTaskEditSheet {
@Local boardId: number = 0;
/** v79:分类 id(0 = 不分类) */
@Local catId: number = 0;
/**
* 任务状态:`todo`(待办)/ `doing`(进行中)/ `done`(已完成)。
*
* 🔴 2026-11-04 新增。用户指出:「新建任务这个功能里面,怎么没有选择是待办、
* 正在进行,还是已完成的地方呢?」—— 原来 `save()` 里写死 `'todo'`,
* 用户建"进行中"的任务只能先建成待办再回去拖看板。
*
* ⚠️ 服务端 `Task::STATUS` 就是这三个值,别自造。
*/
@Local status: string = 'todo';
/** 编辑时记住原状态 —— 用于判断"用户改没改",改过才额外打 /status */
@Local status0: string = 'todo';
/** v79:循环规则('' = 不循环) */
@Local repeat: string = '';
/** 截止日期 `YYYY-MM-DD`,空 = 没有期限 */
@@ -63,6 +84,9 @@ export struct FbTaskEditSheet {
this.boardId = this.editingBoardId;
this.catId = this.editingCatId;
this.repeat = this.editingRepeat;
// 状态回填:新建时 editingStatus 是默认 'todo',编辑时是这条的原状态
this.status = this.editingStatus !== '' ? this.editingStatus : 'todo';
this.status0 = this.status;
// `2026-10-25 18:00` → 日期 + 时分(服务端给的是空格分隔)
const raw: string = this.editingDueAt.trim();
if (raw.length >= 16) {
@@ -187,6 +211,24 @@ export struct FbTaskEditSheet {
.width('100%')
.alignItems(HorizontalAlign.Start)
/* ---- 状态(2026-11-04 新增)----
🔴 用户指出「怎么没有选择是待办、正在进行,还是已完成的地方」。
放在「看板」之前:状态是任务最基本的属性,
而看板是"归到哪一类",后者依赖前者。 */
Column({ space: 6 }) {
Text('状态')
.fontSize(FbMetric.fontTiny)
.fontColor(FbColors.faint)
Row({ space: 8 }) {
this.statusChip('todo', '待做')
this.statusChip('doing', '进行中')
this.statusChip('done', '已完成')
}
.width('100%')
}
.width('100%')
.alignItems(HorizontalAlign.Start)
// ---- 看板 ----
Column({ space: 6 }) {
Text('看板')
@@ -332,7 +374,9 @@ export struct FbTaskEditSheet {
// 没有期限就发空串(服务端 `Api::dt('due_at', '')` → null)
dueAt: this.dueDate !== '' ? fbJoinDateTime(this.dueDate, this.dueTime) : '',
boardId: this.boardId,
status: 'todo',
/* 🔴 2026-11-04:不再写死 'todo' —— 用户可以在表单里选状态。
⚠️ 新建走 `createTask()`,服务端 POST 是**认** status 的(见 FbTaskRepo 文件头)。 */
status: this.status,
/* 0 = 顶层任务(新建子任务走 `FbTaskPanel.addKid`,不在这里) */
parentId: 0,
/* -1 = 这次不改负责人:表单里没有这一项,传 0 会把已指派的负责人清空 */
@@ -344,11 +388,29 @@ export struct FbTaskEditSheet {
const res: FbApiResult = this.editingId > 0
? await updateTask(token, this.editingId, d)
: await createTask(token, d);
this.saving = false;
if (!res.ok) {
this.saving = false;
this.note2 = res.error !== '' ? res.error : '保存失败,请稍后再试。';
return;
}
/* 🔴 2026-11-04:编辑时若改了状态,**必须额外打 `/status`**。
原因(见 FbTaskRepo 文件头):服务端 `PATCH /tasks/<id>` 会**显式忽略**
请求里的 status(原样回填),只有 `POST /tasks/<id>/status` 才生效。
⇒ 不补这一下,用户改了状态、界面说"保存成功"、回去看还是原状态。
⚠️ 只在"改过"时才打:没改就多一次无谓请求(还会白刷一次服务端 update)。
⚠️ 新建不用补:`POST /tasks` 是认 status 的。 */
if (this.editingId > 0 && this.status !== this.status0) {
const st: FbApiResult = await setTaskStatus(token, this.editingId, this.status);
if (!st.ok) {
this.saving = false;
// 内容已存好,只有状态没改成功 —— 说清楚,别让用户以为整条都没保存
this.note2 = '内容已保存,但状态没改成功,请再试一次。';
return;
}
}
this.saving = false;
this.onSaved();
}
@@ -367,6 +429,26 @@ export struct FbTaskEditSheet {
}
/** 分类单选胶囊(v79) */
@Builder
/**
* 状态 chip。
*
* ⚠️ 用 `!==` 而不是 `===` 判断"未选中":`status` 理论上只会是三个合法值,
* 但万一服务端返回了别的(脏数据),不能三个都不亮 —— 那样用户会以为控件坏了。
*/
@Builder
statusChip(v: string, name: string) {
Text(name)
.fontSize(FbMetric.fontTiny)
.fontColor(this.status === v ? '#FFFFFF' : FbColors.muted)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.backgroundColor(this.status === v ? this.accent : FbColors.inputBg)
.borderRadius(FbMetric.radiusPill)
.onClick((): void => {
this.status = v;
})
}
@Builder
catChip(id: number, name: string) {
Text(name)