mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-09-12 16:40:40 +08:00
refactor(love-module): 重构恋爱模块页面与组件
1. 统一主题色小写格式,新增恋爱页面渐变背景类 2. 替换旧版相册解锁弹窗为新版弹窗组件 3. 重构相册查看器样式与布局,适配新的玻璃态弹窗 4. 优化恋爱相册与故事页面的UI布局、样式与交互 5. 新增回顶按钮的样式适配,统一使用love主题色
This commit is contained in:
+366
-191
@@ -1,212 +1,387 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
/**
|
||||
* 恋爱清单页(源自旧项目 pagesA/love/list.vue,新建复刻)
|
||||
* 恋爱清单卡片列表(未开始/进行中/已完成),展开查看详情与回忆图片
|
||||
*/
|
||||
import { ref } from 'vue'
|
||||
import { onLoad, onPullDownRefresh } from '@dcloudio/uni-app'
|
||||
import { getLoveDailyItems } from '@/api/uni-halo'
|
||||
import { checkImageUrl } from '@/utils/url'
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
import type { ILoveDailyItem } from '@/api/types/uni-halo'
|
||||
import { computed, ref } from 'vue'
|
||||
import { onLoad, onPullDownRefresh } from '@dcloudio/uni-app'
|
||||
import { getLoveDailyItems } from '@/api/uni-halo'
|
||||
import { checkImageUrl } from '@/utils/url'
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
import type { ILoveDailyItem } from '@/api/types/uni-halo'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '恋爱清单',
|
||||
navigationStyle: 'custom',
|
||||
enablePullDownRefresh: true,
|
||||
},
|
||||
})
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '恋爱清单',
|
||||
navigationStyle: 'custom',
|
||||
enablePullDownRefresh: true,
|
||||
},
|
||||
})
|
||||
|
||||
/* ---------------- 展示层类型 ---------------- */
|
||||
/** 清单展示卡片(script 预处理后的干净展示数据) */
|
||||
interface ILoveItemCard {
|
||||
/** 唯一 key(metadata.name,无则用索引) */
|
||||
name: string
|
||||
title: string
|
||||
content: string
|
||||
status: 'wait' | 'doing' | 'complete'
|
||||
planDate: string
|
||||
completeDate: string
|
||||
completeRemark: string
|
||||
/** 回忆图片(已预处理 URL) */
|
||||
images: string[]
|
||||
/** 是否展开详情 */
|
||||
open: boolean
|
||||
}
|
||||
/* ---------------- 展示层类型 ---------------- */
|
||||
/** 清单展示卡片(script 预处理后的干净展示数据) */
|
||||
interface ILoveItemCard {
|
||||
/** 唯一 key(metadata.name,无则用索引) */
|
||||
name : string
|
||||
title : string
|
||||
content : string
|
||||
status : 'wait' | 'doing' | 'complete'
|
||||
planDate : string
|
||||
completeDate : string
|
||||
completeRemark : string
|
||||
/** 回忆图片(已预处理 URL) */
|
||||
images : string[]
|
||||
/** 是否展开详情 */
|
||||
open : boolean
|
||||
}
|
||||
|
||||
/** 清单卡片映射:字段取值 + 图片路径预处理(模板不感知原始接口结构) */
|
||||
function mapItemCard(item: ILoveDailyItem, index: number): ILoveItemCard {
|
||||
const spec = item.spec || {}
|
||||
return {
|
||||
name: item.metadata?.name || `item-${index}`,
|
||||
title: spec.title || '',
|
||||
content: spec.content || '',
|
||||
status: spec.status || 'wait',
|
||||
planDate: spec.planDate || '',
|
||||
completeDate: spec.completeDate || '',
|
||||
completeRemark: spec.completeRemark || '',
|
||||
images: (spec.images || []).map(img => checkImageUrl(img || '')),
|
||||
open: false,
|
||||
}
|
||||
}
|
||||
/** 清单卡片映射:字段取值 + 图片路径预处理(模板不感知原始接口结构) */
|
||||
function mapItemCard(item : ILoveDailyItem, index : number) : ILoveItemCard {
|
||||
const spec = item.spec || {}
|
||||
return {
|
||||
name: item.metadata?.name || `item-${index}`,
|
||||
title: spec.title || '',
|
||||
content: spec.content || '',
|
||||
status: spec.status || 'wait',
|
||||
planDate: spec.planDate || '',
|
||||
completeDate: spec.completeDate || '',
|
||||
completeRemark: spec.completeRemark || '',
|
||||
images: (spec.images || []).map(img => checkImageUrl(img || '')),
|
||||
open: false,
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 状态 ---------------- */
|
||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||
const list = ref<ILoveItemCard[]>([])
|
||||
/* ---------------- 状态 ---------------- */
|
||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||
const list = ref<ILoveItemCard[]>([])
|
||||
|
||||
/* ---------------- 数据加载 ---------------- */
|
||||
async function handleGetList() {
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||
try {
|
||||
const res = await getLoveDailyItems({})
|
||||
const items = res.data?.items || []
|
||||
list.value = items.map(mapItemCard)
|
||||
updateLoadingStatus(list.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success)
|
||||
}
|
||||
catch (e) {
|
||||
console.error('获取清单失败', e)
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
||||
}
|
||||
finally {
|
||||
setTimeout(() => {
|
||||
uni.stopPullDownRefresh()
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
/* ---------------- 筛选与排序 ---------------- */
|
||||
interface IFilterOption {
|
||||
label : string
|
||||
value : string
|
||||
}
|
||||
|
||||
/* ---------------- 交互 ---------------- */
|
||||
function handleOnItemOpen(item: ILoveItemCard) {
|
||||
item.open = !item.open
|
||||
}
|
||||
interface IFilterItem {
|
||||
key : 'status' | 'sort'
|
||||
label : string
|
||||
options : IFilterOption[]
|
||||
}
|
||||
|
||||
/** 预览回忆图片(基于已预处理 URL) */
|
||||
function handlePreviewImages(images: string[], index: number) {
|
||||
if (images.length === 0)
|
||||
return
|
||||
uni.previewImage({ current: images[index], urls: images })
|
||||
}
|
||||
/** 筛选维度:状态筛选 + 排序(参考投票列表页顶部胶囊设计,各自独立状态) */
|
||||
const filterConfig : IFilterItem[] = [
|
||||
{
|
||||
key: 'status',
|
||||
label: '状态',
|
||||
options: [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '待完成', value: 'wait' },
|
||||
{ label: '进行中', value: 'doing' },
|
||||
{ label: '已完成', value: 'complete' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'sort',
|
||||
label: '排序',
|
||||
options: [
|
||||
{ label: '默认排序', value: 'default' },
|
||||
{ label: '按状态', value: 'status' },
|
||||
{ label: '按计划时间', value: 'planDate' },
|
||||
{ label: '按完成时间', value: 'completeDate' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
function handleToTopPage(duration = 500) {
|
||||
uni.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration,
|
||||
fail: (err) => {
|
||||
console.error('回顶失败', err)
|
||||
},
|
||||
})
|
||||
}
|
||||
/** 排序方向选项(顺序/倒序,排序弹层内选择) */
|
||||
const sortDirOptions : IFilterOption[] = [
|
||||
{ label: '顺序', value: 'asc' },
|
||||
{ label: '倒序', value: 'desc' },
|
||||
]
|
||||
|
||||
/* ---------------- 生命周期 ---------------- */
|
||||
onLoad(() => {
|
||||
handleGetList()
|
||||
})
|
||||
/** 各维度当前选中值(空串/首项 = 默认) */
|
||||
const filterValues = ref<Record<string, string>>({ status: '', sort: 'default', sortDir: 'asc' })
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
handleGetList()
|
||||
})
|
||||
/** 当前选中中文标签(用于胶囊展示当前状态) */
|
||||
const filterLabels = computed(() => {
|
||||
const map : Record<string, string> = {}
|
||||
for (const f of filterConfig) {
|
||||
const cur = filterValues.value[f.key]
|
||||
map[f.key] = f.options.find(o => o.value === cur)?.label || f.options[0].label
|
||||
}
|
||||
return map
|
||||
})
|
||||
|
||||
/** 筛选弹层 */
|
||||
const filterPopup = ref<{ show : boolean, item : IFilterItem | null }>({ show: false, item: null })
|
||||
|
||||
function handleOpenFilter(item : IFilterItem) {
|
||||
filterPopup.value = { show: true, item }
|
||||
}
|
||||
|
||||
function handleSelectFilter(option : IFilterOption) {
|
||||
const item = filterPopup.value.item
|
||||
if (!item)
|
||||
return
|
||||
if (item.key === 'sort' && option.value === 'default') {
|
||||
// 选回默认排序:方向一并复位
|
||||
filterValues.value.sort = 'default'
|
||||
filterValues.value.sortDir = 'asc'
|
||||
}
|
||||
else {
|
||||
filterValues.value[item.key] = option.value
|
||||
}
|
||||
filterPopup.value.show = false
|
||||
}
|
||||
|
||||
function handleSelectSortDir(option : IFilterOption) {
|
||||
filterValues.value.sortDir = option.value
|
||||
}
|
||||
|
||||
/** 展示列表:状态筛选 + 排序(前端过滤,数据一次拉取) */
|
||||
const showList = computed(() => {
|
||||
const status = filterValues.value.status
|
||||
const dir = filterValues.value.sortDir === 'desc' ? -1 : 1
|
||||
const result = status
|
||||
? list.value.filter(item => item.status === status)
|
||||
: [...list.value]
|
||||
switch (filterValues.value.sort) {
|
||||
case 'status': {
|
||||
const order : Record<string, number> = { wait: 0, doing: 1, complete: 2 }
|
||||
result.sort((a, b) => (order[a.status] - order[b.status]) * dir)
|
||||
break
|
||||
}
|
||||
case 'planDate':
|
||||
result.sort((a, b) => (a.planDate || '').localeCompare(b.planDate || '') * dir)
|
||||
break
|
||||
case 'completeDate':
|
||||
result.sort((a, b) => (a.completeDate || '').localeCompare(b.completeDate || '') * dir)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
/* ---------------- 数据加载 ---------------- */
|
||||
async function handleGetList() {
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||
try {
|
||||
const res = await getLoveDailyItems({})
|
||||
const items = res.data?.items || []
|
||||
list.value = items.map(mapItemCard)
|
||||
updateLoadingStatus(list.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success)
|
||||
}
|
||||
catch (e) {
|
||||
console.error('获取清单失败', e)
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
||||
}
|
||||
finally {
|
||||
setTimeout(() => {
|
||||
uni.stopPullDownRefresh()
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
|
||||
function getCardStatusClass(item : ILoveItemCard) {
|
||||
switch (item.status) {
|
||||
case 'wait':
|
||||
return 'bg-yellow-300'
|
||||
case 'doing':
|
||||
return 'bg-love/30'
|
||||
case 'complete':
|
||||
return 'bg-blue-300'
|
||||
default:
|
||||
return 'bg-yellow-300'
|
||||
}
|
||||
}
|
||||
|
||||
function getCardStatusText(item : ILoveItemCard) {
|
||||
switch (item.status) {
|
||||
case 'wait':
|
||||
return '待完成'
|
||||
case 'doing':
|
||||
return '进行中'
|
||||
case 'complete':
|
||||
return '已完成'
|
||||
default:
|
||||
return '待完成'
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 交互 ---------------- */
|
||||
function handleOnItemOpen(item : ILoveItemCard) {
|
||||
item.open = !item.open
|
||||
}
|
||||
|
||||
/** 预览回忆图片(基于已预处理 URL) */
|
||||
function handlePreviewImages(images : string[], index : number) {
|
||||
if (images.length === 0)
|
||||
return
|
||||
uni.previewImage({ current: images[index], urls: images })
|
||||
}
|
||||
|
||||
function handleToTopPage(duration = 500) {
|
||||
uni.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration,
|
||||
fail: (err) => {
|
||||
console.error('回顶失败', err)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/* ---------------- 生命周期 ---------------- */
|
||||
onLoad(() => {
|
||||
handleGetList()
|
||||
})
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
handleGetList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="app-page box-border min-h-screen w-screen flex flex-col" style="background: linear-gradient(135deg, rgb(247 149 51 / 10%), rgb(243 112 85 / 10%) 15%, rgb(239 78 123 / 10%) 30%, rgb(161 102 171 / 10%) 44%, rgb(80 115 184 / 10%) 58%, rgb(16 152 173 / 10%) 72%, rgb(7 179 155 / 10%) 86%, rgb(109 186 130 / 10%));">
|
||||
<!-- 自定义导航 -->
|
||||
<uh-navbar default-title="恋爱清单" title-color="text-gray-900" />
|
||||
<view class="uh-global-love-page box-border min-h-screen w-screen flex flex-col">
|
||||
<!-- 自定义导航 -->
|
||||
<uh-navbar default-title="恋爱清单" title-color="text-love" back-class="text-love"/>
|
||||
|
||||
<!-- 加载/错误/空占位(状态机) -->
|
||||
<uh-data-loading
|
||||
v-if="loadingStatus !== DataLoadingStatusEnum.Success"
|
||||
:loading-status="loadingStatus"
|
||||
min-height="60vh"
|
||||
empty-text="暂时还没有恋爱清单,快去制定你们的恋爱清单吧~"
|
||||
@refresh="handleGetList"
|
||||
/>
|
||||
<!-- 粘性筛选区:参考投票页顶部胶囊设计,每个维度独立状态 -->
|
||||
<wd-sticky>
|
||||
<view class="box-border px-3 pb-1 pt-2">
|
||||
<view class="box-border flex items-center justify-between gap-x-2">
|
||||
<view v-for="f in filterConfig" :key="f.key"
|
||||
class="uh-global-card-glass box-border flex flex-1 items-center justify-center gap-1 border rounded-full px-4 py-2 text-gray-500"
|
||||
:class="[filterValues[f.key] !== f.options[0].value ? 'bg-love/90 text-white font-bold' : 'bg-white/80 text-gray-600']"
|
||||
@click="handleOpenFilter(f)">
|
||||
<text class="truncate text-xs">{{ filterLabels[f.key] }}</text>
|
||||
<template v-if="f.key!=='status'">
|
||||
<wd-icon v-if="f.key === 'sort' && filterValues.sort !== 'default'"
|
||||
:name="filterValues.sortDir === 'desc' ? 'arrow-down' : 'arrow-up'" size="26rpx" />
|
||||
<wd-icon v-else name="arrow-down" size="26rpx" />
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</wd-sticky>
|
||||
|
||||
<!-- 清单列表 -->
|
||||
<view v-else class="list-wrap box-border flex-1 p-6 pb-[144rpx]">
|
||||
<view class="list-tip mb-7 w-full text-center text-[26rpx] text-[#999]">
|
||||
看看我们的恋爱清单都完成了哪些吧
|
||||
</view>
|
||||
<block v-for="item in list" :key="item.name">
|
||||
<view class="card mb-6 box-border w-full flex flex-col items-center rounded-3xl bg-white p-6 shadow-sm">
|
||||
<view class="head box-border w-full flex items-center" @click="handleOnItemOpen(item)">
|
||||
<view class="status w-[100rpx] flex">
|
||||
<view v-if="item.status === 'wait'" class="text h-[100rpx] w-[100rpx] rounded-full text-center text-[24rpx] leading-[100rpx]" style="background-color: #ffc6ba; color: #55423b;">
|
||||
未开始
|
||||
</view>
|
||||
<view v-else-if="item.status === 'doing'" class="text doing h-[100rpx] w-[100rpx] rounded-full text-center text-[24rpx] leading-[100rpx]" style="background-color: #ffe9a8; color: #55423b;">
|
||||
进行中
|
||||
</view>
|
||||
<view v-else class="text finish h-[100rpx] w-[100rpx] rounded-full text-center text-[24rpx] leading-[100rpx]" style="background-color: #bfe9ef; color: #55423b;">
|
||||
已完成
|
||||
</view>
|
||||
</view>
|
||||
<view class="title box-border w-0 flex-1 px-7">
|
||||
<view class="title-name text-[30rpx] text-[#333] font-bold">
|
||||
{{ item.title }}
|
||||
</view>
|
||||
<view v-if="item.content" class="title-desc mt-1 overflow-hidden text-ellipsis whitespace-nowrap text-[26rpx] text-[#555]">
|
||||
{{ item.content }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="actions w-[50rpx]">
|
||||
<text class="icon inline-block h-[45rpx] w-[45rpx] rounded-full bg-black/20 text-center text-[32rpx] font-bold leading-[45rpx]">{{ item.open ? '-' : '+' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="item.open" class="body mt-6 box-border w-full rounded-3xl bg-black/5 px-6 pb-3 pt-6 text-[26rpx]">
|
||||
<view v-if="item.planDate" class="desc mb-3 flex">
|
||||
<view class="desc-label w-[140rpx] shrink-0 text-[#333]">
|
||||
计划时间
|
||||
</view>
|
||||
<view class="desc-value w-0 flex-1 text-[#333] leading-[1.5]">
|
||||
{{ item.planDate || '-' }}
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="item.completeDate" class="desc mb-3 flex">
|
||||
<view class="desc-label w-[140rpx] shrink-0 text-[#333]">
|
||||
完成时间
|
||||
</view>
|
||||
<view class="desc-value w-0 flex-1 text-[#333] leading-[1.5]">
|
||||
{{ item.completeDate || '-' }}
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="item.completeRemark" class="desc mb-3 flex">
|
||||
<view class="desc-label w-[140rpx] shrink-0 text-[#333]">
|
||||
完成感想
|
||||
</view>
|
||||
<view class="desc-value w-0 flex-1 text-[#333] leading-[1.5]">
|
||||
{{ item.completeRemark || '-' }}
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="item.images.length > 0" class="desc mb-3 flex">
|
||||
<view class="desc-label w-[140rpx] shrink-0 text-[#333]">
|
||||
回忆图片
|
||||
</view>
|
||||
<view class="desc-value w-0 flex-1 text-[#333] leading-[1.5]">
|
||||
<view class="images flex flex-wrap">
|
||||
<view
|
||||
v-for="(img, imgIndex) in item.images"
|
||||
:key="imgIndex"
|
||||
class="image mb-3 mr-3 h-[180rpx] w-[calc((100%-24rpx)/3)] overflow-hidden rounded-lg"
|
||||
@click="handlePreviewImages(item.images, imgIndex)"
|
||||
>
|
||||
<image class="image-src h-full w-full" :src="img" mode="aspectFill" lazy-load />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
<!-- 加载/错误/空占位(状态机) -->
|
||||
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus"
|
||||
min-height="60vh" empty-text="暂时还没有恋爱清单,快去制定你们的恋爱清单吧~" @refresh="handleGetList" />
|
||||
|
||||
<view class="to-top-btn fixed bottom-[160rpx] right-6 z-6 h-[72rpx] w-[72rpx] flex items-center justify-center rounded-full bg-white shadow-sm" @click="handleToTopPage()">
|
||||
<wd-icon name="arrow-up" size="20px" color="#03a9f4" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<!-- 清单列表 -->
|
||||
<view v-else class="box-border flex flex-1 flex-col gap-y-3 p-3 pb-safe">
|
||||
<view
|
||||
class="uh-global-card-glass uh-shadow-xs box-border w-full rounded-xl p-3 text-center text-xs text-love">
|
||||
看看我们的恋爱清单都完成了哪些吧
|
||||
</view>
|
||||
<block v-for="(item, index) in showList" :key="item.name">
|
||||
<view
|
||||
class="uh-global-card-glass uh-shadow-xs box-border w-full flex flex-col items-center rounded-xl p-3">
|
||||
<view class="box-border w-full flex items-center gap-x-3" @click="handleOnItemOpen(item)">
|
||||
<view
|
||||
class="uh-global-card-glass uh-shadow-xs text-md h-11 w-11 flex shrink-0 items-center justify-center rounded-full text-white font-bold"
|
||||
:class="[getCardStatusClass(item)]">
|
||||
{{ index + 1 }}
|
||||
</view>
|
||||
<view class="box-border flex-1">
|
||||
<view class="text-md truncate text-love font-bold">
|
||||
{{ item.title }}
|
||||
</view>
|
||||
<view class="mt-1 text-xs text-gray-500">
|
||||
完成状态:{{ getCardStatusText(item) }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="h-6 w-6 flex shrink-0 items-center justify-center rounded-full text-love font-bold">
|
||||
<wd-icon :name="item.open ? 'up' : 'down'" size="32rpx" />
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="item.open"
|
||||
class="uh-global-card-glass mt-4 box-border w-full rounded-xl p-3 text-xs shadow-none">
|
||||
<view v-if="item.content" class="desc mb-3 flex">
|
||||
<view class="desc-label w-16 shrink-0 text-gray-500">
|
||||
计划内容
|
||||
</view>
|
||||
<view class="desc-value w-0 flex-1 text-gray-900 leading-4">
|
||||
{{ item.content || '-' }}
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="item.planDate" class="desc mb-3 flex">
|
||||
<view class="desc-label w-16 shrink-0 text-gray-500">
|
||||
计划时间
|
||||
</view>
|
||||
<view class="desc-value w-0 flex-1 text-gray-900 leading-4">
|
||||
{{ item.planDate || '-' }}
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="item.completeDate" class="desc mb-3 flex">
|
||||
<view class="desc-label w-16 shrink-0 text-gray-500">
|
||||
完成时间
|
||||
</view>
|
||||
<view class="desc-value w-0 flex-1 text-gray-900 leading-4">
|
||||
{{ item.completeDate || '-' }}
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="item.completeRemark" class="desc mb-3 flex">
|
||||
<view class="desc-label w-16 shrink-0 text-gray-500">
|
||||
完成感想
|
||||
</view>
|
||||
<view class="desc-value w-0 flex-1 text-gray-900 leading-4">
|
||||
{{ item.completeRemark || '-' }}
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="item.images.length > 0" class="desc flex">
|
||||
<view class="desc-label w-16 shrink-0 text-gray-500">
|
||||
回忆图片
|
||||
</view>
|
||||
<view class="desc-value w-0 flex-1 text-gray-900 leading-4">
|
||||
<view class="grid grid-cols-3 gap-2">
|
||||
<view v-for="(img, imgIndex) in item.images" :key="imgIndex"
|
||||
class="h-16 w-full overflow-hidden rounded-lg"
|
||||
@click="handlePreviewImages(item.images, imgIndex)">
|
||||
<image class="h-full w-full" :src="img" mode="aspectFill" lazy-load />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<view class="box-border py-10 text-center text-xs text-gray-500">
|
||||
|
||||
<style scoped>
|
||||
.app-page {
|
||||
/* 布局全部由 UnoCSS 原子类实现 */
|
||||
}
|
||||
</style>
|
||||
</view>
|
||||
<uh-data-loading v-if="showList.length === 0" :loading-status="DataLoadingStatusEnum.Empty"
|
||||
min-height="42vh" empty-text="该筛选条件下暂无清单~" :use-loading-button="false" />
|
||||
</view>
|
||||
|
||||
<!-- 筛选弹层(状态/排序;排序附方向选择) -->
|
||||
<uh-glass-popup v-model="filterPopup.show" :z-index="99" position="bottom" custom-class="rounded-2xl">
|
||||
<view v-if="filterPopup.item" class="box-border p-4">
|
||||
<view class="text-md mb-4 text-center text-gray-900 font-bold">
|
||||
{{ filterPopup.item.label }}
|
||||
</view>
|
||||
<view class="flex flex-col gap-2">
|
||||
<view v-for="opt in filterPopup.item.options" :key="opt.label"
|
||||
class="uh-global-card-glass shadow-none box-border border rounded-xl px-5 py-2 text-center text-sm"
|
||||
:class="filterValues[filterPopup.item.key] === opt.value ? 'bg-love/90 text-white font-bold' : 'text-gray-700'"
|
||||
@click="handleSelectFilter(opt)">
|
||||
{{ opt.label }}
|
||||
</view>
|
||||
</view>
|
||||
<!-- 排序维度附加:方向选择(顺序/倒序) -->
|
||||
<template v-if="filterPopup.item.key === 'sort'">
|
||||
<view class="mb-2 mt-5 text-center text-xs text-gray-400">
|
||||
排序方向
|
||||
</view>
|
||||
<view class="flex gap-2">
|
||||
<view v-for="dir in sortDirOptions" :key="dir.value"
|
||||
class="uh-global-card-glass shadow-none box-border flex-1 border rounded-xl px-5 py-2 text-center text-sm"
|
||||
:class="filterValues.sortDir === dir.value ? 'bg-love/90 text-white font-bold' : 'text-gray-700'"
|
||||
@click="handleSelectSortDir(dir)">
|
||||
{{ dir.label }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</uh-glass-popup>
|
||||
</view>
|
||||
</template>
|
||||
Reference in New Issue
Block a user