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