留言底部被裁 + 留言支持附件 (#77)
用户 2026-11-05: 「留言输入框下面还有东西,现在被挡住了,根本看不见,也滚不动了。 但下面应该还有字什么的,看看是什么。」 「留言是可以需要添加附件的。」 ① 被挡住的是什么 是 #77 刚加的「回车发送」那行小字。 根因:`commentSection()` 是 `Scroll` 里**最后一个**子元素, 而 Scroll 内容末尾只留了 `height(6)`。 Scroll 本身是 `flexShrink(1)`(内容不够高时按内容收缩), 最后一行下边距一旦小于"卡片 padding + footBar 高度", 就会被判定为"已到底" —— 滚不过去、也看不见。 ⇒ 底部留白从 6 改成 28。 ② 留言带附件 服务端网页端早就有(tasks/comment_add 里的 attach_save_files), 但 **API 的 comments 端点只收 content**,且空文字直接 422 ⇒ 端上根本没有传附件的入口。三处一起改: · app/ApiHandler.php:comments POST 支持 multipart - 有 file 就走附件流程:先 addComment 拿 cid,再 Attachment::save (顺序不能反:save 要 owner_id,cid 是插入后才有的) - kind 用 task_comment,与网页端 attach_save_files 同一归属类型 (不同的话同一条留言的附件在两端互相看不见) - **允许只发附件不写字**:自动补占位文案「(附件)」,与网页端同口径 · FbTaskRepo.addTaskCommentWithFile():手拼 multipart - 不能复用 fbUploadOne(那个是"一个文件 + 一个字段", 评论要同时传 content + file) - 文件段必须带 Content-Type:服务端按它判"是不是图片" - boundary 之间用 \r\n(用 \n 有些解析器会把整段当一个字段, 表现为"文字上去了、附件不见了") · FbTaskPanel: - 「+ 添加附件」按钮(DocumentViewPicker,任意文件) - 选中的文件名显示成 chip,可点 ✕ 去掉 - 附件**不立刻上传**,随留言一起发(与「任务附件」那块语义不同: 那个传完就生效) - 发成功后清空待发附件,否则下一条会重复带上 实测(模拟上传链路): 只有文字 => cid=18 ✅ 文字 + 附件 => cid=19,附件数 1 ✅(key=files/6b4e...txt) 只发附件(占位) => cid=20 ✅ 测试数据已清理。
This commit is contained in:
@@ -47,8 +47,7 @@ import { fbImageSrc } from '../data/FbAttachQueue';
|
||||
import {
|
||||
addTaskComment, createTask, deleteTask, delTaskAttach, getTask, moveTask,
|
||||
setTaskStatus, uploadTaskAttach, FbTaskDraft,
|
||||
fetchTaskShares, shareTask, unshareTask,
|
||||
} from '../data/FbTaskRepo';
|
||||
fetchTaskShares, shareTask, unshareTask, addTaskCommentWithFile } from '../data/FbTaskRepo';
|
||||
import { parseTaskDetail } from '../data/FbParse';
|
||||
import { picker } from '@kit.CoreFileKit';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
@@ -129,6 +128,13 @@ export struct FbTaskPanel {
|
||||
/* #77c:附件预览浮层 —— 存当前要全屏看的那张图的地址,空 = 没开 */
|
||||
@Local previewSrc: string = '';
|
||||
@Local previewName: string = '';
|
||||
|
||||
/* #77g:留言待发附件(URI + 显示名)。
|
||||
⚠️ 只存**一个**:服务端评论附件接口一次收一个 `file`。
|
||||
要支持多个得改 API 成数组,用户没提,先按单个做(够用且简单)。 */
|
||||
@Local cmtAttachUri: string = '';
|
||||
@Local cmtAttachNames: string[] = [];
|
||||
@Local cmtBusy: boolean = false;
|
||||
// 共享(工单 #26)
|
||||
@Local roster: FbRosterPerson[] = [];
|
||||
@Local pickingShare: boolean = false;
|
||||
@@ -583,21 +589,90 @@ export struct FbTaskPanel {
|
||||
this.onChanged();
|
||||
}
|
||||
|
||||
/** 选一个附件挂到待发留言上(#77g)——**不立刻上传**,随留言一起发 */
|
||||
private async pickCmtAttach(): Promise<void> {
|
||||
if (this.cmtBusy) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const opts: picker.DocumentSelectOptions = new picker.DocumentSelectOptions();
|
||||
opts.maxSelectNumber = 1;
|
||||
const p: picker.DocumentViewPicker = new picker.DocumentViewPicker();
|
||||
const uris: string[] = await p.select(opts);
|
||||
if (uris.length === 0) {
|
||||
return;
|
||||
}
|
||||
const u: string = uris[0];
|
||||
const slash: number = u.lastIndexOf('/');
|
||||
const nm: string = slash >= 0 && slash < u.length - 1 ? u.substring(slash + 1) : '附件';
|
||||
this.cmtAttachUri = u;
|
||||
/* ⚠️ 用**新数组**赋值:`@Local` 只代理第一层,
|
||||
push 到原数组不会触发刷新(本项目踩过多次)。 */
|
||||
this.cmtAttachNames = [nm];
|
||||
this.note = '';
|
||||
} catch (e) {
|
||||
const be: BusinessError = e as BusinessError;
|
||||
hilog.warn(0x0000, 'FamilyBook', '选留言附件失败:%{public}s', JSON.stringify(be));
|
||||
this.note = '选文件失败,请再试一次。';
|
||||
}
|
||||
}
|
||||
|
||||
/** 去掉待发附件 */
|
||||
private dropCmtAttach(idx: number): void {
|
||||
this.cmtAttachUri = '';
|
||||
this.cmtAttachNames = [];
|
||||
}
|
||||
|
||||
/** 发一条留言 */
|
||||
/**
|
||||
* 发一条留言(#77g:支持带附件)。
|
||||
*
|
||||
* 🔴 用户 2026-11-05:「留言是可以需要添加附件的。」
|
||||
*
|
||||
* 两条路径:
|
||||
* · 没附件 → 原来的 `addTaskComment()`(JSON,轻)
|
||||
* · 有附件 → `addTaskCommentWithFile()`(multipart:content + file)
|
||||
*
|
||||
* ⚠️ **允许只发附件不写字**:拍张照片直接发是常见用法
|
||||
* (服务端也认,会自动补占位文案「(附件)」)。
|
||||
* 所以判空从"文字为空就 return"改成"文字和附件都为空才 return"。
|
||||
* ⚠️ 发成功后要清掉待发附件,否则下一条留言会重复带上这张图。
|
||||
*/
|
||||
private async sendComment(): Promise<void> {
|
||||
const txt: string = this.cmtDraft.trim();
|
||||
if (this.busy || txt === '') {
|
||||
const hasFile: boolean = this.cmtAttachUri !== '';
|
||||
if (this.busy || this.cmtBusy) {
|
||||
return;
|
||||
}
|
||||
if (txt === '' && !hasFile) {
|
||||
return;
|
||||
}
|
||||
this.busy = true;
|
||||
this.cmtBusy = true;
|
||||
this.note = '';
|
||||
const res: FbApiResult = await addTaskComment(this.token(), this.taskId, txt);
|
||||
this.busy = false;
|
||||
if (!res.ok) {
|
||||
this.note = res.error !== '' ? res.error : '留言没发出去。';
|
||||
return;
|
||||
|
||||
if (hasFile) {
|
||||
const err: string = await addTaskCommentWithFile(
|
||||
this.token(), this.taskId, txt, this.cmtAttachUri);
|
||||
this.busy = false;
|
||||
this.cmtBusy = false;
|
||||
if (err !== '') {
|
||||
this.note = err;
|
||||
return; // 附件留着,让用户能重试
|
||||
}
|
||||
} else {
|
||||
const res: FbApiResult = await addTaskComment(this.token(), this.taskId, txt);
|
||||
this.busy = false;
|
||||
this.cmtBusy = false;
|
||||
if (!res.ok) {
|
||||
this.note = res.error !== '' ? res.error : '留言没发出去。';
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.cmtDraft = '';
|
||||
this.cmtAttachUri = '';
|
||||
this.cmtAttachNames = [];
|
||||
await this.reload();
|
||||
this.onChanged();
|
||||
}
|
||||
@@ -730,7 +805,18 @@ export struct FbTaskPanel {
|
||||
.width('100%')
|
||||
.padding({ top: 10 })
|
||||
}
|
||||
Row().height(6)
|
||||
/* 🔴 #77g(用户 2026-11-05):「留言输入框下面还有东西,
|
||||
现在被挡住了,根本看不见,也滚不动了。」
|
||||
—— 被挡住的是 `commentSection()` 末尾那行「回车发送」小字。
|
||||
|
||||
根因:`commentSection` 是 `Scroll` 里的**最后一个**子元素,
|
||||
而这里原来只留了 `height(6)`。
|
||||
`Scroll` 又是 `flexShrink(1)`(内容不够高时按内容收缩),
|
||||
最后一行的下边距一旦小于"卡片 padding + footBar 高度",
|
||||
就会被判定为"已到底",滚不过去也看不见。
|
||||
⇒ 底部留一块**足够高**的空白(不是 6vp 意思一下),
|
||||
保证最后一行能滚到可视区中间。 */
|
||||
Row().height(28)
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
@@ -1634,12 +1720,67 @@ export struct FbTaskPanel {
|
||||
.width('100%')
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
/* 「回车发送」提示:放在输入行**下方**,小字、不抢视线 */
|
||||
Text('回车发送')
|
||||
.fontSize(10)
|
||||
.fontColor(FbColors.faint)
|
||||
/* 🔴 #77g(用户 2026-11-05):「留言是可以需要添加附件的。」
|
||||
|
||||
服务端**早就支持** —— `tasks/comment_add` 里一直有
|
||||
`attach_save_files($familyId, 'task_comment', $cid, 'attach', $me)`
|
||||
(网页端那个表单也已经有 file input),只是**鸿蒙端从来没传过附件**。
|
||||
⇒ `addTaskComment()` 目前只发 JSON(content 一个字段),
|
||||
要带附件必须改成 multipart 上传 —— 见 FbTaskRepo 的改动。
|
||||
|
||||
⚠️ 交互设计:附件是"随这条留言一起发出",不是立刻上传
|
||||
(与「任务附件」那块不同 —— 那个传完就生效)。
|
||||
所以选中的文件先挂在本地,点「发送」时和文字一起提交。
|
||||
⚠️ 选中的文件名要显示出来,否则用户不知道有没有选上。 */
|
||||
if (this.cmtAttachNames.length > 0) {
|
||||
Flex({ wrap: FlexWrap.Wrap }) {
|
||||
ForEach(this.cmtAttachNames, (n: string, i: number) => {
|
||||
Row({ space: 4 }) {
|
||||
Text('📎')
|
||||
.fontSize(9)
|
||||
Text(n)
|
||||
.fontSize(9)
|
||||
.fontColor(FbColors.muted)
|
||||
.maxLines(1)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
.constraintSize({ maxWidth: 120 })
|
||||
Text('✕')
|
||||
.fontSize(9)
|
||||
.fontColor(FbColors.faint)
|
||||
.onClick((): void => {
|
||||
this.dropCmtAttach(i);
|
||||
})
|
||||
}
|
||||
.padding({ left: 7, right: 7, top: 3, bottom: 3 })
|
||||
.margin({ right: 6, bottom: 4 })
|
||||
.backgroundColor(FbColors.inputBg)
|
||||
.borderRadius(FbMetric.radiusPill)
|
||||
.alignItems(VerticalAlign.Center)
|
||||
}, (n: string, i: number): string => 'ca_' + i.toString() + '_' + n)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ top: 2 })
|
||||
.padding({ top: 6 })
|
||||
}
|
||||
|
||||
/* 「回车发送」+「添加附件」同一行:一个说明、一个动作 */
|
||||
Row({ space: 8 }) {
|
||||
Text('回车发送')
|
||||
.fontSize(10)
|
||||
.fontColor(FbColors.faint)
|
||||
.layoutWeight(1)
|
||||
Text(this.cmtBusy ? '发送中…' : '+ 添加附件')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(this.cmtBusy ? FbColors.faint : this.accent)
|
||||
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
|
||||
.backgroundColor(fbAlpha(this.accent, 0.10))
|
||||
.borderRadius(FbMetric.radiusPill)
|
||||
.onClick((): void => {
|
||||
this.pickCmtAttach();
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ top: 6 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
|
||||
Reference in New Issue
Block a user