观影详情卡:评价评论内嵌到底部可直评;简介/观影照片按全局规范改样式 (#79)
用户:电影弹出框,评价与评论放在最下面,直接显示出来,可以直接评论,不用再打开了。
简介、观影照片,注意按照我们的约定进行颜色和字体的修改。
① 评价与评论内嵌
- 删掉 FbMovieDetailSheet 里的入口胶囊与 reviewsOpen 死状态;
- 在 body() 末尾内嵌 FbMovieReviewSheet({embedded:true}),顺序与网页端一致:
海报/片名/时间/评分/影院/同看的人/简介/备注/观影照片/**评价与评论**;
- FbMovieReviewSheet 加 embedded 开关,两种形态共用 reviewContent(),
不抄第二份 300 行(抄必然改一处漏一处)。
② 评论框默认隐藏(用户明确):点「发表评论/回复」才展开,发送成功或取消即收起
(submitComment 成功路径本来就会把 commentTarget 归零)。
③ 我的评价两态(用户选定):已评过=摘要(★+感受+正文3行+修改/删除),
没评过=展开表单。保存成功自动收起;删除成功自动展开并清空草稿。
加 myFormTouched 防止 load() 重跑时把刚写完的表单又收起来。
④ 简介/备注/观影照片标签按《全局表单规范》:
主题色 accent + Bold + fontSmall + letterSpacing 0.6,标签独占一行、内容第二行整宽。
改前是 fontTiny + faint 灰,与正文挤在一起。「N 张」保持 fontTiny+faint(同 extra 口径)。
验证(真机 5NC0225B27004718):
- 逐段抓 UI 文本,顺序命中 简介→观影照片→评价与评论,评价在最下面;
- 标签取最深5%像素均值:简介(98,67,110)、评价与评论(98,68,110)、写评价(97,69,109),
均为主题紫色相;改前的 faint 灰是 (150,150,150) 量级;
- 底部文本含 写评价/评分/观后感受/发表评价,未评过故表单默认展开;
- hvigorw assembleHap BUILD SUCCESSFUL。
This commit is contained in:
@@ -38,6 +38,23 @@ export struct FbMovieReviewSheet {
|
||||
@Param movieTitle: string = '';
|
||||
/** 观影模块色 */
|
||||
@Param accent: string = '#7A4E8C';
|
||||
/**
|
||||
* 内嵌模式(2026-11-06)。
|
||||
*
|
||||
* 🔴 用户原话:「电影弹出框,评价与评论放在最下面,直接显示出来,
|
||||
* 可以直接评论,不用再打开了。」
|
||||
* ⇒ 本组件现在有两种形态:
|
||||
* · `embedded = false`(默认,老形态):自带遮罩 + 圆角卡 + 标题栏,
|
||||
* 作为独立浮层弹出(`bindSheet` 之类);
|
||||
* · `embedded = true`:**只出内容**(我的评价 + 全部影评 + 评论),
|
||||
* 不画遮罩、不画圆角卡、不画标题栏 —— 由 `FbMovieDetailSheet`
|
||||
* 在它自己的 Scroll 里直接渲染。
|
||||
*
|
||||
* ⚠️ 为什么不另写一份「内嵌版」:影评/评论的表单、打分、感受多选、
|
||||
* 回复树有 300+ 行,抄一份必然出现"改一处漏一处"(本项目已多次踩过)。
|
||||
* 加一个开关,两条路径共用同一套逻辑与 DOM。
|
||||
*/
|
||||
@Param embedded: boolean = false;
|
||||
/** 保存/删除后通知宿主(列表的平均分可能要刷新) */
|
||||
@Event onChanged: () => void = (): void => {};
|
||||
|
||||
@@ -58,6 +75,21 @@ export struct FbMovieReviewSheet {
|
||||
@Local draft: string = '';
|
||||
@Local ratingPickOpen: boolean = false;
|
||||
@Local saving: boolean = false;
|
||||
/**
|
||||
* 我的评价表单是否展开(2026-11-06)。
|
||||
*
|
||||
* 🔴 用户选择:「已评过=收起,没评过=展开」。
|
||||
* · 已经写过的人:只看到评分摘要,想改再点「修改」展开 ——
|
||||
* 否则每次打开详情卡都被一大块表单占住,评论被挤到很下面;
|
||||
* · 还没写的人:直接看到表单,进来就能评。
|
||||
*
|
||||
* ⚠️ 初始值给 `false`,由 `load()` 拿到 `mine` 之后再定
|
||||
* (数据还没回来时不知道写没写过,不能提前猜)。
|
||||
* ⚠️ 是 `@Local` 而不是从这里反推 `this.mine !== null`:
|
||||
* 用户点「修改」要能把它展开,而 `mine` 在**保存成功后**才会变,
|
||||
* 两者不是同一个状态。
|
||||
*/
|
||||
@Local myFormOpen: boolean = false;
|
||||
|
||||
/** 评论输入(针对某条影评) */
|
||||
@Local commentTarget: number = 0;
|
||||
@@ -93,8 +125,26 @@ export struct FbMovieReviewSheet {
|
||||
this.pickedFeelings = this.mine.feelings.slice();
|
||||
this.draft = this.mine.content;
|
||||
}
|
||||
/* 「已评过=收起,没评过=展开」(用户 2026-11-06 选定)。
|
||||
⚠️ 只在**首次加载**时决定,不能每次 load 都重置:
|
||||
保存成功后 `load()` 会再跑一遍,若无条件按 `mine !== null` 赋 false,
|
||||
用户刚写完评价、表单立刻收起来 —— 看着像"没保存成功"。
|
||||
⇒ 用 `loading` 之前那个「首轮」标记控制(见 myFormTouched)。 */
|
||||
if (!this.myFormTouched) {
|
||||
this.myFormOpen = this.mine === null;
|
||||
this.myFormTouched = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单开合是否已被"用户意图"或"首轮加载"定过。
|
||||
*
|
||||
* 为什么要这个标记:`load()` 在保存/删除后都会重跑,
|
||||
* 若无条件按 `mine` 重置 `myFormOpen`,刚写完评价表单就自己收起,
|
||||
* 用户会以为没保存成功。⇒ 只有第一轮才按"写没写过"决定。
|
||||
*/
|
||||
@Local myFormTouched: boolean = false;
|
||||
|
||||
private catalogKeys(): string[] {
|
||||
return Object.keys(this.catalog);
|
||||
}
|
||||
@@ -150,6 +200,10 @@ export struct FbMovieReviewSheet {
|
||||
this.note = res.error !== '' ? res.error : '没发出去,请稍后再试。';
|
||||
return;
|
||||
}
|
||||
/* 保存成功 ⇒ 收起表单(用户刚写完,接着就是想看结果/评论,
|
||||
而不是继续盯着输入框)。⚠️ 必须在 `load()` 之后置,否则会被
|
||||
load 里"首轮"判断覆盖。 */
|
||||
this.myFormOpen = false;
|
||||
await this.load();
|
||||
this.onChanged();
|
||||
}
|
||||
@@ -166,6 +220,12 @@ export struct FbMovieReviewSheet {
|
||||
this.note = res.error !== '' ? res.error : '没删掉,请稍后再试。';
|
||||
return;
|
||||
}
|
||||
/* 删掉之后「我的评价」不存在了 ⇒ 表单重新展开,让用户能再写一个
|
||||
(否则会停在一个空的摘要块上)。清空草稿与打分,别把旧内容带回来。 */
|
||||
this.rating = 0;
|
||||
this.pickedFeelings = [];
|
||||
this.draft = '';
|
||||
this.myFormOpen = true;
|
||||
await this.load();
|
||||
this.onChanged();
|
||||
}
|
||||
@@ -228,110 +288,225 @@ export struct FbMovieReviewSheet {
|
||||
}
|
||||
|
||||
build() {
|
||||
Stack({ alignContent: Alignment.Center }) {
|
||||
Column()
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.onClick((): void => {
|
||||
this.onClose();
|
||||
})
|
||||
/* 🔴 用户 2026-11-06:「评价与评论放在最下面,直接显示出来,可以直接评论,
|
||||
不用再打开了。」
|
||||
⇒ 内嵌模式只出内容(发表遮罩/圆角卡/标题栏),由详情卡在自己的
|
||||
Scroll 里直接渲染。内容与独立模式**同一份** `reviewContent()`,
|
||||
不抄第二份 —— 影评/评论那 300 行抄一份必然改一处漏一处。 */
|
||||
if (this.embedded) {
|
||||
this.reviewContent(true)
|
||||
} else {
|
||||
Stack({ alignContent: Alignment.Center }) {
|
||||
Column()
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.onClick((): void => {
|
||||
this.onClose();
|
||||
})
|
||||
|
||||
Column() {
|
||||
FbSheetRing({ radius: FbMetric.radius, ringColor: this.accent }) {
|
||||
Column({ space: 0 }) {
|
||||
// ---- 标题栏 ----
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text('评价与评论' + (this.movieTitle !== '' ? ' · ' + this.movieTitle : ''))
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
.maxLines(1)
|
||||
Text(this.loading
|
||||
? '正在加载…'
|
||||
: (this.count > 0
|
||||
? this.count.toString() + ' 条评价 · 均分 ' + this.avg.toFixed(1)
|
||||
: '还没有评价,等你来评一个'))
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(this.accent)
|
||||
.maxLines(1)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
.flexShrink(1)
|
||||
|
||||
FbSheetClose({
|
||||
onClose: (): void => {
|
||||
this.onClose();
|
||||
},
|
||||
color: this.accent
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ bottom: 8 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
if (this.note !== '') {
|
||||
Text(this.note)
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.red)
|
||||
.width('100%')
|
||||
.padding({ top: 6, bottom: 6 })
|
||||
.maxLines(3)
|
||||
}
|
||||
|
||||
Scroll() {
|
||||
Column({ space: 12 }) {
|
||||
// ---- 我的影评(写 / 改 / 删)----
|
||||
if (this.canWrite()) {
|
||||
this.myReviewBlock()
|
||||
Column() {
|
||||
FbSheetRing({ radius: FbMetric.radius, ringColor: this.accent }) {
|
||||
Column({ space: 0 }) {
|
||||
// ---- 标题栏 ----
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text('评价与评论' + (this.movieTitle !== '' ? ' · ' + this.movieTitle : ''))
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
.maxLines(1)
|
||||
Text(this.summaryText())
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(this.accent)
|
||||
.maxLines(1)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
.flexShrink(1)
|
||||
|
||||
// ---- 全部影评 + 评论 ----
|
||||
if (this.reviews.length === 0 && !this.loading) {
|
||||
Text('还没有评价')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.faint)
|
||||
.width('100%')
|
||||
} else {
|
||||
ForEach(this.reviews, (rv: FbMovieReview) => {
|
||||
this.reviewBlock(rv)
|
||||
}, (rv: FbMovieReview): string => 'mvr_' + rv.id.toString())
|
||||
}
|
||||
FbSheetClose({
|
||||
onClose: (): void => {
|
||||
this.onClose();
|
||||
},
|
||||
color: this.accent
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ top: 4, bottom: 20 })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.padding({ bottom: 8 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
if (this.note !== '') {
|
||||
Text(this.note)
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.red)
|
||||
.width('100%')
|
||||
.padding({ top: 6, bottom: 6 })
|
||||
.maxLines(3)
|
||||
}
|
||||
|
||||
Scroll() {
|
||||
this.reviewContent(false)
|
||||
}
|
||||
.constraintSize({ maxHeight: 520 })
|
||||
.width('100%')
|
||||
.scrollBar(BarState.Off)
|
||||
.align(Alignment.TopStart)
|
||||
}
|
||||
.constraintSize({ maxHeight: 520 })
|
||||
.width('100%')
|
||||
.scrollBar(BarState.Off)
|
||||
.align(Alignment.TopStart)
|
||||
.padding({ left: 18, right: 18, top: 18, bottom: 18 })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: 18, right: 18, top: 18, bottom: 18 })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
.width('92%')
|
||||
}
|
||||
.width('92%')
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.hitTestBehavior(HitTestMode.Transparent)
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.hitTestBehavior(HitTestMode.Transparent)
|
||||
}
|
||||
|
||||
/** 我的影评区块:未写 = 写表单;已写 = 摘要 + 改/删 */
|
||||
/** 标题下方那句统计(独立模式用;内嵌模式由详情卡的区块标题承担) */
|
||||
private summaryText(): string {
|
||||
if (this.loading) {
|
||||
return '正在加载…';
|
||||
}
|
||||
if (this.count > 0) {
|
||||
return this.count.toString() + ' 条评价 · 均分 ' + this.avg.toFixed(1);
|
||||
}
|
||||
return '还没有评价,等你来评一个';
|
||||
}
|
||||
|
||||
/**
|
||||
* 评价与评论的正文(两种形态共用)。
|
||||
*
|
||||
* @param inner 是否为内嵌模式 —— 内嵌时不加额外上下留白
|
||||
* (宿主已经在卡片里排好了间距,再补会多一截空)。
|
||||
*/
|
||||
@Builder
|
||||
reviewContent(inner: boolean) {
|
||||
Column({ space: 12 }) {
|
||||
// ---- 我的影评(写 / 改 / 删)----
|
||||
if (this.canWrite()) {
|
||||
this.myReviewBlock()
|
||||
}
|
||||
|
||||
// ---- 全部影评 + 评论 ----
|
||||
if (this.reviews.length === 0 && !this.loading) {
|
||||
Text('还没有评价')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.faint)
|
||||
.width('100%')
|
||||
} else {
|
||||
ForEach(this.reviews, (rv: FbMovieReview) => {
|
||||
this.reviewBlock(rv)
|
||||
}, (rv: FbMovieReview): string => 'mvr_' + rv.id.toString())
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ top: inner ? 0 : 4, bottom: inner ? 0 : 20 })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
|
||||
/**
|
||||
* 我的影评区块。
|
||||
*
|
||||
* 两个态(用户 2026-11-06 选定「已评过=收起,没评过=展开」):
|
||||
* · `myFormOpen === false` 且已写过 → **摘要态**:评分 + 感受 + 正文摘要
|
||||
* +「修改 / 删除」,只占几行,把空间让给下面的评论;
|
||||
* · `myFormOpen === true` 或无评价 → **表单态**:打分 + 感受 + 正文 + 发表。
|
||||
*/
|
||||
@Builder
|
||||
myReviewBlock() {
|
||||
if (this.mine !== null && !this.myFormOpen) {
|
||||
this.myReviewSummary()
|
||||
} else {
|
||||
this.myReviewForm()
|
||||
}
|
||||
}
|
||||
|
||||
/** 收起态:我写过的评价摘要 + 改/删 */
|
||||
@Builder
|
||||
myReviewSummary() {
|
||||
Column({ space: 6 }) {
|
||||
Row({ space: 8 }) {
|
||||
Text('我的评价')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.fontColor(this.accent)
|
||||
.letterSpacing(0.6)
|
||||
Blank()
|
||||
Text('修改')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(this.accent)
|
||||
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
|
||||
.onClick((): void => {
|
||||
this.myFormOpen = true;
|
||||
})
|
||||
Text('删除')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.red)
|
||||
.padding({ left: 6, right: 2, top: 4, bottom: 4 })
|
||||
.onClick((): void => {
|
||||
this.deleteReview();
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
if (this.mine !== null) {
|
||||
Row({ space: 8 }) {
|
||||
Text('★ ' + this.mine.rating.toFixed(0))
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor('#C08A2E')
|
||||
if (this.mine.feelings.length > 0) {
|
||||
Text(this.mine.feelings.map((k: string): string => this.featText(k)).join(' · '))
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.muted)
|
||||
.maxLines(1)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
.layoutWeight(1)
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
if (this.mine.content !== '') {
|
||||
Text(this.mine.content)
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.ink)
|
||||
.maxLines(3)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
.width('100%')
|
||||
}
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.padding(12)
|
||||
.backgroundColor(fbAlpha(this.accent, 0.06))
|
||||
.borderRadius(FbMetric.radiusS)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
|
||||
/** 展开态:写 / 改评价的表单 */
|
||||
@Builder
|
||||
myReviewForm() {
|
||||
Column({ space: 8 }) {
|
||||
Row({ space: 8 }) {
|
||||
Text(this.mine !== null ? '我的评价' : '写评价')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.fontColor(this.accent)
|
||||
.letterSpacing(0.6)
|
||||
Blank()
|
||||
if (this.mine !== null) {
|
||||
Text('收起')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.muted)
|
||||
.padding({ left: 6, right: 4, top: 4, bottom: 4 })
|
||||
.onClick((): void => {
|
||||
this.myFormOpen = false;
|
||||
})
|
||||
Text('删除')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.red)
|
||||
|
||||
Reference in New Issue
Block a user