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 接口定义
|
||||
* 覆盖 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
|
||||
<!-- 加载/错误/空占位(状态机) -->
|
||||
<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="photo-info box-border px-6 py-5">
|
||||
<view v-if="photo.title" class="photo-title mb-3 text-[30rpx] text-[#333] font-bold">
|
||||
<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 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]">
|
||||
<view v-if="photo.description" class="mb-1 text-xs text-white leading-5">
|
||||
{{ photo.description }}
|
||||
</view>
|
||||
</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>
|
||||
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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 列表 -->
|
||||
|
||||
+124
-118
@@ -1,48 +1,48 @@
|
||||
<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({
|
||||
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 {
|
||||
/* ---------------- 展示层类型 ---------------- */
|
||||
/** 相册展示卡片(script 预处理后的干净展示数据) */
|
||||
interface ILoveAlbumCard {
|
||||
/** 相册 key(metadata.name,用于解锁/详情请求) */
|
||||
name : string
|
||||
displayName : string
|
||||
locked : boolean
|
||||
photoCount : number
|
||||
name: string
|
||||
displayName: string
|
||||
locked: boolean
|
||||
photoCount: number
|
||||
/** 封面图(已预处理 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
|
||||
return {
|
||||
name: item.name || item.metadata?.name || '',
|
||||
@@ -53,29 +53,31 @@
|
||||
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() {
|
||||
/* ---------------- 缓存 ---------------- */
|
||||
function handleRestoreUnlockedAlbums() {
|
||||
try {
|
||||
const saved = getCache<Record<string, string>>(UNLOCKED_ALBUMS_CACHE_KEY)
|
||||
if (saved) {
|
||||
@@ -85,19 +87,19 @@
|
||||
catch (e) {
|
||||
console.error('恢复解锁状态失败', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleSaveUnlockedAlbums() {
|
||||
function handleSaveUnlockedAlbums() {
|
||||
try {
|
||||
setCache(UNLOCKED_ALBUMS_CACHE_KEY, unlockedAlbums.value, ALBUM_TOKEN_TTL_SECONDS)
|
||||
}
|
||||
catch (e) {
|
||||
console.error('保存解锁状态失败', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 数据加载 ---------------- */
|
||||
async function handleGetData() {
|
||||
/* ---------------- 数据加载 ---------------- */
|
||||
async function handleGetData() {
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||
try {
|
||||
const res = await getLoveAlbums({})
|
||||
@@ -116,10 +118,10 @@
|
||||
uni.stopPullDownRefresh()
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 加载已解锁相册的照片 */
|
||||
async function handleLoadUnlockedAlbumPhotos() {
|
||||
/** 加载已解锁相册的照片 */
|
||||
async function handleLoadUnlockedAlbumPhotos() {
|
||||
for (const item of dataList.value) {
|
||||
const token = unlockedAlbums.value[item.name]
|
||||
if (item.locked && token) {
|
||||
@@ -139,47 +141,25 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 交互 ---------------- */
|
||||
function handleOnAlbumClick(item : ILoveAlbumCard) {
|
||||
/* ---------------- 交互 ---------------- */
|
||||
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) {
|
||||
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
|
||||
}
|
||||
}
|
||||
// 照片数据由 uh-album-photo-viewer 内部自请求(见组件 handleLoadPhotos)
|
||||
}
|
||||
|
||||
function handleOnUnlockSuccess(data : { albumKey : string, token : string, photos : unknown[] }) {
|
||||
function handleOnUnlockSuccess(data: { albumKey: string, token: string, photos: unknown[] }) {
|
||||
unlockedAlbums.value[data.albumKey] = data.token
|
||||
handleSaveUnlockedAlbums()
|
||||
|
||||
@@ -192,9 +172,9 @@
|
||||
if (albumIndex !== -1) {
|
||||
handleOpenPhotoViewer(dataList.value[albumIndex])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleToTopPage(duration = 500) {
|
||||
function handleToTopPage(duration = 500) {
|
||||
uni.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration,
|
||||
@@ -202,17 +182,17 @@
|
||||
console.error('回顶失败', err)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 生命周期 ---------------- */
|
||||
onLoad(() => {
|
||||
/* ---------------- 生命周期 ---------------- */
|
||||
onLoad(() => {
|
||||
handleRestoreUnlockedAlbums()
|
||||
handleGetData()
|
||||
})
|
||||
})
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
onPullDownRefresh(() => {
|
||||
handleGetData()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -221,44 +201,70 @@
|
||||
<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 v-else class="grid grid-cols-2 box-border gap-3 p-3 pt-2">
|
||||
<view
|
||||
class="album-name overflow-hidden text-ellipsis whitespace-nowrap text-[30rpx] text-[#333] font-bold">
|
||||
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-gray-500">
|
||||
<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-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" />
|
||||
<!-- 相册图片查看弹窗(数据在组件内部自请求) -->
|
||||
<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%));
|
||||
}
|
||||
.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>
|
||||
@@ -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>
|
||||
|
||||
<!-- 筛选弹层(状态/排序;排序附方向选择) -->
|
||||
|
||||
@@ -1,80 +1,75 @@
|
||||
<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({
|
||||
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({
|
||||
const { pluginId, checking, tips, available: uniHaloPluginAvailable, check: checkPluginAvailable } = usePluginAvailable({
|
||||
pluginId: NeedPluginIds.PluginSearchWidget,
|
||||
tips: '检测到当前插件没有安装或者启用,无法使用搜索功能哦,请联系管理员',
|
||||
})
|
||||
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({
|
||||
/* ---------------- 状态 ---------------- */
|
||||
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 dataList = ref<{
|
||||
metadataName ?: string
|
||||
type ?: string
|
||||
title ?: string
|
||||
description ?: string
|
||||
content ?: string
|
||||
updateTimestamp ?: string
|
||||
}[]>([])
|
||||
|
||||
/* ---------------- 动画(对应旧版 mixin calcAniWait) ---------------- */
|
||||
/** 预计算列表项入场延迟(每 10 项重置一轮,每项递增 50ms);必须在渲染外算好,渲染中修改响应式状态会导致递归更新 */
|
||||
const calcAniDelays = computed(() => {
|
||||
/* ---------------- 动画(对应旧版 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(() =>
|
||||
/** 空态文案(无关键词提示输入;有关键词提示未搜到) */
|
||||
const emptyText = computed(() =>
|
||||
queryParams.value.keyword ? `未搜到 ${queryParams.value.keyword} 相关内容` : '请输入关键词搜索',
|
||||
)
|
||||
)
|
||||
|
||||
/* ---------------- 搜索 ---------------- */
|
||||
async function handleGetData() {
|
||||
/* ---------------- 搜索 ---------------- */
|
||||
async function handleGetData() {
|
||||
if (calcAuditModeEnabled.value)
|
||||
return
|
||||
{return}
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||
try {
|
||||
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 || []
|
||||
await sleep(600)
|
||||
updateLoadingStatus(
|
||||
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||||
)
|
||||
@@ -88,9 +83,9 @@ async function handleGetData() {
|
||||
uni.stopPullDownRefresh()
|
||||
}, 800)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleOnSearch() {
|
||||
function handleOnSearch() {
|
||||
if (!queryParams.value.keyword) {
|
||||
dataList.value = []
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Empty)
|
||||
@@ -98,20 +93,19 @@ function handleOnSearch() {
|
||||
else {
|
||||
handleGetData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 实时搜索:输入防抖 400ms 后触发(对应旧版 tm-search 的 @input) */
|
||||
const handleOnInput = debounce(() => {
|
||||
/** 实时搜索:输入防抖 400ms 后触发(对应旧版 tm-search 的 @input) */
|
||||
const handleOnInput = debounce(() => {
|
||||
handleOnSearch()
|
||||
}, 400)
|
||||
}, 400)
|
||||
|
||||
function isArticle(item: { type?: string }): boolean {
|
||||
function isArticle(item : { type ?: string }) : boolean {
|
||||
return item.type === 'post.content.halo.run'
|
||||
}
|
||||
}
|
||||
|
||||
function handleToDetail(item: { metadataName?: string, type?: string }) {
|
||||
if (calcAuditModeEnabled.value)
|
||||
return
|
||||
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}`,
|
||||
@@ -124,20 +118,15 @@ function handleToDetail(item: { metadataName?: string, type?: string }) {
|
||||
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 () => {
|
||||
/* ---------------- 生命周期 ---------------- */
|
||||
onLoad(async () => {
|
||||
await checkPluginAvailable()
|
||||
if (!uniHaloPluginAvailable.value) {
|
||||
uni.stopPullDownRefresh()
|
||||
@@ -150,15 +139,15 @@ onLoad(async () => {
|
||||
else {
|
||||
handleGetData()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
onPullDownRefresh(() => {
|
||||
if (!uniHaloPluginAvailable.value) {
|
||||
uni.stopPullDownRefresh()
|
||||
return
|
||||
}
|
||||
handleOnSearch()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -166,80 +155,49 @@ onPullDownRefresh(() => {
|
||||
<!-- 自定义导航 -->
|
||||
<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"
|
||||
/>
|
||||
<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">
|
||||
<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-[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" />
|
||||
<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>
|
||||
|
||||
<!-- 加载/错误/空占位(状态机) -->
|
||||
<uh-data-loading
|
||||
v-if="loadingStatus !== 'success'"
|
||||
:loading-status="loadingStatus"
|
||||
min-height="65vh"
|
||||
error-text="搜索异常"
|
||||
:empty-text="emptyText"
|
||||
@refresh="handleOnSearch"
|
||||
/>
|
||||
<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"
|
||||
<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)"
|
||||
>
|
||||
: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'"
|
||||
>
|
||||
<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>
|
||||
<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
|
||||
/>
|
||||
<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>
|
||||
@@ -248,8 +206,7 @@ onPullDownRefresh(() => {
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 列表项入场动画(对应旧版 tm-translate fadeUp):@keyframes 无法用原子类表达,保留 scoped 样式 */
|
||||
.fade-up {
|
||||
.fade-up {
|
||||
animation: fade-up 0.4s ease-out both;
|
||||
|
||||
@keyframes fade-up {
|
||||
@@ -257,10 +214,11 @@ onPullDownRefresh(() => {
|
||||
opacity: 0;
|
||||
transform: translateY(24rpx);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user