鸿蒙原生应用实战(四):我的追剧与统计页 —— 三态Tab与数据可视化
鸿蒙原生应用实战(四):我的追剧与统计页 —— 三态Tab与数据可视化
前言
上一篇我们完成了搜索页和详情页。本篇将实现最后两个功能页面——我的追剧页(MyListPage) 和 统计页(StatsPage)。
这两个页面代表着从"浏览"到"管理"的转变,涉及更多交互逻辑:
- 多Tab状态管理(在看/想看/看完)
- 列表数据的增删改
- 数据可视化展示
- 成就徽章系统
一、我的追剧页(MyListPage)完整实现
1.1 页面功能分析
MyListPage
├── 顶部导航栏:返回 + 标题"我的追剧" + 管理按钮
├── Tab切换栏:在看 | 想看 | 看完
├── 筛选结果列表:根据选中Tab展示对应剧集
│ ├── 有数据:剧集卡片列表
│ └── 无数据:空状态引导页
└── 快速操作区:批量导入 / 导出列表 / 排序 / 统计
1.2 数据结构
interface MyDrama {
id: number;
title: string;
genre: string;
episodes: number; // 总集数
watchedEpisodes: number; // 已看集数
status: string; // "连载中"/"已完结"
rating: number; // 评分
progress: number; // 进度百分比 0-100
myStatus: string; // 我的状态:"在看"/"想看"/"看完" (核心字段)
addedDate: string; // 添加日期
}
核心字段 myStatus:它决定了剧集属于哪个Tab。三个Tab的值分别为 "在看"、"想看"、"看完"。
1.3 状态变量与初始化
@Component
struct MyListPage {
@State currentTab: string = '在看'; // 当前选中的Tab
@State myDramas: MyDrama[] = []; // 全部追剧数据
tabs: string[] = ['在看', '想看', '看完'];
aboutToAppear(): void {
this.initMyDramas();
}
initMyDramas(): void {
this.myDramas = [
{ id: 1, title: '星落凝成糖', genre: '古装仙侠', episodes: 40, watchedEpisodes: 12,
status: '连载中', rating: 47, progress: 30, myStatus: '在看', addedDate: '2025-01-10' },
// ... 在看4部 + 想看3部 + 看完3部 = 10条数据
];
}
}
数据分布策略:
- “在看”(4部):正在追的剧,progress 13%-44%不等
- “想看”(3部):还没开始看,progress均为0%
- “看完”(3部):已看完,progress均为100%
1.4 Tab过滤
getFilteredDramas(): MyDrama[] {
return this.myDramas.filter((item: MyDrama) => item.myStatus === this.currentTab);
}
与搜索页的filter对比:
- 搜索页:从全量数据中多次过滤(关键词 + 分类 + 状态)
- 我的追剧页:按Tab精确匹配
myStatus字段,一次过滤
1.5 顶部导航栏
@Builder buildHeader() {
Column() {
Row() {
Text('←').fontSize(20).fontColor('#333333')
.onClick(() => { router.back(); })
Blank()
Text('我的追剧').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#1A1A2E')
Blank()
Text('管理').fontSize(14).fontColor('#FF6B35') // 预留功能入口
}
.width('100%').padding({ left: 16, right: 16, top: 12, bottom: 8 })
// Tab切换
Row() {
ForEach(this.tabs, (tab: string) => {
Column() {
Text(tab).fontSize(14)
.fontColor(this.currentTab === tab ? '#FFFFFF' : '#666666')
.padding({ left: 20, right: 20, top: 6, bottom: 6 })
.backgroundColor(this.currentTab === tab ? '#FF6B35' : '#F0F0F0')
.borderRadius(16)
}
.margin({ right: 10 })
.onClick(() => { this.currentTab = tab; })
}, (tab: string) => tab)
}
.width('100%').padding({ left: 16, top: 8, bottom: 8 })
}
.width('100%').backgroundColor('#FFFFFF').padding({ bottom: 8 })
}
Tab样式区别:这里与详情页的Tab样式不同——
- 详情页Tab:文字 + 下划线指示器(类似浏览器标签)
- 我的追剧Tab:胶囊形状按钮(类似分类标签)
不同场景选择不同的Tab样式,视觉上给用户明确的区分。
1.6 剧集卡片
@Builder buildDramaCard(item: MyDrama) {
Row() {
// 封面占位
Stack() {
Column().width(60).height(80).backgroundColor('#E8E8E8').borderRadius(6)
Text('🎬').fontSize(24)
}.width(60).height(80)
Column() {
Row() {
Text(item.title).fontSize(15).fontWeight(FontWeight.Medium).fontColor('#1A1A2E')
Blank()
Text(item.rating.toString()).fontSize(12)
.fontColor(item.rating >= 48 ? '#E74C3C' : '#F39C12')
}.width('100%')
Text(item.genre).fontSize(12).fontColor('#999999').margin({ top: 4 })
Row() {
Text(item.status).fontSize(11).fontColor(Color.White)
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.backgroundColor(this.getStatusColor(item.status)).borderRadius(6)
Text(`更新至${item.watchedEpisodes}集`).fontSize(11).fontColor('#BBBBBB').margin({ left: 8 })
}.margin({ top: 4 })
// 进度条 + 百分比
Row() {
Progress({ value: item.progress, total: 100, style: ProgressStyle.Linear })
.width('70%').height(4).color('#FF6B35').backgroundColor('#F0F0F0').borderRadius(2)
Text(`${item.progress}%`).fontSize(11).fontColor('#FF6B35').margin({ left: 6 })
}.width('100%').margin({ top: 4 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Start).margin({ left: 10 })
}
.width('100%').padding(12).backgroundColor('#FFFFFF').borderRadius(10).margin({ top: 8 })
.onClick(() => {
router.pushUrl({ url: 'pages/DetailPage', params: { dramaId: item.id } });
})
}
与搜索页卡片的区别:
- 搜索页:显示年份、导演、主演等完整信息
- 我的追剧:突出进度条和已看集数,更关注追剧进度
1.7 空状态设计(重要!)
@Builder buildEmptyState() {
Column() {
Text('📺').fontSize(48)
Text('暂无剧集').fontSize(16).fontColor('#999999').margin({ top: 12 })
Text('去首页或搜索添加你想追的剧吧').fontSize(13).fontColor('#CCCCCC').margin({ top: 8 })
Row() {
Text('去首页').fontSize(14).fontColor(Color.White)
.padding({ left: 24, right: 24, top: 8, bottom: 8 })
.backgroundColor('#FF6B35').borderRadius(18)
.onClick(() => { router.pushUrl({ url: 'pages/Index' }); })
Text('去搜索').fontSize(14).fontColor(Color.White)
.padding({ left: 24, right: 24, top: 8, bottom: 8 })
.backgroundColor('#3498DB').borderRadius(18).margin({ left: 12 })
.onClick(() => { router.pushUrl({ url: 'pages/SearchPage' }); })
}.margin({ top: 20 })
}
.width('100%').padding({ top: 60 }).alignItems(HorizontalAlign.Center)
}
空状态的三要素:
- 图标:友好的Emoji图标,缓解用户"无数据"的失落感
- 文案:说明 + 引导,“暂无剧集"→"去首页或搜索添加你想追的剧吧”
- 操作按钮:提供直接跳转的入口,降低用户操作路径
何时显示空状态:
if (this.getFilteredDramas().length === 0) {
this.buildEmptyState() // 当前Tab没有数据
} else {
// 显示卡片列表
}
这里的"空状态"不是真的没有数据,而是当前Tab没有匹配的剧集。比如"想看"Tab下没有数据时,显示空状态引导用户去搜索。
1.8 快速操作区
@Builder buildStatusActions() {
Column() {
Text('快速操作').fontSize(15).fontWeight(FontWeight.Bold)
.fontColor('#1A1A2E').width('100%').padding({ left: 16, top: 12 })
Row() {
Column() {
Text('📥').fontSize(24); Text('批量导入').fontSize(11).fontColor('#666666').margin({ top: 4 })
}.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column() {
Text('📤').fontSize(24); Text('导出列表').fontSize(11).fontColor('#666666').margin({ top: 4 })
}.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column() {
Text('🔄').fontSize(24); Text('排序').fontSize(11).fontColor('#666666').margin({ top: 4 })
}.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column() {
Text('📊').fontSize(24); Text('统计').fontSize(11).fontColor('#666666').margin({ top: 4 })
}.layoutWeight(1).alignItems(HorizontalAlign.Center)
.onClick(() => { router.pushUrl({ url: 'pages/StatsPage' }); })
}
.width('100%').padding({ left: 16, right: 16, top: 12, bottom: 12 })
}
.width('100%').backgroundColor('#FFFFFF').borderRadius(10)
.margin({ top: 12, left: 16, right: 16 })
}
这个区域使用 Emoji 作为图标,布局均匀分布。目前是静态展示,后续可以对接实际功能。
1.9 主布局
build(): void {
Column() {
this.buildHeader()
Scroll() {
Column() {
if (this.getFilteredDramas().length === 0) {
this.buildEmptyState()
} else {
Text(`共 ${this.getFilteredDramas().length} 部`)
.fontSize(12).fontColor('#999999')
.width('100%').padding({ left: 16, top: 12 })
ForEach(this.getFilteredDramas(), (item: MyDrama) => {
this.buildDramaCard(item)
}, (item: MyDrama) => item.id.toString() + item.myStatus)
}
this.buildStatusActions()
}
.width('100%').padding({ bottom: 20 })
}
.scrollable(ScrollDirection.Vertical)
.layoutWeight(1).width('100%')
.backgroundColor('#F5F5F5')
}
.width('100%').height('100%')
}
ForEach的key设计:
(item: MyDrama) => item.id.toString() + item.myStatus
这里的key不仅包含 id,还拼接了 myStatus。原因:当用户切换Tab时,同一部剧出现在不同Tab中,加上 myStatus 后缀确保key在不同Tab中唯一。如果只用 id,切换Tab时Diff算法可能错误地复用列表项。
二、统计页(StatsPage)完整实现
2.1 页面功能分析
StatsPage
├── 顶部导航栏:返回 + 标题"我的追剧报告" + 分享按钮
├── 统计概览卡片(4个指标):累计追剧 / 总集数 / 总时长 / 完成率
├── 追剧进度总览:环形图 + 在看/看完/想看数量
├── 类型分布图:带进度条的类型占比
├── 月度追剧趋势:柱状图
├── 成就徽章:已解锁/未解锁徽章
└── 底部语录:追剧是一种生活方式
2.2 数据结构
interface StatsItem {
label: string; // 标签名(如"累计追剧")
value: string; // 数值(如"8")
unit: string; // 单位(如"部")
color: ResourceStr; // 颜色
}
interface GenreStat {
name: string; // 类型名
count: number; // 数量
percent: number; // 百分比
color: ResourceStr; // 颜色
}
interface Badge {
icon: string; // 徽章图标
title: string; // 徽章名
desc: string; // 描述
unlocked: boolean; // 是否已解锁
}
interface MonthData {
month: string; // 月份
count: number; // 该月追剧数量
}
2.3 统计数据计算
@Component
struct StatsPage {
@State statsItems: StatsItem[] = [];
@State genreStats: GenreStat[] = [];
@State badges: Badge[] = [];
@State monthData: MonthData[] = [];
@State totalHours: number = 0;
@State completedCount: number = 0;
@State watchingCount: number = 0;
@State totalEpisodes: number = 0;
calculateStats(): void {
this.totalEpisodes = 342;
this.completedCount = 3;
this.watchingCount = 4;
this.totalHours = 256;
this.statsItems = [
{ label: '累计追剧', value: '8', unit: '部', color: '#FF6B35' },
{ label: '总集数', value: this.totalEpisodes.toString(), unit: '集', color: '#3498DB' },
{ label: '总时长', value: this.totalHours.toString(), unit: '小时', color: '#2ECC71' },
{ label: '完成率', value: '37.5', unit: '%', color: '#9B59B6' }
];
const colors: ResourceStr[] = ['#FF6B35', '#3498DB', '#2ECC71', '#E74C3C',
'#F39C12', '#9B59B6', '#1ABC9C', '#E91E63'];
const genreNames: string[] = ['古装', '都市', '悬疑', '青春', '科幻', '年代', '爱情', '其他'];
const counts: number[] = [3, 2, 1, 1, 1, 1, 1, 1];
const total: number = 11;
this.genreStats = [];
for (let i: number = 0; i < genreNames.length; i++) {
this.genreStats.push({
name: genreNames[i],
count: counts[i],
percent: Math.round((counts[i] / total) * 100),
color: colors[i]
});
}
this.badges = [
{ icon: '🏆', title: '追剧达人', desc: '累计追剧超过5部', unlocked: true },
{ icon: '⏰', title: '夜猫子', desc: '凌晨追剧超过10次', unlocked: true },
{ icon: '🎯', title: '全勤王', desc: '完整看完3部剧', unlocked: true },
{ icon: '🔥', title: '追更先锋', desc: '追5部以上连载剧', unlocked: false }
];
this.monthData = [
{ month: '11月', count: 2 },
{ month: '12月', count: 3 },
{ month: '1月', count: 3 }
];
}
}
统计数据的计算方式:
- 当前为静态模拟数据
- 后续可从后端API获取
- 也可在客户端根据追剧列表实时计算
2.4 统计概览卡片
@Builder buildStatsCards() {
Column() {
Row() {
ForEach(this.statsItems, (item: StatsItem, index?: number) => {
Column() {
Text(item.value).fontSize(22).fontWeight(FontWeight.Bold)
.fontColor(item.color as string)
Text(`${item.label}(${item.unit})`).fontSize(11)
.fontColor('#999999').margin({ top: 4 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
.padding({ top: 8, bottom: 8 })
}, (item: StatsItem) => item.label)
}.width('100%')
}
.width('100%').padding(12).backgroundColor('#FFFFFF')
.borderRadius(10).margin({ top: 12, left: 16, right: 16 })
}
四个指标等分宽度,每个指标大号数字 + 小字标签。item.color as string 是类型断言,因为 ResourceStr 在用作fontColor时需要转换。
2.5 追剧进度总览
@Builder buildProgressCard() {
Column() {
Text('追剧进度总览').fontSize(15).fontWeight(FontWeight.Bold)
.fontColor('#1A1A2E').width('100%')
Row() {
// 左侧:仿环形图(使用Stack叠加)
Stack() {
Column().width(80).height(80).borderRadius(40)
.backgroundColor('#F0F0F0')
Column() {
Text('8').fontSize(28).fontWeight(FontWeight.Bold).fontColor('#FF6B35')
Text('部剧').fontSize(11).fontColor('#999999')
}
}.width(80).height(80)
// 右侧:在看/已完结/想看数量
Column() {
Row() {
Column().width(10).height(10).borderRadius(5).backgroundColor('#FF6B35')
Text(` 在看 ${this.watchingCount}部`).fontSize(13).fontColor('#333333')
Blank()
}.width('100%').margin({ top: 6 })
Row() {
Column().width(10).height(10).borderRadius(5).backgroundColor('#2ECC71')
Text(` 已完结 ${this.completedCount}部`).fontSize(13).fontColor('#333333')
Blank()
}.width('100%').margin({ top: 6 })
Row() {
Column().width(10).height(10).borderRadius(5).backgroundColor('#3498DB')
Text(` 想看 1部`).fontSize(13).fontColor('#333333')
Blank()
}.width('100%').margin({ top: 6 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Start).margin({ left: 16 })
}.width('100%').margin({ top: 12 })
}
.width('100%').padding(16).backgroundColor('#FFFFFF').borderRadius(10)
.margin({ top: 12, left: 16, right: 16 }).alignItems(HorizontalAlign.Start)
}
仿环形图实现:
- 80x80的圆形灰色背景
- 中心显示"8部剧"
- 可以用
Circle组件替代Column实现真正环形进度条
右侧图例说明:三个小圆点 + 文字组成图例,分别对应三种状态,颜色与主色调保持一致。
2.6 类型分布条状图
@Builder buildGenreChart() {
Column() {
Text('类型分布').fontSize(15).fontWeight(FontWeight.Bold)
.fontColor('#1A1A2E').width('100%')
ForEach(this.genreStats, (item: GenreStat) => {
Row() {
// 圆点图例
Column().width(10).height(10).borderRadius(5).backgroundColor(item.color as string)
Text(item.name).fontSize(13).fontColor('#333333').margin({ left: 8 }).width(40)
// 进度条
Stack() {
Column().width('100%').height(14).backgroundColor('#F0F0F0').borderRadius(7)
Column().width(`${item.percent}%`).height(14)
.backgroundColor(item.color as string).borderRadius(7)
.alignSelf(ItemAlign.Start) // 从左向右填充
}
.layoutWeight(1).margin({ left: 8, right: 8 })
// 右侧数值
Text(`${item.count}部`).fontSize(12).fontColor('#999999')
.width(30).textAlign(TextAlign.End)
}
.width('100%').margin({ top: 8 })
}, (item: GenreStat) => item.name)
}
.width('100%').padding(16).backgroundColor('#FFFFFF').borderRadius(10)
.margin({ top: 12, left: 16, right: 16 }).alignItems(HorizontalAlign.Start)
}
水平条状图的实现原理:
Stack (100%宽度)
├── Column (100%, #F0F0F0) ← 背景条
└── Column (item.percent%, item.color) ← 前景填充条
└── alignSelf(ItemAlign.Start) 确保从左开始
每一行由四个元素构成:圆点图例 → 类型名 → 进度条 → 数值。八种类型从上到下排列,颜色各不相同,整体形成一个色彩丰富的类型分布图。
2.7 月度追剧趋势(柱状图)
@Builder buildMonthlyChart() {
Column() {
Text('月度追剧趋势').fontSize(15).fontWeight(FontWeight.Bold)
.fontColor('#1A1A2E').width('100%')
Row() {
ForEach(this.monthData, (month: MonthData) => {
Column() {
Text(month.month).fontSize(12).fontColor('#999999')
// 柱状条:高度 = count × 20
Column().width(30).height(month.count * 20)
.backgroundColor('#FF6B35').borderRadius(4).margin({ top: 8 })
Text(`${month.count}部`).fontSize(12).fontColor('#FF6B35')
.fontWeight(FontWeight.Medium).margin({ top: 4 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
}, (month: MonthData) => month.month)
}
.width('100%').height(120)
.alignItems(VerticalAlign.Bottom) // 柱状图从底部对齐
.margin({ top: 12 })
}
.width('100%').padding(16).backgroundColor('#FFFFFF').borderRadius(10)
.margin({ top: 12, left: 16, right: 16 }).alignItems(HorizontalAlign.Start)
}
柱状图实现要点:
alignItems(VerticalAlign.Bottom)让所有柱子底部对齐- 柱子高度
month.count * 20是简单线性映射 - 每个月三行:月份标签 → 柱状条 → 数值标签
这是最简单直观的柱状图实现,实际项目中可以用 Canvas 组件绘制更复杂的图表。
2.8 成就徽章系统
@Builder buildBadgesSection() {
Column() {
Text('成就徽章').fontSize(15).fontWeight(FontWeight.Bold)
.fontColor('#1A1A2E').width('100%')
Row() {
ForEach(this.badges, (badge: Badge) => {
Column() {
Text(badge.icon).fontSize(28)
.grayscale(badge.unlocked ? 0 : 1) // 未解锁变灰
Text(badge.title).fontSize(11)
.fontColor(badge.unlocked ? '#333333' : '#CCCCCC')
.margin({ top: 4 })
Text(badge.desc).fontSize(9)
.fontColor(badge.unlocked ? '#999999' : '#DDDDDD')
.margin({ top: 2 })
.maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
}, (badge: Badge) => badge.title)
}
.width('100%').margin({ top: 12 })
}
.width('100%').padding(16).backgroundColor('#FFFFFF').borderRadius(10)
.margin({ top: 12, left: 16, right: 16 }).alignItems(HorizontalAlign.Start)
}
徽章系统的视觉设计:
- 已解锁:彩色图标 + 黑色标题 + 灰色描述
- 未解锁:灰度图标(
grayscale(1))+ 浅灰标题 + 浅灰描述
grayscale 滤镜:ArkTS的Image和Text组件支持 grayscale 属性,设置1时完全变灰,设置0时恢复原色。这是实现"锁定/解锁"视觉差异的快捷方式。
2.9 底部语录
@Builder buildQuoteSection() {
Column() {
Text('🎬').fontSize(32)
Text('追剧是一种生活方式').fontSize(14).fontColor('#666666').margin({ top: 8 })
Text('每一个故事都是一段旅程').fontSize(12).fontColor('#999999').margin({ top: 4 })
}
.width('100%').padding(20).backgroundColor('#FFFFFF').borderRadius(10)
.margin({ top: 12, left: 16, right: 16 }).alignItems(HorizontalAlign.Center)
}
这个区域增加了一些人文气息,让统计页不是冷冰冰的数据展示,而是一种有温度的记录。
2.10 主布局
build(): void {
Column() {
this.buildHeader()
Scroll() {
Column() {
this.buildStatsCards()
this.buildProgressCard()
this.buildGenreChart()
this.buildMonthlyChart()
this.buildBadgesSection()
this.buildQuoteSection()
}
.width('100%').padding({ bottom: 30 })
}
.scrollable(ScrollDirection.Vertical)
.layoutWeight(1).width('100%')
.backgroundColor('#F5F5F5')
}
.width('100%').height('100%')
}
六个区块垂直排列在一个Scroll中,用户上下滑动浏览全部统计数据。每个区块用白底 + 圆角卡片独立展示,视觉清晰。
三、ArkTS状态管理的更多思考
3.1 @State vs 普通变量
| 特性 | @State | 普通变量 |
|---|---|---|
| 触发UI刷新 | ✅ 是 | ❌ 否 |
| 初始化时机 | aboutToAppear时 | 声明时/构造函数 |
| 适用场景 | UI直接依赖的数据 | 内部计算、常量 |
3.2 数组变更的刷新机制
// ✅ 直接修改数组元素(能触发刷新)
this.myDramas[i].myStatus = '看完';
// ✅ 重新赋值整个数组(能触发刷新)
this.myDramas = [...this.myDramas];
// ❌ 数组方法如 push 可能不触发刷新
this.myDramas.push(newItem); // 部分版本不支持触发刷新
最佳实践:修改数组后重新赋值,确保UI刷新。
3.3 条件渲染的选择
// 方式一:if/else
if (condition) { buildA() } else { buildB() }
// 方式二:visibility
Column().visibility(condition ? Visibility.Visible : Visibility.None)
选择建议:
if/else:组件完全不同,切换时完全重建/销毁visibility:组件结构相同,仅切换显隐- 列表页的空状态建议用
if/else,因为内容差异大
四、设计思路总结
4.1 我的追剧页的设计目标
- 三态管理:帮助用户分类管理所有剧集
- 进度可视化:每个卡片都显示进度条,一目了然
- 操作引导:空状态时主动引导用户去首页或搜索
- 扩展性:快速操作区预留了未来功能扩展位
4.2 统计页的设计目标
- 数据仪表盘:核心数据集中展示,快速了解整体情况
- 多维分析:从数量、时长、类型、月度趋势多角度分析
- 成就驱动:徽章系统激励用户持续使用
- 情感化设计:底部语录让数据更有温度

五、篇末总结
本篇我们完成了最后两个页面,核心内容包括:
- ✅ @State状态驱动的三Tab列表管理
- ✅ 空状态的三要素设计(图标 + 文案 + 操作按钮)
- ✅ ForEach key的唯一性设计
- ✅ 统计页的六个数据区块构建
- ✅ 水平条状图与柱状图的自定义实现
- ✅ 成就徽章系统的grayscale灰度控制
- ✅ @State状态管理的最佳实践
下一篇是本系列的完结篇,将讲解:
- hvigor构建命令的使用
- 严格模式下的常见编译错误
- 代码混淆配置
- 从开发到上架的完整流程
文章索引:
- (一)项目初始化与Stage模型架构设计
- (二)首页开发 —— 周历导航与@Builder组件化实践
- (三)搜索与详情页 —— 多维度筛选与动态路由
- (四)我的追剧与统计页 —— 三态Tab与数据可视化 ← 当前
- (五)编译构建与性能优化 —— 从开发到上架
更多推荐



所有评论(0)