mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-09-12 16:40:40 +08:00
refactor: 完成项目多模块优化与功能迭代
本次提交包含多项优化与功能更新: 1. 关闭本地开发模式,准备正式发布 2. 重构空状态按钮,统一使用刷新按钮替代加载按钮 3. 优化导航栏样式与返回按钮细节 4. 为图库页面添加图片标题展示与阴影优化 5. 关闭相册接口缓存,确保数据实时性 6. 重构相册查看组件,改为内部自动请求数据并优化布局 7. 优化投票页面样式与配色 8. 重构搜索页面代码结构与细节样式 9. 优化恋爱相册页面布局与交互体验
This commit is contained in:
+3
-5
@@ -1,10 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* uni-halo 自定义 API 接口定义
|
* uni-halo 自定义 API 接口定义
|
||||||
* 覆盖 plugin-uni-halo(/apis/api.unihalo.ialley.cn)与三方插件接口:
|
|
||||||
* 受限阅读(tools.muyin.site)、友链提交(linkssubmit.muyin.site)、投票(api.vote.kunkunyu.com)、
|
|
||||||
* 豆瓣(api.douban.moony.la)、评论组件(api.commentwidget.halo.run)
|
|
||||||
* 风格参考 src/api/foo-alova.ts:http.Get<IResponse<T>>(url, { params, header, meta })
|
|
||||||
* 源自旧项目 api/v2/all.config.js、all.api.js(三方部分)、love.*.js,按需命名导出
|
|
||||||
*/
|
*/
|
||||||
import { http } from '@/http/alova'
|
import { http } from '@/http/alova'
|
||||||
import { RequestFrom } from '@/http/tools/enum'
|
import { RequestFrom } from '@/http/tools/enum'
|
||||||
@@ -211,6 +206,7 @@ export function getLoveConfig() {
|
|||||||
export function getLoveAlbums(params: ILoveAlbumListReq) {
|
export function getLoveAlbums(params: ILoveAlbumListReq) {
|
||||||
return http.Get<IResponse<ILoveAlbumListRes>>('/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/love-albums', {
|
return http.Get<IResponse<ILoveAlbumListRes>>('/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/love-albums', {
|
||||||
params,
|
params,
|
||||||
|
cacheFor: 0,
|
||||||
meta: { requestFrom: RequestFrom.Halo },
|
meta: { requestFrom: RequestFrom.Halo },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -221,6 +217,7 @@ export function getLoveAlbums(params: ILoveAlbumListReq) {
|
|||||||
export function getLoveAlbumByName(name: string, params: ILoveAlbumListReq) {
|
export function getLoveAlbumByName(name: string, params: ILoveAlbumListReq) {
|
||||||
return http.Get<IResponse<ILoveAlbum>>(`/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/love-albums/${name}`, {
|
return http.Get<IResponse<ILoveAlbum>>(`/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/love-albums/${name}`, {
|
||||||
params,
|
params,
|
||||||
|
cacheFor: 0,
|
||||||
meta: { requestFrom: RequestFrom.Halo },
|
meta: { requestFrom: RequestFrom.Halo },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -249,6 +246,7 @@ export function getLoveDailyItems(params: ILoveDailyItemListReq) {
|
|||||||
'/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/love-daily-items',
|
'/apis/api.unihalo.ialley.cn/v1alpha1/plugins/plugin-uni-halo/love-daily-items',
|
||||||
{
|
{
|
||||||
params,
|
params,
|
||||||
|
cacheFor: 0,
|
||||||
meta: { requestFrom: RequestFrom.Halo },
|
meta: { requestFrom: RequestFrom.Halo },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
|
import { getLoveAlbumByName } from '@/api/uni-halo'
|
||||||
import { checkImageUrl } from '@/utils/url'
|
import { checkImageUrl } from '@/utils/url'
|
||||||
|
import { sleep } from '@/utils/common'
|
||||||
|
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||||
|
import type { ILoveAlbum } from '@/api/types/uni-halo'
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
show : boolean
|
show : boolean
|
||||||
albumName ?: string
|
albumName ?: string
|
||||||
photos ?: IAlbumPhoto[]
|
/** 相册 key(详情请求参数) */
|
||||||
loading ?: boolean
|
albumKey ?: string
|
||||||
|
/** 解锁 token(加密相册已解锁时传入) */
|
||||||
|
token ?: string
|
||||||
}>(), {
|
}>(), {
|
||||||
albumName: '',
|
albumName: '',
|
||||||
photos: () => [],
|
albumKey: '',
|
||||||
loading: false,
|
token: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -28,24 +34,39 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isShow = ref(false)
|
const isShow = ref(false)
|
||||||
|
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||||
watch(() => props.show, (val) => {
|
const photos = ref<IAlbumPhoto[]>([])
|
||||||
isShow.value = val
|
|
||||||
})
|
|
||||||
|
|
||||||
/** 预处理图片路径(相对路径拼接 BASE_API) */
|
/** 预处理图片路径(相对路径拼接 BASE_API) */
|
||||||
const photoList = computed<IAlbumPhoto[]>(() =>
|
const photoList = computed<IAlbumPhoto[]>(() =>
|
||||||
(props.photos || []).map(photo => ({
|
photos.value.map(photo => ({
|
||||||
...photo,
|
...photo,
|
||||||
url: checkImageUrl(photo.url || ''),
|
url: checkImageUrl(photo.url || ''),
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 左列(偶数位照片,瀑布流错落) */
|
/** 内部自请求:获取相册详情并填充照片 */
|
||||||
const leftPhotos = computed(() => photoList.value.filter((_, index) => index % 2 === 0))
|
async function handleLoadPhotos() {
|
||||||
|
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||||
|
try {
|
||||||
|
const detail = await getLoveAlbumByName(props.albumKey, { token: props.token })
|
||||||
|
photos.value = (detail.data as unknown as ILoveAlbum)?.photos || []
|
||||||
|
await sleep(600)
|
||||||
|
updateLoadingStatus(photos.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success)
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error('获取相册照片失败', e)
|
||||||
|
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 右列(奇数位照片) */
|
watch(() => props.show, (val) => {
|
||||||
const rightPhotos = computed(() => photoList.value.filter((_, index) => index % 2 === 1))
|
isShow.value = val
|
||||||
|
if (val) {
|
||||||
|
photos.value = []
|
||||||
|
handleLoadPhotos()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
isShow.value = false
|
isShow.value = false
|
||||||
@@ -71,88 +92,61 @@
|
|||||||
safe-area-inset-bottom @close="handleClose">
|
safe-area-inset-bottom @close="handleClose">
|
||||||
<view class="box-border h-full w-full flex flex-col gap-y-3 p-4">
|
<view class="box-border h-full w-full flex flex-col gap-y-3 p-4">
|
||||||
<!-- 头部 -->
|
<!-- 头部 -->
|
||||||
<view class="w-full shrink-0 flex items-center justify-between">
|
<view class="w-full flex shrink-0 items-center justify-between">
|
||||||
<view class="font-bold flex items-center gap-x-1">
|
<view class="flex items-center gap-x-1 font-bold">
|
||||||
{{ albumName }}
|
{{ albumName }}
|
||||||
</view>
|
</view>
|
||||||
<view
|
<view
|
||||||
class="w-6 h-6 uh-global-card-glass border uh-shadow-xs flex items-center justify-center rounded-lg"
|
class="uh-global-card-glass uh-shadow-xs h-6 w-6 flex items-center justify-center border rounded-lg"
|
||||||
@click="handleClose">
|
@click="handleClose">
|
||||||
<wd-icon name="close" size="32rpx"></wd-icon>
|
<wd-icon name="close" size="32rpx" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 照片列表 -->
|
<!-- 照片列表 -->
|
||||||
<scroll-view class="box-border max-h-[50vh] flex-1" scroll-y :show-scrollbar="false">
|
<scroll-view class="box-border max-h-[50vh] flex-1" scroll-y :show-scrollbar="false">
|
||||||
<view v-if="loading" class="viewer-empty box-border h-full flex items-center justify-center p-10">
|
<!-- 加载/错误/空占位(状态机) -->
|
||||||
<view class="viewer-loading flex flex-col items-center">
|
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :use-refresh-button="false"
|
||||||
<view class="loading-text mt-7 text-[28rpx] text-[#56bbf9]">
|
:loading-status="loadingStatus" error-text="照片加载失败,请点击刷新重试" empty-text="这个相册暂时还没有照片~"
|
||||||
照片正在努力加载中啦~
|
min-height="40vh" @refresh="handleLoadPhotos" />
|
||||||
</view>
|
<view v-else class="box-border grid grid-cols-2 gap-2">
|
||||||
</view>
|
<view v-for="photo in photoList" :key="photo.name"
|
||||||
</view>
|
class="relative box-border overflow-hidden uh-global-card-glass rounded-xl">
|
||||||
<view v-else-if="photoList.length === 0"
|
<image class="w-full h-46 block" :src="photo.url" mode="aspectFill" lazy-load
|
||||||
class="viewer-empty box-border h-full flex items-center justify-center p-10">
|
@click="handlePreview(photo.url)" />
|
||||||
<wd-empty description="这个相册暂时还没有照片~" />
|
<view
|
||||||
</view>
|
class="absolute bottom-0 w-full box-border p-3 pt-4 z-2 bg-gradient-to-b from-white/0 to-white/60">
|
||||||
<view v-else class="photo-list box-border flex items-start p-5">
|
<view v-if="photo.title" class="mb-1 text-xs text-love font-bold">
|
||||||
<!-- 左列 -->
|
{{ photo.title }}
|
||||||
<view class="photo-column box-border min-w-0 flex-1 mr-[20rpx]">
|
|
||||||
<view v-for="photo in leftPhotos" :key="photo.name"
|
|
||||||
class="photo-card mb-6 box-border overflow-hidden rounded-xl bg-white shadow-sm">
|
|
||||||
<image class="photo-image w-full" :src="photo.url" mode="widthFix" lazy-load
|
|
||||||
@click="handlePreview(photo.url)" />
|
|
||||||
<view class="photo-info box-border px-6 py-5">
|
|
||||||
<view v-if="photo.title" class="photo-title mb-3 text-[30rpx] text-[#333] font-bold">
|
|
||||||
{{ photo.title }}
|
|
||||||
</view>
|
|
||||||
<view v-if="photo.takenDate || photo.location"
|
|
||||||
class="photo-meta mb-3 flex flex-wrap items-center">
|
|
||||||
<text v-if="photo.takenDate"
|
|
||||||
class="meta-item mr-8 text-[24rpx] text-[#999]">{{ photo.takenDate }}</text>
|
|
||||||
<text v-if="photo.location"
|
|
||||||
class="meta-item mr-8 text-[24rpx] text-[#999]">{{ photo.location }}</text>
|
|
||||||
</view>
|
|
||||||
<view v-if="photo.description"
|
|
||||||
class="photo-desc text-[26rpx] text-[#666] leading-[1.6]">
|
|
||||||
{{ photo.description }}
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
<view v-if="photo.description" class="mb-1 text-xs text-white leading-5">
|
||||||
</view>
|
{{ photo.description }}
|
||||||
<!-- 右列 -->
|
|
||||||
<view class="photo-column box-border min-w-0 flex-1">
|
|
||||||
<view v-for="photo in rightPhotos" :key="photo.name"
|
|
||||||
class="photo-card mb-6 box-border overflow-hidden rounded-xl bg-white shadow-sm">
|
|
||||||
<image class="photo-image w-full" :src="photo.url" mode="widthFix" lazy-load
|
|
||||||
@click="handlePreview(photo.url)" />
|
|
||||||
<view class="photo-info box-border px-6 py-5">
|
|
||||||
<view v-if="photo.title" class="photo-title mb-3 text-[30rpx] text-[#333] font-bold">
|
|
||||||
{{ photo.title }}
|
|
||||||
</view>
|
|
||||||
<view v-if="photo.takenDate || photo.location"
|
|
||||||
class="photo-meta mb-3 flex flex-wrap items-center">
|
|
||||||
<text v-if="photo.takenDate"
|
|
||||||
class="meta-item mr-8 text-[24rpx] text-[#999]">{{ photo.takenDate }}</text>
|
|
||||||
<text v-if="photo.location"
|
|
||||||
class="meta-item mr-8 text-[24rpx] text-[#999]">{{ photo.location }}</text>
|
|
||||||
</view>
|
|
||||||
<view v-if="photo.description"
|
|
||||||
class="photo-desc text-[26rpx] text-[#666] leading-[1.6]">
|
|
||||||
{{ photo.description }}
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
<view v-if="photo.takenDate || photo.location"
|
||||||
|
class="flex flex-col gap-y-1">
|
||||||
|
<text v-if="photo.takenDate" class="text-xs text-white">
|
||||||
|
<wd-icon name="time-line"></wd-icon> {{ photo.takenDate }}
|
||||||
|
</text>
|
||||||
|
<text v-if="photo.location" class="text-xs text-white">
|
||||||
|
<wd-icon name="location"></wd-icon> {{ photo.location }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
<!-- 底部关闭 -->
|
<!-- 底部关闭 -->
|
||||||
<view class="w-full shrink-0">
|
<view class="w-full shrink-0 flex items-center justify-center gap-x-2">
|
||||||
<uh-button custom-class="py-2 uh-global-card-glass rounded-xl !bg-love/90 text-white border"
|
<uh-button custom-class="py-2 flex-1 uh-global-card-glass border rounded-xl bg-white/90"
|
||||||
@click="handleClose">
|
@click="handleClose">
|
||||||
关闭
|
关闭
|
||||||
</uh-button>
|
</uh-button>
|
||||||
|
<uh-button custom-class="flex-1 py-2 uh-global-card-glass rounded-xl !bg-love/90 text-white border"
|
||||||
|
@click="handleLoadPhotos">
|
||||||
|
刷新
|
||||||
|
</uh-button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</uh-glass-popup>
|
</uh-glass-popup>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
loadingSubText ?: string
|
loadingSubText ?: string
|
||||||
errorSubText ?: string
|
errorSubText ?: string
|
||||||
emptySubText ?: string
|
emptySubText ?: string
|
||||||
useLoadingButton ?: boolean
|
useRefreshButton ?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<IProps>(), {
|
const props = withDefaults(defineProps<IProps>(), {
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
loadingSubText: '',
|
loadingSubText: '',
|
||||||
errorSubText: '请检查网络连接,或稍后再试',
|
errorSubText: '请检查网络连接,或稍后再试',
|
||||||
emptySubText: '稍后再来看看吧~',
|
emptySubText: '稍后再来看看吧~',
|
||||||
useLoadingButton: true,
|
useRefreshButton: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{ (e : 'refresh') : void }>()
|
const emit = defineEmits<{ (e : 'refresh') : void }>()
|
||||||
@@ -106,8 +106,8 @@
|
|||||||
<text v-if="statusScene.subText" class="mt-3 text-xs text-gray-500">
|
<text v-if="statusScene.subText" class="mt-3 text-xs text-gray-500">
|
||||||
{{ statusScene.subText }}
|
{{ statusScene.subText }}
|
||||||
</text>
|
</text>
|
||||||
<view v-if="props.useLoadingButton" class="mt-5 uh-global-card-glass uh-shadow-xs border rounded-lg">
|
<view v-if="props.useRefreshButton" class="mt-5">
|
||||||
<uh-button custom-class="!rounded-lg" @click="emit('refresh')">
|
<uh-button custom-class="!rounded-lg uh-global-card-glass uh-shadow-xs border rounded-lg" @click="emit('refresh')">
|
||||||
刷新试试
|
刷新试试
|
||||||
</uh-button>
|
</uh-button>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -95,11 +95,11 @@
|
|||||||
<!-- 左边 -->
|
<!-- 左边 -->
|
||||||
<view class="shrink-0 min-w-18" @click="handleBack()">
|
<view class="shrink-0 min-w-18" @click="handleBack()">
|
||||||
<view v-if="props.useBack"
|
<view v-if="props.useBack"
|
||||||
class="uh-global-card-glass h-7 px-3 rounded-full border flex items-center gap-x-2 text-sm"
|
class="uh-global-card-glass uh-shadow-xs h-8 px-3 rounded-full border flex items-center gap-x-2 text-sm"
|
||||||
:class="props.backClass" :style="[props.backStyle]">
|
:class="props.backClass" :style="[props.backStyle]">
|
||||||
<wd-icon name="arrow-left" size="32rpx"></wd-icon>
|
<wd-icon name="arrow-left" size="34rpx"></wd-icon>
|
||||||
<view class="w-[1px] h-4 bg-white/60" />
|
<view class="w-[1px] h-4 bg-white/60" />
|
||||||
<text class="text-xs font-bold">返回</text>
|
<text class="text-sm font-bold">返回</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- 中间 -->
|
<!-- 中间 -->
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<uh-glass-popup v-model="isShow" position="center" :z-index="9999" radius="24rpx" @close="handleClose">
|
<uh-glass-popup v-model="isShow" position="center" :z-index="9999" custom-class="rounded-xl" @close="handleClose">
|
||||||
<view v-if="notice" class="box-border w-[80vw] p-4">
|
<view v-if="notice" class="box-border w-[80vw] p-4">
|
||||||
<!-- 头部:标题 + 关闭 -->
|
<!-- 头部:标题 + 关闭 -->
|
||||||
<view class="flex items-center justify-between">
|
<view class="flex items-center justify-between">
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ const emptyText = computed(() => (activeKind.value === 'post' ? '还没有收藏
|
|||||||
<!-- 空态(当前 Tab 无收藏):uh-data-loading 统一渲染,视觉与 tabbar 页一致 -->
|
<!-- 空态(当前 Tab 无收藏):uh-data-loading 统一渲染,视觉与 tabbar 页一致 -->
|
||||||
<uh-data-loading
|
<uh-data-loading
|
||||||
v-if="loadingStatus !== 'success'" :loading-status="loadingStatus" min-height="65vh"
|
v-if="loadingStatus !== 'success'" :loading-status="loadingStatus" min-height="65vh"
|
||||||
:use-loading-button="false" :empty-text="emptyText" empty-sub-text="快去阅读文章/瞬间点击收藏吧"
|
:use-refresh-button="false" :empty-text="emptyText" empty-sub-text="快去阅读文章/瞬间点击收藏吧"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 成功态:当前 Tab 列表 -->
|
<!-- 成功态:当前 Tab 列表 -->
|
||||||
|
|||||||
+240
-234
@@ -1,264 +1,270 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { onLoad, onPullDownRefresh } from '@dcloudio/uni-app'
|
import { onLoad, onPullDownRefresh } from '@dcloudio/uni-app'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { getLoveAlbumByName, getLoveAlbums } from '@/api/uni-halo'
|
import { getLoveAlbumByName, getLoveAlbums } from '@/api/uni-halo'
|
||||||
import { useAppConfigStore } from '@/store/appConfig'
|
import { useAppConfigStore } from '@/store/appConfig'
|
||||||
import { checkImageUrl } from '@/utils/url'
|
import { checkImageUrl } from '@/utils/url'
|
||||||
import { getCache, setCache } from '@/utils/storage'
|
import { getCache, setCache } from '@/utils/storage'
|
||||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||||
import type { ILoveAlbum, ILovePhoto } from '@/api/types/uni-halo'
|
import type { ILoveAlbum, ILovePhoto } from '@/api/types/uni-halo'
|
||||||
|
|
||||||
definePage({
|
definePage({
|
||||||
style: {
|
style: {
|
||||||
navigationBarTitleText: '恋爱相册',
|
navigationBarTitleText: '恋爱相册',
|
||||||
navigationStyle: 'custom',
|
navigationStyle: 'custom',
|
||||||
enablePullDownRefresh: true,
|
enablePullDownRefresh: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const appConfigStore = useAppConfigStore()
|
const appConfigStore = useAppConfigStore()
|
||||||
const loveConfig = computed(() => appConfigStore.configs.loveConfig)
|
const loveConfig = computed(() => appConfigStore.configs.loveConfig)
|
||||||
|
|
||||||
/** 已解锁相册本地缓存 key */
|
/** 已解锁相册本地缓存 key */
|
||||||
const UNLOCKED_ALBUMS_CACHE_KEY = 'unlocked_albums'
|
const UNLOCKED_ALBUMS_CACHE_KEY = 'unlocked_albums'
|
||||||
/** 解锁 token 有效期(后端默认 30 分钟) */
|
/** 解锁 token 有效期(后端默认 30 分钟) */
|
||||||
const ALBUM_TOKEN_TTL_SECONDS = 30 * 60
|
const ALBUM_TOKEN_TTL_SECONDS = 30 * 60
|
||||||
|
|
||||||
/* ---------------- 展示层类型 ---------------- */
|
/* ---------------- 展示层类型 ---------------- */
|
||||||
/** 相册展示卡片(script 预处理后的干净展示数据) */
|
/** 相册展示卡片(script 预处理后的干净展示数据) */
|
||||||
interface ILoveAlbumCard {
|
interface ILoveAlbumCard {
|
||||||
/** 相册 key(metadata.name,用于解锁/详情请求) */
|
/** 相册 key(metadata.name,用于解锁/详情请求) */
|
||||||
name : string
|
name: string
|
||||||
displayName : string
|
displayName: string
|
||||||
locked : boolean
|
locked: boolean
|
||||||
photoCount : number
|
photoCount: number
|
||||||
/** 封面图(已预处理 URL) */
|
/** 封面图(已预处理 URL) */
|
||||||
image : string
|
image: string
|
||||||
/** 创建时间(格式化展示) */
|
/** 创建时间(格式化展示) */
|
||||||
takeTime : string
|
takeTime: string
|
||||||
/** 相册照片(解锁后填充) */
|
/** 相册照片(解锁后填充) */
|
||||||
photos : ILovePhoto[]
|
photos: ILovePhoto[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 相册卡片映射:字段取值 + 封面/时间预处理(模板不感知原始接口结构) */
|
/** 相册卡片映射:字段取值 + 封面/时间预处理(模板不感知原始接口结构) */
|
||||||
function mapAlbumCard(item : ILoveAlbum) : ILoveAlbumCard {
|
function mapAlbumCard(item: ILoveAlbum): ILoveAlbumCard {
|
||||||
const creationTimestamp = item.metadata?.creationTimestamp
|
const creationTimestamp = item.metadata?.creationTimestamp
|
||||||
return {
|
return {
|
||||||
name: item.name || item.metadata?.name || '',
|
name: item.name || item.metadata?.name || '',
|
||||||
displayName: item.displayName || item.title || '',
|
displayName: item.displayName || item.title || '',
|
||||||
locked: !!item.locked,
|
locked: !!item.locked,
|
||||||
photoCount: Number(item.photoCount) || 0,
|
photoCount: Number(item.photoCount) || 0,
|
||||||
image: checkImageUrl(item.cover || ''),
|
image: checkImageUrl(item.cover || ''),
|
||||||
takeTime: creationTimestamp ? dayjs(creationTimestamp).format('DD/MM/YYYY') : '',
|
takeTime: creationTimestamp ? dayjs(creationTimestamp).format('DD/MM/YYYY') : '',
|
||||||
photos: item.photos || [],
|
photos: item.photos || [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 状态 ---------------- */
|
/* ---------------- 状态 ---------------- */
|
||||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||||
const dataList = ref<ILoveAlbumCard[]>([])
|
const dataList = ref<ILoveAlbumCard[]>([])
|
||||||
const unlockedAlbums = ref<Record<string, string>>({})
|
const unlockedAlbums = ref<Record<string, string>>({})
|
||||||
|
|
||||||
/** 密码解锁弹窗 */
|
/** 密码解锁弹窗 */
|
||||||
const showUnlockModal = ref(false)
|
const showUnlockModal = ref(false)
|
||||||
const currentUnlockAlbum = ref<ILoveAlbumCard | null>(null)
|
const currentUnlockAlbum = ref<ILoveAlbumCard | null>(null)
|
||||||
|
|
||||||
/** 图片查看弹窗 */
|
/** 图片查看弹窗 */
|
||||||
const showPhotoViewer = ref(false)
|
const showPhotoViewer = ref(false)
|
||||||
const currentViewerAlbum = ref<ILoveAlbumCard | null>(null)
|
const currentViewerAlbum = ref<ILoveAlbumCard | null>(null)
|
||||||
const viewerLoading = ref(false)
|
|
||||||
|
|
||||||
const unlockAlbumName = computed(() => currentUnlockAlbum.value?.displayName || '')
|
const unlockAlbumName = computed(() => currentUnlockAlbum.value?.displayName || '')
|
||||||
const unlockAlbumKey = computed(() => currentUnlockAlbum.value?.name || '')
|
const unlockAlbumKey = computed(() => currentUnlockAlbum.value?.name || '')
|
||||||
const viewerAlbumName = computed(() => currentViewerAlbum.value?.displayName || '')
|
const viewerAlbumName = computed(() => currentViewerAlbum.value?.displayName || '')
|
||||||
const viewerPhotos = computed(() => currentViewerAlbum.value?.photos || [])
|
const viewerAlbumKey = computed(() => currentViewerAlbum.value?.name || '')
|
||||||
|
const viewerAlbumToken = computed(() => currentViewerAlbum.value
|
||||||
|
? (unlockedAlbums.value[currentViewerAlbum.value.name] || '')
|
||||||
|
: '')
|
||||||
|
|
||||||
/* ---------------- 缓存 ---------------- */
|
/* ---------------- 缓存 ---------------- */
|
||||||
function handleRestoreUnlockedAlbums() {
|
function handleRestoreUnlockedAlbums() {
|
||||||
try {
|
try {
|
||||||
const saved = getCache<Record<string, string>>(UNLOCKED_ALBUMS_CACHE_KEY)
|
const saved = getCache<Record<string, string>>(UNLOCKED_ALBUMS_CACHE_KEY)
|
||||||
if (saved) {
|
if (saved) {
|
||||||
unlockedAlbums.value = saved
|
unlockedAlbums.value = saved
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.error('恢复解锁状态失败', e)
|
console.error('恢复解锁状态失败', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSaveUnlockedAlbums() {
|
function handleSaveUnlockedAlbums() {
|
||||||
try {
|
try {
|
||||||
setCache(UNLOCKED_ALBUMS_CACHE_KEY, unlockedAlbums.value, ALBUM_TOKEN_TTL_SECONDS)
|
setCache(UNLOCKED_ALBUMS_CACHE_KEY, unlockedAlbums.value, ALBUM_TOKEN_TTL_SECONDS)
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.error('保存解锁状态失败', e)
|
console.error('保存解锁状态失败', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 数据加载 ---------------- */
|
/* ---------------- 数据加载 ---------------- */
|
||||||
async function handleGetData() {
|
async function handleGetData() {
|
||||||
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||||
try {
|
try {
|
||||||
const res = await getLoveAlbums({})
|
const res = await getLoveAlbums({})
|
||||||
const items = res.data?.items || []
|
const items = res.data?.items || []
|
||||||
dataList.value = items.map(mapAlbumCard)
|
dataList.value = items.map(mapAlbumCard)
|
||||||
updateLoadingStatus(dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success)
|
updateLoadingStatus(dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success)
|
||||||
if (dataList.value.length > 0)
|
if (dataList.value.length > 0)
|
||||||
handleLoadUnlockedAlbumPhotos()
|
handleLoadUnlockedAlbumPhotos()
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.error('获取相册失败', e)
|
console.error('获取相册失败', e)
|
||||||
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uni.stopPullDownRefresh()
|
uni.stopPullDownRefresh()
|
||||||
}, 200)
|
}, 200)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 加载已解锁相册的照片 */
|
/** 加载已解锁相册的照片 */
|
||||||
async function handleLoadUnlockedAlbumPhotos() {
|
async function handleLoadUnlockedAlbumPhotos() {
|
||||||
for (const item of dataList.value) {
|
for (const item of dataList.value) {
|
||||||
const token = unlockedAlbums.value[item.name]
|
const token = unlockedAlbums.value[item.name]
|
||||||
if (item.locked && token) {
|
if (item.locked && token) {
|
||||||
try {
|
try {
|
||||||
const detail = await getLoveAlbumByName(item.name, { token })
|
const detail = await getLoveAlbumByName(item.name, { token })
|
||||||
if (detail.locked) {
|
if (detail.locked) {
|
||||||
delete unlockedAlbums.value[item.name]
|
delete unlockedAlbums.value[item.name]
|
||||||
handleSaveUnlockedAlbums()
|
handleSaveUnlockedAlbums()
|
||||||
}
|
}
|
||||||
else if (detail.photos) {
|
else if (detail.photos) {
|
||||||
item.photos = detail.photos
|
item.photos = detail.photos
|
||||||
item.locked = false
|
item.locked = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.error('加载相册照片失败', e)
|
console.error('加载相册照片失败', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 交互 ---------------- */
|
/* ---------------- 交互 ---------------- */
|
||||||
function handleOnAlbumClick(item : ILoveAlbumCard) {
|
function handleOnAlbumClick(item: ILoveAlbumCard) {
|
||||||
if (item.locked && !unlockedAlbums.value[item.name]) {
|
if (item.locked && !unlockedAlbums.value[item.name]) {
|
||||||
currentUnlockAlbum.value = item
|
currentUnlockAlbum.value = item
|
||||||
showUnlockModal.value = true
|
showUnlockModal.value = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
handleOpenPhotoViewer(item)
|
handleOpenPhotoViewer(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleOpenPhotoViewer(item : ILoveAlbumCard) {
|
function handleOpenPhotoViewer(item: ILoveAlbumCard) {
|
||||||
currentViewerAlbum.value = item
|
currentViewerAlbum.value = item
|
||||||
showPhotoViewer.value = true
|
showPhotoViewer.value = true
|
||||||
if (item.photos.length > 0) { return }
|
// 照片数据由 uh-album-photo-viewer 内部自请求(见组件 handleLoadPhotos)
|
||||||
viewerLoading.value = true
|
}
|
||||||
try {
|
|
||||||
const token = unlockedAlbums.value[item.name] || ''
|
|
||||||
const detail = await getLoveAlbumByName(item.name, { token })
|
|
||||||
if (detail) {
|
|
||||||
if (detail.locked) {
|
|
||||||
delete unlockedAlbums.value[item.name]
|
|
||||||
handleSaveUnlockedAlbums()
|
|
||||||
}
|
|
||||||
else if (detail.photos) {
|
|
||||||
item.photos = detail.photos
|
|
||||||
item.locked = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.error('获取相册照片失败', e)
|
|
||||||
uni.showToast({ icon: 'none', title: '照片加载失败,请稍后重试' })
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
viewerLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleOnUnlockSuccess(data : { albumKey : string, token : string, photos : unknown[] }) {
|
function handleOnUnlockSuccess(data: { albumKey: string, token: string, photos: unknown[] }) {
|
||||||
unlockedAlbums.value[data.albumKey] = data.token
|
unlockedAlbums.value[data.albumKey] = data.token
|
||||||
handleSaveUnlockedAlbums()
|
handleSaveUnlockedAlbums()
|
||||||
|
|
||||||
const albumIndex = dataList.value.findIndex(a => a.name === data.albumKey)
|
const albumIndex = dataList.value.findIndex(a => a.name === data.albumKey)
|
||||||
if (albumIndex !== -1) {
|
if (albumIndex !== -1) {
|
||||||
dataList.value[albumIndex].photos = data.photos as ILovePhoto[]
|
dataList.value[albumIndex].photos = data.photos as ILovePhoto[]
|
||||||
dataList.value[albumIndex].locked = false
|
dataList.value[albumIndex].locked = false
|
||||||
}
|
}
|
||||||
currentUnlockAlbum.value = null
|
currentUnlockAlbum.value = null
|
||||||
if (albumIndex !== -1) {
|
if (albumIndex !== -1) {
|
||||||
handleOpenPhotoViewer(dataList.value[albumIndex])
|
handleOpenPhotoViewer(dataList.value[albumIndex])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleToTopPage(duration = 500) {
|
function handleToTopPage(duration = 500) {
|
||||||
uni.pageScrollTo({
|
uni.pageScrollTo({
|
||||||
scrollTop: 0,
|
scrollTop: 0,
|
||||||
duration,
|
duration,
|
||||||
fail: (err) => {
|
fail: (err) => {
|
||||||
console.error('回顶失败', err)
|
console.error('回顶失败', err)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 生命周期 ---------------- */
|
/* ---------------- 生命周期 ---------------- */
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
handleRestoreUnlockedAlbums()
|
handleRestoreUnlockedAlbums()
|
||||||
handleGetData()
|
handleGetData()
|
||||||
})
|
})
|
||||||
|
|
||||||
onPullDownRefresh(() => {
|
onPullDownRefresh(() => {
|
||||||
handleGetData()
|
handleGetData()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="app-page box-border min-h-screen w-screen flex flex-col pb-safe">
|
<view class="app-page box-border min-h-screen w-screen flex flex-col pb-safe">
|
||||||
<!-- 自定义导航 -->
|
<!-- 自定义导航 -->
|
||||||
<uh-navbar default-title="恋爱相册" title-color="text-love" back-class="text-love" />
|
<uh-navbar default-title="恋爱相册" title-color="text-love" back-class="text-love" />
|
||||||
|
|
||||||
<!-- 加载/错误/空占位(状态机) -->
|
<!-- 加载/错误/空占位(状态机) -->
|
||||||
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus"
|
<uh-data-loading
|
||||||
min-height="75vh" empty-text="相册暂时还没有数据~" @refresh="handleGetData" />
|
v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus"
|
||||||
|
min-height="75vh" empty-text="相册暂时还没有数据~" @refresh="handleGetData"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 相册列表(两列网格) -->
|
<!-- 相册列表(两列网格) -->
|
||||||
<view v-else class="box-border grid grid-cols-2 p-3 pt-2 gap-3">
|
<view v-else class="grid grid-cols-2 box-border gap-3 p-3 pt-2">
|
||||||
<view v-for="(item) in dataList" :key="item.name"
|
<view
|
||||||
class="uh-global-card-glass box-border overflow-hidden rounded-xl" @click="handleOnAlbumClick(item)">
|
v-for="(item) in dataList" :key="item.name"
|
||||||
<view class="relative h-24 w-full">
|
class="uh-global-card-glass box-border overflow-hidden rounded-xl" @click="handleOnAlbumClick(item)"
|
||||||
<image class="h-full w-full" :src="item.image" mode="aspectFill" lazy-load />
|
>
|
||||||
<view v-if="item.locked && !unlockedAlbums[item.name]"
|
<view class="relative h-36 w-full">
|
||||||
class="absolute left-0 top-0 h-full w-full flex flex-col items-center justify-center gap-1 bg-black/45">
|
<image class="h-full w-full" :src="item.image" mode="aspectFill" lazy-load />
|
||||||
<wd-icon name="lock" size="52rpx" class="text-white" />
|
<view
|
||||||
<view class="text-xs text-white"> 已加密 </view>
|
v-if="item.locked && !unlockedAlbums[item.name]"
|
||||||
</view>
|
class="absolute right-0 top-0 px-2 py-1 rounded-lb-md flex items-center justify-center gap-1 bg-black/30"
|
||||||
</view>
|
>
|
||||||
<view class="album-info box-border p-3">
|
<wd-icon name="lock" size="30rpx" class="text-white" />
|
||||||
<view
|
<view class="text-xs text-white">
|
||||||
class="album-name overflow-hidden text-ellipsis whitespace-nowrap text-[30rpx] text-[#333] font-bold">
|
已加密
|
||||||
{{ item.displayName }}
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="album-count mt-1 text-xs text-gray-500">
|
</view>
|
||||||
{{ item.photoCount }} 张照片
|
|
||||||
</view>
|
<view class="absolute left-0 right-0 bottom-0 box-border p-3 pt-6 bg-gradient-to-b from-white/0 to-white/60">
|
||||||
</view>
|
<view
|
||||||
</view>
|
class="truncate text-sm text-love font-bold"
|
||||||
</view>
|
>
|
||||||
<!-- 密码解锁弹窗 -->
|
{{ item.displayName }}
|
||||||
<uh-album-unlock-popup v-if="currentUnlockAlbum" v-model="showUnlockModal" :show="showUnlockModal" :album-name="unlockAlbumName"
|
</view>
|
||||||
:album-key="unlockAlbumKey" @update:show="showUnlockModal = $event" @success="handleOnUnlockSuccess" />
|
<view class="album-count mt-1 text-xs text-white">
|
||||||
|
{{ item.photoCount }} 张照片
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 密码解锁弹窗 -->
|
||||||
|
<uh-album-unlock-popup
|
||||||
|
v-if="currentUnlockAlbum" v-model="showUnlockModal" :show="showUnlockModal" :album-name="unlockAlbumName"
|
||||||
|
:album-key="unlockAlbumKey" @update:show="showUnlockModal = $event" @success="handleOnUnlockSuccess"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 相册图片查看弹窗 -->
|
<!-- 相册图片查看弹窗(数据在组件内部自请求) -->
|
||||||
<uh-album-photo-viewer v-if="currentViewerAlbum" v-model="showPhotoViewer" :show="showPhotoViewer" :album-name="viewerAlbumName"
|
<uh-album-photo-viewer
|
||||||
:photos="viewerPhotos" :loading="viewerLoading" @update:show="showPhotoViewer = $event" />
|
v-if="currentViewerAlbum" v-model="showPhotoViewer" :show="showPhotoViewer"
|
||||||
</view>
|
:album-name="viewerAlbumName" :album-key="viewerAlbumKey" :token="viewerAlbumToken"
|
||||||
|
@update:show="showPhotoViewer = $event"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.app-page {
|
.app-page {
|
||||||
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%));
|
background: linear-gradient(
|
||||||
}
|
-135deg,
|
||||||
</style>
|
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%)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -350,7 +350,7 @@
|
|||||||
|
|
||||||
</view>
|
</view>
|
||||||
<uh-data-loading v-if="showList.length === 0" :loading-status="DataLoadingStatusEnum.Empty"
|
<uh-data-loading v-if="showList.length === 0" :loading-status="DataLoadingStatusEnum.Empty"
|
||||||
min-height="42vh" empty-text="该筛选条件下暂无清单~" :use-loading-button="false" />
|
min-height="42vh" empty-text="该筛选条件下暂无清单~" :use-refresh-button="false" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 筛选弹层(状态/排序;排序附方向选择) -->
|
<!-- 筛选弹层(状态/排序;排序附方向选择) -->
|
||||||
|
|||||||
+198
-240
@@ -1,266 +1,224 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
/**
|
import { computed, ref } from 'vue'
|
||||||
* 内容搜索页(源自旧项目 pagesA/articles,新建复刻)
|
import { onLoad, onPullDownRefresh } from '@dcloudio/uni-app'
|
||||||
* 功能:关键词搜索文章/瞬间,结果列表展示
|
import { getPostListByKeyword } from '@/api/halo'
|
||||||
*/
|
import { sleep } from '@/utils/common'
|
||||||
import { computed, ref } from 'vue'
|
import { useAppConfigStore } from '@/store/appConfig'
|
||||||
import { onLoad, onPullDownRefresh } from '@dcloudio/uni-app'
|
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||||
import { getPostListByKeyword } from '@/api/halo'
|
import { NeedPluginIds } from '@/hooks/usePluginAvailable'
|
||||||
import { useAppConfigStore } from '@/store/appConfig'
|
import { markdownConfig } from '@/config/markdown'
|
||||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
import { debounce } from '@/utils/debounce'
|
||||||
import { NeedPluginIds } from '@/hooks/usePluginAvailable'
|
|
||||||
import { markdownConfig } from '@/config/markdown'
|
|
||||||
import { debounce } from '@/utils/debounce'
|
|
||||||
|
|
||||||
definePage({
|
definePage({
|
||||||
style: {
|
style: {
|
||||||
navigationBarTitleText: '内容搜索',
|
navigationBarTitleText: '内容搜索',
|
||||||
enablePullDownRefresh: true,
|
enablePullDownRefresh: true,
|
||||||
navigationStyle: 'custom',
|
navigationStyle: 'custom',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const appConfigStore = useAppConfigStore()
|
const appConfigStore = useAppConfigStore()
|
||||||
const calcAuditModeEnabled = computed(() => appConfigStore.auditModeEnabled)
|
const calcAuditModeEnabled = computed(() => appConfigStore.auditModeEnabled)
|
||||||
|
|
||||||
/** 依赖插件(plugin-search-widget,参考 gallery 对象传参模式) */
|
const { pluginId, checking, tips, available: uniHaloPluginAvailable, check: checkPluginAvailable } = usePluginAvailable({
|
||||||
const { pluginId, checking, tips, available: uniHaloPluginAvailable, check: checkPluginAvailable } = usePluginAvailable({
|
pluginId: NeedPluginIds.PluginSearchWidget,
|
||||||
pluginId: NeedPluginIds.PluginSearchWidget,
|
tips: '啊偶,当前功能未开放!',
|
||||||
tips: '检测到当前插件没有安装或者启用,无法使用搜索功能哦,请联系管理员',
|
})
|
||||||
})
|
|
||||||
|
|
||||||
/** 重新检测插件:可用则重新搜索(供 uh-plugin-unavailable 刷新按钮) */
|
async function handlePluginRefresh() {
|
||||||
async function handlePluginRefresh() {
|
if (await checkPluginAvailable()) { handleOnSearch() }
|
||||||
if (await checkPluginAvailable())
|
}
|
||||||
handleOnSearch()
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------- 状态 ---------------- */
|
/* ---------------- 状态 ---------------- */
|
||||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||||
const queryParams = ref({
|
const queryParams = ref({
|
||||||
keyword: '',
|
keyword: '',
|
||||||
limit: 50,
|
limit: 50,
|
||||||
highlightPreTag: '',
|
highlightPreTag: '',
|
||||||
highlightPostTag: '',
|
highlightPostTag: '',
|
||||||
})
|
})
|
||||||
const dataList = ref<{
|
const dataList = ref<{
|
||||||
metadataName?: string
|
metadataName ?: string
|
||||||
type?: string
|
type ?: string
|
||||||
title?: string
|
title ?: string
|
||||||
description?: string
|
description ?: string
|
||||||
content?: string
|
content ?: string
|
||||||
updateTimestamp?: string
|
updateTimestamp ?: string
|
||||||
}[]>([])
|
}[]>([])
|
||||||
|
|
||||||
/* ---------------- 动画(对应旧版 mixin calcAniWait) ---------------- */
|
/* ---------------- 动画(对应旧版 mixin calcAniWait) ---------------- */
|
||||||
/** 预计算列表项入场延迟(每 10 项重置一轮,每项递增 50ms);必须在渲染外算好,渲染中修改响应式状态会导致递归更新 */
|
/** 预计算列表项入场延迟(每 10 项重置一轮,每项递增 50ms);必须在渲染外算好,渲染中修改响应式状态会导致递归更新 */
|
||||||
const calcAniDelays = computed(() => {
|
const calcAniDelays = computed(() => {
|
||||||
let wait = 0
|
let wait = 0
|
||||||
return dataList.value.map((_, index) => {
|
return dataList.value.map((_, index) => {
|
||||||
wait = (index + 1) % 10 === 0 ? 1 : wait + 1
|
wait = (index + 1) % 10 === 0 ? 1 : wait + 1
|
||||||
return wait * 50
|
return wait * 50
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 空态文案(无关键词提示输入;有关键词提示未搜到) */
|
/** 空态文案(无关键词提示输入;有关键词提示未搜到) */
|
||||||
const emptyText = computed(() =>
|
const emptyText = computed(() =>
|
||||||
queryParams.value.keyword ? `未搜到 ${queryParams.value.keyword} 相关内容` : '请输入关键词搜索',
|
queryParams.value.keyword ? `未搜到 ${queryParams.value.keyword} 相关内容` : '请输入关键词搜索',
|
||||||
)
|
)
|
||||||
|
|
||||||
/* ---------------- 搜索 ---------------- */
|
/* ---------------- 搜索 ---------------- */
|
||||||
async function handleGetData() {
|
async function handleGetData() {
|
||||||
if (calcAuditModeEnabled.value)
|
if (calcAuditModeEnabled.value)
|
||||||
return
|
{return}
|
||||||
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||||
try {
|
try {
|
||||||
const res = await getPostListByKeyword({ ...queryParams.value })
|
const res = await getPostListByKeyword({ ...queryParams.value })
|
||||||
dataList.value = (res.data as unknown as { hits?: typeof dataList.value }).hits || []
|
dataList.value = (res.data as unknown as { hits ?: typeof dataList.value }).hits || []
|
||||||
updateLoadingStatus(
|
await sleep(600)
|
||||||
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
updateLoadingStatus(
|
||||||
)
|
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||||||
}
|
)
|
||||||
catch (err) {
|
}
|
||||||
console.error(err)
|
catch (err) {
|
||||||
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
console.error(err)
|
||||||
}
|
updateLoadingStatus(DataLoadingStatusEnum.Error)
|
||||||
finally {
|
}
|
||||||
setTimeout(() => {
|
finally {
|
||||||
uni.stopPullDownRefresh()
|
setTimeout(() => {
|
||||||
}, 800)
|
uni.stopPullDownRefresh()
|
||||||
}
|
}, 800)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleOnSearch() {
|
function handleOnSearch() {
|
||||||
if (!queryParams.value.keyword) {
|
if (!queryParams.value.keyword) {
|
||||||
dataList.value = []
|
dataList.value = []
|
||||||
updateLoadingStatus(DataLoadingStatusEnum.Empty)
|
updateLoadingStatus(DataLoadingStatusEnum.Empty)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
handleGetData()
|
handleGetData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 实时搜索:输入防抖 400ms 后触发(对应旧版 tm-search 的 @input) */
|
/** 实时搜索:输入防抖 400ms 后触发(对应旧版 tm-search 的 @input) */
|
||||||
const handleOnInput = debounce(() => {
|
const handleOnInput = debounce(() => {
|
||||||
handleOnSearch()
|
handleOnSearch()
|
||||||
}, 400)
|
}, 400)
|
||||||
|
|
||||||
function isArticle(item: { type?: string }): boolean {
|
function isArticle(item : { type ?: string }) : boolean {
|
||||||
return item.type === 'post.content.halo.run'
|
return item.type === 'post.content.halo.run'
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleToDetail(item: { metadataName?: string, type?: string }) {
|
function handleToDetail(item : { metadataName ?: string, type ?: string }) {
|
||||||
if (calcAuditModeEnabled.value)
|
if (calcAuditModeEnabled.value) { return }
|
||||||
return
|
if (isArticle(item)) {
|
||||||
if (isArticle(item)) {
|
uni.navigateTo({
|
||||||
uni.navigateTo({
|
url: `/pages-blog/article-detail/article-detail?name=${item.metadataName}`,
|
||||||
url: `/pages-blog/article-detail/article-detail?name=${item.metadataName}`,
|
animationType: 'slide-in-right',
|
||||||
animationType: 'slide-in-right',
|
})
|
||||||
})
|
}
|
||||||
}
|
else {
|
||||||
else {
|
uni.navigateTo({
|
||||||
uni.navigateTo({
|
url: `/pages-blog/moment-detail/moment-detail?name=${item.metadataName}`,
|
||||||
url: `/pages-blog/moment-detail/moment-detail?name=${item.metadataName}`,
|
animationType: 'slide-in-right',
|
||||||
animationType: 'slide-in-right',
|
})
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function handleToTopPage(duration = 500) {
|
function handleResetSearch() {
|
||||||
uni.pageScrollTo({
|
queryParams.value.keyword = '';
|
||||||
scrollTop: 0,
|
handleOnSearch()
|
||||||
duration,
|
}
|
||||||
fail: (err) => {
|
|
||||||
console.error('回顶失败', err)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------- 生命周期 ---------------- */
|
/* ---------------- 生命周期 ---------------- */
|
||||||
onLoad(async () => {
|
onLoad(async () => {
|
||||||
await checkPluginAvailable()
|
await checkPluginAvailable()
|
||||||
if (!uniHaloPluginAvailable.value) {
|
if (!uniHaloPluginAvailable.value) {
|
||||||
uni.stopPullDownRefresh()
|
uni.stopPullDownRefresh()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 关键词非空(如带参进入)时自动搜索,否则展示空态
|
// 关键词非空(如带参进入)时自动搜索,否则展示空态
|
||||||
if (!queryParams.value.keyword) {
|
if (!queryParams.value.keyword) {
|
||||||
updateLoadingStatus(DataLoadingStatusEnum.Empty)
|
updateLoadingStatus(DataLoadingStatusEnum.Empty)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
handleGetData()
|
handleGetData()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onPullDownRefresh(() => {
|
onPullDownRefresh(() => {
|
||||||
if (!uniHaloPluginAvailable.value) {
|
if (!uniHaloPluginAvailable.value) {
|
||||||
uni.stopPullDownRefresh()
|
uni.stopPullDownRefresh()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
handleOnSearch()
|
handleOnSearch()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="box-border min-h-screen w-screen flex flex-col bg-page pb-6">
|
<view class="box-border min-h-screen w-screen flex flex-col bg-page pb-6">
|
||||||
<!-- 自定义导航 -->
|
<!-- 自定义导航 -->
|
||||||
<uh-navbar default-title="内容搜索" title-color="text-gray-900" />
|
<uh-navbar default-title="内容搜索" title-color="text-gray-900" />
|
||||||
|
|
||||||
<uh-plugin-unavailable
|
<uh-plugin-unavailable v-if="!uniHaloPluginAvailable" :plugin-id="pluginId" :error-text="tips"
|
||||||
v-if="!uniHaloPluginAvailable"
|
:checking="checking" @on-refresh="handlePluginRefresh" />
|
||||||
:plugin-id="pluginId"
|
|
||||||
:error-text="tips"
|
|
||||||
:checking="checking"
|
|
||||||
@on-refresh="handlePluginRefresh"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<template v-else>
|
|
||||||
<!-- 顶部搜索框-->
|
|
||||||
<wd-sticky class="">
|
|
||||||
<view class="w-screen box-border px-3 py-2">
|
|
||||||
<view class="uh-global-card-glass h-9 flex items-center gap-3 rounded-full px-5">
|
|
||||||
<wd-icon name="search" size="16px" />
|
|
||||||
<input
|
|
||||||
v-model="queryParams.keyword"
|
|
||||||
class="flex-1 text-[26rpx] text-gray-900"
|
|
||||||
placeholder="哈喽,想看些什么呢~"
|
|
||||||
placeholder-class="text-gray-400"
|
|
||||||
confirm-type="search"
|
|
||||||
@input="handleOnInput"
|
|
||||||
@confirm="handleOnSearch"
|
|
||||||
>
|
|
||||||
<view v-if="queryParams.keyword" class="clear-btn flex items-center" @click="queryParams.keyword = ''; handleOnSearch()">
|
|
||||||
<wd-icon name="close" size="14px" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</wd-sticky>
|
|
||||||
|
|
||||||
<!-- 加载/错误/空占位(状态机) -->
|
<template v-else>
|
||||||
<uh-data-loading
|
<!-- 顶部搜索框-->
|
||||||
v-if="loadingStatus !== 'success'"
|
<wd-sticky class="">
|
||||||
:loading-status="loadingStatus"
|
<view class="w-screen box-border px-3 py-2">
|
||||||
min-height="65vh"
|
<view class="box-border uh-global-card-glass h-9 flex items-center gap-3 rounded-full pl-1 pr-3">
|
||||||
error-text="搜索异常"
|
<wd-icon name="search" size="16px" />
|
||||||
:empty-text="emptyText"
|
<input v-model="queryParams.keyword" class="flex-1 text-sm text-gray-900"
|
||||||
@refresh="handleOnSearch"
|
placeholder="哈喽,想看些什么呢~" placeholder-class="text-gray-400" confirm-type="search"
|
||||||
/>
|
@input="handleOnInput" @confirm="handleOnSearch">
|
||||||
|
<view v-if="queryParams.keyword" class="clear-btn flex items-center"
|
||||||
|
@click="handleResetSearch()">
|
||||||
|
<wd-icon name="close" size="28rpx" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</wd-sticky>
|
||||||
|
|
||||||
<!-- 内容区域(成功态) -->
|
<!-- 加载/错误/空占位(状态机) -->
|
||||||
<view v-else class="box-border pt-2 px-3 flex flex-col gap-y-3">
|
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus"
|
||||||
<block v-if="dataList.length !== 0">
|
min-height="70vh" error-text="搜索异常" :empty-text="emptyText" @refresh="handleOnSearch" />
|
||||||
<view
|
|
||||||
v-for="(item, index) in dataList"
|
<!-- 内容区域(成功态) -->
|
||||||
:key="index"
|
<view v-else class="box-border pt-2 px-3 flex flex-col gap-y-3">
|
||||||
class="uh-global-card-glass uh-shadow-xs border flex flex-col overflow-hidden rounded-2xl p-4"
|
<block v-if="dataList.length !== 0">
|
||||||
:style="{ animationDelay: `${calcAniDelays[index]}ms` }"
|
<view v-for="(item, index) in dataList" :key="index"
|
||||||
@click="handleToDetail(item)"
|
class="uh-global-card-glass uh-shadow-xs border flex flex-col overflow-hidden rounded-2xl p-4"
|
||||||
>
|
:style="{ animationDelay: `${calcAniDelays[index]}ms` }" @click="handleToDetail(item)">
|
||||||
<view class="card-head mb-3 flex items-center">
|
<view class="card-head mb-3 flex items-center">
|
||||||
<view
|
<view class="type-tag mr-3 shrink-0 rounded-md px-1.5 py-1 text-xs leading-none"
|
||||||
class="type-tag mr-3 shrink-0 rounded-md px-1.5 py-1 text-xs leading-none"
|
:class="isArticle(item) ? 'bg-secondary text-gray-900' : 'bg-blue-500 text-gray-50'">
|
||||||
:class="isArticle(item) ? 'bg-secondary text-gray-900' : 'bg-blue-500 text-gray-50'"
|
{{ isArticle(item) ? '文章' : '瞬间' }}
|
||||||
>
|
</view>
|
||||||
{{ isArticle(item) ? '文章' : '瞬间' }}
|
<text
|
||||||
</view>
|
class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-gray-900 font-bold">{{ item.title }}</text>
|
||||||
<text class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-gray-900 font-bold">{{ item.title }}</text>
|
</view>
|
||||||
</view>
|
<mp-html class="evan-markdown" lazy-load :domain="markdownConfig.domain ?? ''"
|
||||||
<mp-html
|
:loading-img="markdownConfig.loadingGif" scroll-table selectable
|
||||||
class="evan-markdown"
|
:tag-style="markdownConfig.tagStyle" :container-style="markdownConfig.containStyle"
|
||||||
lazy-load
|
:content="item.description || item.content || ''" :markdown="true" :show-line-number="true"
|
||||||
:domain="markdownConfig.domain ?? ''"
|
:show-language-name="true" copy-by-long-press />
|
||||||
:loading-img="markdownConfig.loadingGif"
|
</view>
|
||||||
scroll-table
|
</block>
|
||||||
selectable
|
</view>
|
||||||
:tag-style="markdownConfig.tagStyle"
|
</template>
|
||||||
:container-style="markdownConfig.containStyle"
|
</view>
|
||||||
:content="item.description || item.content || ''"
|
|
||||||
:markdown="true"
|
|
||||||
:show-line-number="true"
|
|
||||||
:show-language-name="true"
|
|
||||||
copy-by-long-press
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
</block>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
</view>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
/* 列表项入场动画(对应旧版 tm-translate fadeUp):@keyframes 无法用原子类表达,保留 scoped 样式 */
|
.fade-up {
|
||||||
.fade-up {
|
animation: fade-up 0.4s ease-out both;
|
||||||
animation: fade-up 0.4s ease-out both;
|
|
||||||
|
|
||||||
@keyframes fade-up {
|
@keyframes fade-up {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(24rpx);
|
transform: translateY(24rpx);
|
||||||
}
|
}
|
||||||
to {
|
|
||||||
opacity: 1;
|
to {
|
||||||
transform: translateY(0);
|
opacity: 1;
|
||||||
}
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
}
|
||||||
|
</style>
|
||||||
@@ -274,7 +274,7 @@ onShareTimeline(() => ({
|
|||||||
<!-- 投票信息 -->
|
<!-- 投票信息 -->
|
||||||
<view class="uh-global-card-glass box-border flex flex-col gap-y-3 rounded-2xl p-3">
|
<view class="uh-global-card-glass box-border flex flex-col gap-y-3 rounded-2xl p-3">
|
||||||
<uh-section-title> 投票信息 </uh-section-title>
|
<uh-section-title> 投票信息 </uh-section-title>
|
||||||
<view class="flex flex-col gap-3 rounded-xl bg-gray-100 p-4 text-sm text-gray-600">
|
<view class="flex flex-col gap-2 rounded-xl bg-gray-100 px-4 py-3 text-sm text-gray-900">
|
||||||
<view class="info-row">
|
<view class="info-row">
|
||||||
<text>投票类型:</text>
|
<text>投票类型:</text>
|
||||||
<text class="tag">{{ vote.spec?._uh_type }}</text>
|
<text class="tag">{{ vote.spec?._uh_type }}</text>
|
||||||
@@ -290,7 +290,7 @@ onShareTimeline(() => ({
|
|||||||
</view>
|
</view>
|
||||||
<view class="info-row">
|
<view class="info-row">
|
||||||
<text>投票方式:</text>
|
<text>投票方式:</text>
|
||||||
<text class="tag" :class="vote.spec?.canAnonymously ? 'text-primary' : 'text-[#f44336]'">
|
<text class="tag" :class="vote.spec?.canAnonymously ? 'text-primary' : 'text-red-400'">
|
||||||
{{ vote.spec?.canAnonymously ? '匿名' : '不匿名' }}
|
{{ vote.spec?.canAnonymously ? '匿名' : '不匿名' }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -307,7 +307,7 @@ onShareTimeline(() => ({
|
|||||||
<!-- 投票内容 -->
|
<!-- 投票内容 -->
|
||||||
<view class="uh-global-card-glass box-border flex flex-col gap-3 rounded-2xl p-3">
|
<view class="uh-global-card-glass box-border flex flex-col gap-3 rounded-2xl p-3">
|
||||||
<uh-section-title> 投票内容 </uh-section-title>
|
<uh-section-title> 投票内容 </uh-section-title>
|
||||||
<view class="box-border flex flex-col gap-y-2 rounded-xl bg-gray-100 p-4">
|
<view class="box-border flex flex-col gap-y-2 rounded-xl bg-gray-100 px-4 py-3">
|
||||||
<view class="text-sm text-gray-900 font-bold">
|
<view class="text-sm text-gray-900 font-bold">
|
||||||
{{ vote.spec?.title }}
|
{{ vote.spec?.title }}
|
||||||
</view>
|
</view>
|
||||||
@@ -326,12 +326,12 @@ onShareTimeline(() => ({
|
|||||||
{{ vote.spec?.maxVotes }} 项)
|
{{ vote.spec?.maxVotes }} 项)
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="options flex flex-col gap-3">
|
<view class="options flex flex-col gap-3 w-full">
|
||||||
<!-- PK 对抗条(与旧项目 pk-container 一致;样式需顶层定义,勿嵌套在 .vote-card 下) -->
|
<!-- PK 对抗条(与旧项目 pk-container 一致;样式需顶层定义,勿嵌套在 .vote-card 下) -->
|
||||||
<view v-if="vote.spec?.type === 'pk'" class="pk-container box-border w-full flex">
|
<view v-if="vote.spec?.type === 'pk'" class="pk-container box-border w-full flex">
|
||||||
<view
|
<view
|
||||||
v-for="(option, optionIndex) in vote.spec?.options" :key="optionIndex"
|
v-for="(option, optionIndex) in vote.spec?.options" :key="optionIndex"
|
||||||
class="radio-item" :class="optionIndex === 0 ? 'radio-left' : 'radio-right'"
|
class="flex-1 radio-item" :class="optionIndex === 0 ? 'radio-left' : 'radio-right'"
|
||||||
:style="{ width: `${option._uh_percent}%` }"
|
:style="{ width: `${option._uh_percent}%` }"
|
||||||
>
|
>
|
||||||
<view
|
<view
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
const articleDetailPath = '/pages-blog/article-detail/article-detail'
|
const articleDetailPath = '/pages-blog/article-detail/article-detail'
|
||||||
|
|
||||||
// 本地开发快速跳转页面,发布请置为 false
|
// 本地开发快速跳转页面,发布请置为 false
|
||||||
const DEV_MODE = true
|
const DEV_MODE = false
|
||||||
const DEV_TO_TYPE = 'page' as 'page' | 'tabbar'
|
const DEV_TO_TYPE = 'page' as 'page' | 'tabbar'
|
||||||
const DEV_TO_PATH = `/pages-blog/love/love`
|
const DEV_TO_PATH = `/pages-blog/love/love`
|
||||||
|
|
||||||
|
|||||||
@@ -193,7 +193,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="min-h-screen w-screen flex flex-col bg-page pb-6">
|
<view class="min-h-screen w-screen flex flex-col bg-page pb-6">
|
||||||
<uh-navbar :use-back="false" default-title="我的图库" title-color="text-gray-900"></uh-navbar>
|
<uh-navbar :use-back="false" default-title="我的图库" title-color="text-gray-900"></uh-navbar>
|
||||||
|
|
||||||
<uh-plugin-unavailable v-if="!uniHaloPluginAvailable" :plugin-id="pluginId" :error-text="tips"
|
<uh-plugin-unavailable v-if="!uniHaloPluginAvailable" :plugin-id="pluginId" :error-text="tips"
|
||||||
:checking="checking" @on-refresh="checkPluginAvailable" />
|
:checking="checking" @on-refresh="checkPluginAvailable" />
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -217,9 +217,13 @@
|
|||||||
<view v-else class="box-border w-full p-3">
|
<view v-else class="box-border w-full p-3">
|
||||||
<view class="grid grid-cols-2 gap-2.5">
|
<view class="grid grid-cols-2 gap-2.5">
|
||||||
<view v-for="(item, index) in dataList" :key="index"
|
<view v-for="(item, index) in dataList" :key="index"
|
||||||
class="uh-global-card-glass h-38 w-full overflow-hidden rounded-xl">
|
class="relative uh-global-card-glass h-38 w-full overflow-hidden rounded-xl">
|
||||||
<image class="h-full w-full" :src="item.spec.url" mode="aspectFill" lazy-load
|
<image class="h-full w-full" :src="item.spec.url" mode="aspectFill" lazy-load
|
||||||
@click="handlePreview(item)" />
|
@click="handlePreview(item)" />
|
||||||
|
<view v-if="item.spec.displayName"
|
||||||
|
class="absolute bottom-0 w-full box-border p-3 pt-6 z-2 bg-gradient-to-b from-white/0 to-black/40 text-xs text-white">
|
||||||
|
{{item.spec.displayName}}
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="load-text w-full py-4 text-center text-xs text-gray-500">
|
<view class="load-text w-full py-4 text-center text-xs text-gray-500">
|
||||||
|
|||||||
Reference in New Issue
Block a user