鸿蒙端:分类、循环改成可编辑 (#83)

用户 2026-11-05:「分类和重复,也就是循环,都需要加上,鸿蒙端也得加上。」

原来这两个只在**有值**时才画一行只读文字
(`if (this.categoryOf() !== )` / `if (this.repeatOf() !== )`)——
用户不仅改不了,连"这里本来有这一项"都看不出来。
(这与 #76 里网页端"负责人为空就整行消失"是同一个毛病。)

改法:与截止/负责人同形态(标签一行 + 控件一行),**始终显示**。

① FbTaskPanel 新增 `@Param cats: FbCatOpt[]`
   ⚠️ 与 boards 同一理由:**不能从任务里反推** ——
      反推只能看见"已经被用过的"分类,新建的分类在端上永远选不到。
② 下拉用 ArkUI 原生 `Select`,第 0 项固定「未分类」/「不循环」
   - 分类:index 0 → category_id = 0;index N → cats[N-1].id
   - 循环:index 0 → (不循环);index N → REPEAT_KEYS[N-1]
   ⚠️ REPEAT_KEYS 与 REPEAT_WORDS **必须严格对齐**,
      错一位就是"选每天存成每周"这种静默错数据。
③ `inlineBusy` 时退回只读文字:请求在飞的时候不给选,
   否则用户连点两下会发两次相反的值。
④ 首页 / 任务页两处宿主传 `cats`(tskCats / metaCats)。
⑤ 服务端 API 早就支持这两个字段(`Api::str(repeat_rule, ...)`),
   本轮只补了 `Task::setAttrs` 的 repeat_rule 分支(见服务端那次提交)。

编译通过,已装真机。
This commit is contained in:
2026-11-05 20:15:25 +08:00
parent 8fe1131334
commit 79bc593d43
3 changed files with 142 additions and 6 deletions
+140 -6
View File
@@ -31,7 +31,7 @@
import { FbColors, FbMetric, FB_MASK, fbAlpha, fbTint } from '../common/Theme'; import { FbColors, FbMetric, FB_MASK, fbAlpha, fbTint } from '../common/Theme';
import { import {
FbBoardOpt, FbTaskAttach, FbTaskComment, FbTaskDetail, FbTaskItem, FbTaskKid, FbTaskShare, FbBoardOpt, FbCatOpt, FbTaskAttach, FbTaskComment, FbTaskDetail, FbTaskItem, FbTaskKid, FbTaskShare,
} from '../model/Types'; } from '../model/Types';
import { FbSheetRing } from './FbSheetRing'; import { FbSheetRing } from './FbSheetRing';
import { FbSheetClose } from './FbSheetClose'; import { FbSheetClose } from './FbSheetClose';
@@ -69,6 +69,16 @@ export struct FbTaskPanel {
@Param seed: FbTaskItem | null = null; @Param seed: FbTaskItem | null = null;
/** 全部看板(含空看板)—— 父页面用 `?view=boards` 拿,别从任务里反推 */ /** 全部看板(含空看板)—— 父页面用 `?view=boards` 拿,别从任务里反推 */
@Param boards: FbBoardOpt[] = []; @Param boards: FbBoardOpt[] = [];
/**
* 全部分类(`id=0` 由这里补「未分类」,所以传进来的是"真实分类")。
*
* 🔴 #83(用户 2026-11-05):「分类和重复,也就是循环,都需要加上,
* 鸿蒙端也得加上。」
*
* ⚠️ 与 boards 同一理由:**不能从任务里反推** ——
* 反推只能看见"已经被用过的"分类,新建的分类在端上永远选不到。
*/
@Param cats: FbCatOpt[] = [];
/** 模块主题色(任务 = 赭红) */ /** 模块主题色(任务 = 赭红) */
@Param accent: string = '#A8574F'; @Param accent: string = '#A8574F';
/** /**
@@ -1445,13 +1455,77 @@ export struct FbTaskPanel {
.width('100%') .width('100%')
.alignItems(HorizontalAlign.Start) .alignItems(HorizontalAlign.Start)
/* ---- 分类 / 循环(同样上下两行)---- */ /* ---- 分类 ---- */
if (this.categoryOf() !== '') { /* 🔴 #83(用户 2026-11-05):「分类和重复,也就是循环,都需要加上。」
this.kvRow('分类', this.categoryOf()) 原来这两个只在**有值**时才画一行只读文字 ——
用户不仅改不了,连"这里本来有这一项"都看不出来。
⇒ 改成与截止/负责人同形态(标签一行 + 控件一行),且始终显示。 */
Column({ space: 6 }) {
this.fieldLb('分类')
if (this.inlineBusy) {
Text(this.categoryOf() !== '' ? this.categoryOf() : '未分类')
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.faint)
.width('100%')
.height(40)
.padding({ left: 12, right: 12 })
.backgroundColor(FbColors.inputBg)
.borderRadius(FbMetric.radiusS)
} else {
Select(this.catOptions())
.selected(this.catIndex())
.value(this.categoryOf() !== '' ? this.categoryOf() : '未分类')
.font({ size: FbMetric.fontSmall })
.fontColor(FbColors.ink)
.selectedOptionFont({ size: FbMetric.fontSmall })
.optionFont({ size: FbMetric.fontSmall })
.width('100%')
.height(40)
.backgroundColor(FbColors.inputBg)
.borderRadius(FbMetric.radiusS)
.onSelect((idx: number): void => {
/* index 0 = 「未分类」→ category_id 传 0 */
const cid: number = idx === 0 ? 0
: (idx - 1 < this.cats.length ? this.cats[idx - 1].id : 0);
this.saveField('category_id', '', cid);
})
}
} }
if (this.repeatOf() !== '') { .width('100%')
this.kvRow('循环', this.repeatOf()) .alignItems(HorizontalAlign.Start)
/* ---- 循环 ---- */
Column({ space: 6 }) {
this.fieldLb('循环')
if (this.inlineBusy) {
Text(this.repeatOf() !== '' ? this.repeatOf() : '不循环')
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.faint)
.width('100%')
.height(40)
.padding({ left: 12, right: 12 })
.backgroundColor(FbColors.inputBg)
.borderRadius(FbMetric.radiusS)
} else {
Select(this.repeatOptions())
.selected(this.repeatIndex())
.value(this.repeatOf() !== '' ? this.repeatWordOf(this.repeatOf()) : '不循环')
.font({ size: FbMetric.fontSmall })
.fontColor(FbColors.ink)
.selectedOptionFont({ size: FbMetric.fontSmall })
.optionFont({ size: FbMetric.fontSmall })
.width('100%')
.height(40)
.backgroundColor(FbColors.inputBg)
.borderRadius(FbMetric.radiusS)
.onSelect((idx: number): void => {
const rk: string = idx === 0 ? '' : FbTaskPanel.REPEAT_KEYS[idx - 1];
this.saveField('repeat_rule', rk, -1);
})
}
} }
.width('100%')
.alignItems(HorizontalAlign.Start)
/* ---- 备注 ---- */ /* ---- 备注 ---- */
/* 🔴 #77n(用户 2026-11-05):「包括下面的备注也是一样」 /* 🔴 #77n(用户 2026-11-05):「包括下面的备注也是一样」
@@ -1488,6 +1562,66 @@ export struct FbTaskPanel {
.padding({ top: 4 }) .padding({ top: 4 })
} }
/* ---------------- 分类 / 循环 的下拉选项 ---------------- */
/**
* 循环的可选键(与编辑表单 `FbTaskEditSheet.repeatChip` 同一套)。
* ⚠️ 顺序必须与 `REPEAT_WORDS` 严格对齐 —— 两边错一位就会"选每天存成每周"。
*/
private static readonly REPEAT_KEYS: string[] = ['day', 'week', 'month', 'year'];
private static readonly REPEAT_WORDS: string[] = ['每天', '每周', '每月', '每年'];
/** 分类下拉的选项文本:第 0 项固定是「未分类」(对应 category_id = 0) */
private catOptions(): SelectOption[] {
const out: SelectOption[] = [{ value: '未分类' }];
for (let i = 0; i < this.cats.length; i++) {
out.push({ value: this.cats[i].name });
}
return out;
}
/** 当前选中的下标(0 = 未分类) */
private catIndex(): number {
const d: FbTaskDetail | null = this.detail;
const cid: number = d !== null ? d.categoryId : 0;
if (cid <= 0) {
return 0;
}
for (let i = 0; i < this.cats.length; i++) {
if (this.cats[i].id === cid) {
return i + 1;
}
}
return 0; // 分类被删了/不在这份清单里 → 显示未分类
}
private repeatOptions(): SelectOption[] {
const out: SelectOption[] = [{ value: '不循环' }];
for (let i = 0; i < FbTaskPanel.REPEAT_WORDS.length; i++) {
out.push({ value: FbTaskPanel.REPEAT_WORDS[i] });
}
return out;
}
private repeatIndex(): number {
const r: string = this.repeatOf();
for (let i = 0; i < FbTaskPanel.REPEAT_KEYS.length; i++) {
if (FbTaskPanel.REPEAT_KEYS[i] === r) {
return i + 1;
}
}
return 0;
}
private repeatWordOf(key: string): string {
for (let i = 0; i < FbTaskPanel.REPEAT_KEYS.length; i++) {
if (FbTaskPanel.REPEAT_KEYS[i] === key) {
return FbTaskPanel.REPEAT_WORDS[i];
}
}
return '不循环';
}
/** 截止时间的时分(`HH:mm`),给日期浮层做初值;没有期限就给当前时刻 */ /** 截止时间的时分(`HH:mm`),给日期浮层做初值;没有期限就给当前时刻 */
private dueTimeOf(): string { private dueTimeOf(): string {
const t: FbTaskItem | null = this.cur(); const t: FbTaskItem | null = this.cur();
@@ -1950,6 +1950,7 @@ export struct FbHomePage {
taskId: this.tskBase.id, taskId: this.tskBase.id,
seed: this.tskBase, seed: this.tskBase,
boards: this.tskBoards, boards: this.tskBoards,
cats: this.tskCats,
accent: this.taskAccent(), accent: this.taskAccent(),
refreshTick: this.tskTick, refreshTick: this.tskTick,
onClose: (): void => { onClose: (): void => {
@@ -547,6 +547,7 @@ export struct FbTasksPage {
taskId: this.panelId, taskId: this.panelId,
seed: this.panelSeed, seed: this.panelSeed,
boards: this.boardOpts(), boards: this.boardOpts(),
cats: this.metaCats,
accent: this.accent, accent: this.accent,
refreshTick: this.panelTick, refreshTick: this.panelTick,
onClose: (): void => { onClose: (): void => {