mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-09-12 16:40:40 +08:00
refactor: 整体优化项目页面与组件样式与结构
1. 移除冗余组件注释与老旧类名,统一样式规范 2. 新增自定义导航栏适配,修复导航栏层级与返回按钮逻辑 3. 调整列表页面布局,统一添加box-border与间距规范 4. 优化友链弹窗与联系页面样式,修复文案与交互细节 5. 重构关于与免责声明页面,适配动态站点信息 6. 统一瞬间页面与投票页面的UI风格与代码结构
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { checkUrl } from '@/utils/url'
|
||||
import { useAppConfigStore } from '@/store/appConfig'
|
||||
import type { DataLoadingStatus } from '@/hooks/useDataLoading'
|
||||
|
||||
interface IProps {
|
||||
@@ -31,6 +33,16 @@
|
||||
|
||||
const emit = defineEmits<{ (e : 'refresh') : void }>()
|
||||
|
||||
const appConfigStore = useAppConfigStore()
|
||||
|
||||
const appInfo = computed(() => {
|
||||
const _appInfo = (appConfigStore.configs?.appConfig?.appInfo as any)
|
||||
return {
|
||||
name: _appInfo.name ?? 'UniHalo',
|
||||
logo: checkUrl(_appInfo?.logo ?? 'https://uni-halo.925i.cn/logo.png')
|
||||
}
|
||||
})
|
||||
|
||||
const isLoading = computed(() => props.loadingStatus === 'loading')
|
||||
|
||||
const statusScene = computed(() => {
|
||||
@@ -64,9 +76,13 @@
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="w-full flex flex-col items-center justify-center gap-y-4 text-sm"
|
||||
<view class="relative w-full flex flex-col items-center justify-center gap-y-4 text-sm"
|
||||
:style="{ minHeight: props.minHeight }">
|
||||
<view class="scene relative h-[250rpx] w-[250rpx] flex items-center justify-center"
|
||||
|
||||
<!-- logo背景 -->
|
||||
<image v-if="false" :src="appInfo.logo" class="absolute left-1/2 top-1/2 -translate-1/2 z-0 opacity-10 w-46 h-46"></image>
|
||||
|
||||
<view class="scene relative z-1 h-[250rpx] w-[250rpx] flex items-center justify-center"
|
||||
:class="statusScene.stageClass">
|
||||
<view class="glow absolute inset-0 m-auto h-[220rpx] w-[220rpx] rounded-full" />
|
||||
<view class="deco-dot dot-a absolute rounded-full" />
|
||||
@@ -79,7 +95,7 @@
|
||||
</view>
|
||||
|
||||
<!-- 文案区 -->
|
||||
<view class="flex flex-col items-center">
|
||||
<view class="relative z-2 flex flex-col items-center">
|
||||
<view class="flex items-center justify-center text-sm font-bold" :class="statusScene.mainTextClass">
|
||||
<text>{{ statusScene.mainText }}</text>
|
||||
<view v-if="isLoading" class="ml-1 flex items-end gap-1">
|
||||
|
||||
@@ -87,11 +87,11 @@
|
||||
|
||||
<template>
|
||||
<view class="w-full box-border">
|
||||
<view class="box-border pt-safe w-full fixed left-0 top-0 z-50" :class="customCalss" :style="[customStyle]">
|
||||
<view class="box-border pt-safe w-full fixed left-0 top-0 z-100" :class="customCalss" :style="[customStyle]">
|
||||
<view class="w-full h-[46px] flex items-center gap-x-4 box-border px-3 backdrop-blur-[2rpx]">
|
||||
<!-- 左边 -->
|
||||
<view class="shrink-0" @click="handleBack()">
|
||||
<view
|
||||
<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-gray-900 text-sm">
|
||||
<wd-icon name="arrow-left" size="32rpx"></wd-icon>
|
||||
<view class="w-[1px] h-4 bg-white/60" />
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 投票卡片(源自旧项目 components/vote-card,新建复刻)
|
||||
* 纯展示卡片:列表接口一次返回,页面加工后传入,点击跳转详情
|
||||
*/
|
||||
import { formatTime as formatTimeUtil } from '@/utils/formatTime'
|
||||
|
||||
interface IVoteCardOption {
|
||||
@@ -50,7 +46,7 @@ function formatTime(date?: string, fmt = 'yyyy-MM-dd HH:mm'): string {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="uh-vote-card uh-global-card-glass box-border w-full rounded-2xl p-4" @click="handleToDetail">
|
||||
<view class="uh-global-card-glass box-border w-full rounded-2xl p-4" @click="handleToDetail">
|
||||
<!-- 头部:类型/状态/已投票 -->
|
||||
<view class="flex items-center justify-between">
|
||||
<view class="flex flex-wrap items-center gap-1">
|
||||
|
||||
@@ -1,27 +1,35 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 关于项目页(源自旧项目 pagesA/about,新建复刻)
|
||||
* 数字名片式设计:Hero 名片卡(渐变光斑透卡 + 徽章) + 彩色瓦片链接列表,点击复制
|
||||
*/
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { useAppConfigStore } from '@/store/appConfig'
|
||||
import { checkUrl } from '@/utils/url'
|
||||
|
||||
definePage({
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '关于项目',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
const links = [
|
||||
const appConfigStore = useAppConfigStore()
|
||||
|
||||
const appInfo = computed(() => {
|
||||
const _appInfo = (appConfigStore.configs?.appConfig?.appInfo as any)
|
||||
return {
|
||||
name: _appInfo.name ?? 'UniHalo',
|
||||
logo: checkUrl(_appInfo?.logo ?? 'https://uni-halo.925i.cn/logo.png')
|
||||
}
|
||||
})
|
||||
|
||||
const links = [
|
||||
{ title: '开源组织', value: '巷子工坊', copy: 'https://www.ialley.cn', tip: '巷子工坊官网已复制成功!', tileColor: '#5c6bc0', tileLetter: '组' },
|
||||
{ title: '开源作者', value: '小莫唐尼', copy: 'https://www.xiaoxiaomo.cn', tip: '作者主页地址已复制', tileColor: '#26a69a', tileLetter: '作' },
|
||||
{ title: '作者博客', value: 'https://blog.xiaoxiaomo.cn', copy: 'https://blog.xiaoxiaomo.cn', tip: '作者博客地址已复制', tileColor: '#7e57c2', tileLetter: '博' },
|
||||
{ title: '文档地址', value: 'https://uni-halo.925i.cn', copy: 'https://uni-halo.925i.cn', tip: '项目码云仓库已复制', tileColor: '#039be5', tileLetter: '文' },
|
||||
{ title: '码云仓库', value: 'https://gitee.com/ialley-workshop-open/uni-halo', copy: 'https://gitee.com/ialley-workshop-open/uni-halo', tip: '码云仓库地址已复制', tileColor: '#c71d23', tileLetter: '码' },
|
||||
{ title: 'Github', value: 'https://github.com/ialley-workshop-open/uni-halo', copy: 'https://github.com/ialley-workshop-open/uni-halo', tip: 'Github地址已复制', tileColor: '#24292f', tileLetter: 'G' },
|
||||
]
|
||||
]
|
||||
|
||||
function copyText(content: string, tips: string) {
|
||||
function copyText(content : string, tips : string) {
|
||||
uni.setClipboardData({
|
||||
data: content,
|
||||
showToast: false,
|
||||
@@ -29,50 +37,48 @@ function copyText(content: string, tips: string) {
|
||||
uni.showToast({ icon: 'none', title: tips })
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
onLoad(() => {
|
||||
uni.setNavigationBarTitle({ title: '关于项目' })
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="app-page box-border min-h-screen w-screen flex flex-col overflow-hidden bg-page px-4 pb-8 pt-6">
|
||||
<view class="box-border min-h-screen w-screen flex flex-col overflow-hidden bg-page px-4 pb-8 pt-2">
|
||||
<!-- 自定义导航 -->
|
||||
<uh-navbar default-title="关于项目" title-color="text-gray-900" />
|
||||
|
||||
<!-- Hero 名片卡(主题色光斑透过毛玻璃形成柔和渐变) -->
|
||||
<view class="hero-wrap relative">
|
||||
<view class="relative">
|
||||
<view class="absolute h-[220rpx] w-[220rpx] rounded-full bg-[rgba(185,228,36,0.32)] -right-8 -top-8" />
|
||||
<view class="absolute top-[150rpx] h-[180rpx] w-[180rpx] rounded-full bg-[rgba(215,249,76,0.45)] -left-10" />
|
||||
<view class="hero-card uh-global-card-glass relative flex flex-col items-center rounded-3xl px-6 pb-7 pt-10">
|
||||
<image class="logo-img h-[140rpx] w-[140rpx] rounded-3xl shadow-lg" src="https://uni-halo.925i.cn/logo.png" mode="aspectFill" />
|
||||
<view class="mt-4 text-[40rpx] text-gray-900 font-bold">
|
||||
uni-halo
|
||||
<view
|
||||
class="absolute top-[150rpx] h-[180rpx] w-[180rpx] rounded-full bg-[rgba(215,249,76,0.45)] -left-10" />
|
||||
<view class="uh-global-card-glass relative flex flex-col items-center rounded-2xl px-6 pb-7 pt-10">
|
||||
<image class="h-[140rpx] w-[140rpx] rounded-3xl shadow-lg" :src="appInfo.logo" mode="aspectFill" />
|
||||
<view class="mt-4 text-xl text-gray-900 font-bold">
|
||||
{{appInfo.name}}
|
||||
</view>
|
||||
<view class="mt-3 flex items-center gap-2">
|
||||
<text class="rounded-full bg-secondary px-3 py-1 text-[20rpx] text-[#4d7c0f]">AGPL-3.0 开源协议</text>
|
||||
<text class="rounded-full bg-[#f6f3ee] px-3 py-1 text-[20rpx] text-gray-500">uni-app × Halo</text>
|
||||
<text class="rounded-full bg-[#f6f3ee] px-3 py-1 text-[20rpx] text-gray-500">UniApp × Halo</text>
|
||||
</view>
|
||||
<view class="mt-4 text-center text-[24rpx] text-gray-500 leading-relaxed">
|
||||
<view class="mt-4 text-center text-xs text-gray-500 leading-relaxed">
|
||||
基于 uni-app 打造的 Halo 博客跨端客户端
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 相关链接 -->
|
||||
<uh-section-title class="mb-3 mt-6 text-[30rpx]">
|
||||
<uh-section-title class="mb-3 mt-6">
|
||||
相关链接
|
||||
</uh-section-title>
|
||||
<view class="link-list uh-global-card-glass overflow-hidden rounded-2xl">
|
||||
<view
|
||||
v-for="(link, index) in links"
|
||||
:key="link.title"
|
||||
class="link-item flex items-center gap-3 px-4 py-4"
|
||||
<view v-for="(link, index) in links" :key="link.title" class="link-item flex items-center gap-3 px-4 py-4"
|
||||
:class="index < links.length - 1 ? 'border-b border-black/5' : ''"
|
||||
@click="copyText(link.copy, link.tip)"
|
||||
>
|
||||
<view class="tile h-[76rpx] w-[76rpx] flex shrink-0 items-center justify-center rounded-xl border border-black/5" :style="{ backgroundColor: `${link.tileColor}1A` }">
|
||||
@click="copyText(link.copy, link.tip)">
|
||||
<view
|
||||
class="tile h-[76rpx] w-[76rpx] flex shrink-0 items-center justify-center rounded-xl border border-black/5"
|
||||
:style="{ backgroundColor: `${link.tileColor}1A` }">
|
||||
<text class="text-[30rpx] font-bold" :style="{ color: link.tileColor }">{{ link.tileLetter }}</text>
|
||||
</view>
|
||||
<view class="min-w-0 flex flex-1 flex-col">
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 联系博主页(源自旧项目 pagesA/contact,新建复刻)
|
||||
* 数字名片式设计:Hero 名片卡(渐变光斑透卡) + 品牌色字母瓦片联系方式列表,点击复制
|
||||
*/
|
||||
import { computed, ref, watch, watchEffect } from 'vue'
|
||||
import { useAppConfigStore } from '@/store/appConfig'
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
@@ -94,7 +90,7 @@ watch(socialConfig, () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="app-page box-border min-h-screen w-screen overflow-hidden bg-page px-4 pb-10 pt-6">
|
||||
<view class="box-border min-h-screen w-screen overflow-hidden bg-page px-4 pb-10 pt-2">
|
||||
<!-- 自定义导航 -->
|
||||
<uh-navbar default-title="联系博主" title-color="text-gray-900" />
|
||||
|
||||
@@ -102,7 +98,7 @@ watch(socialConfig, () => {
|
||||
<view class="hero-wrap relative">
|
||||
<view class="absolute h-[220rpx] w-[220rpx] rounded-full bg-[rgba(185,228,36,0.32)] -right-8 -top-8" />
|
||||
<view class="absolute top-[150rpx] h-[180rpx] w-[180rpx] rounded-full bg-[rgba(215,249,76,0.45)] -left-10" />
|
||||
<view class="hero-card uh-global-card-glass relative flex flex-col items-center rounded-3xl px-6 pb-6 pt-10">
|
||||
<view class="hero-card uh-global-card-glass uh-shadow-xs relative flex flex-col items-center rounded-2xl px-6 pb-6 pt-10">
|
||||
<view class="avatar relative box-border h-[150rpx] w-[150rpx] overflow-hidden border-5 border-white/90 rounded-full shadow-lg">
|
||||
<image class="avatar-img h-full w-full" :src="bloggerInfo.avatar" mode="aspectFill" />
|
||||
</view>
|
||||
@@ -125,26 +121,26 @@ watch(socialConfig, () => {
|
||||
empty-text="暂无联系方式" empty-sub-text="" @refresh="handleGetData"
|
||||
/>
|
||||
<template v-else>
|
||||
<uh-section-title class="mb-3 mt-6 text-[30rpx]">
|
||||
<uh-section-title class="mb-3 mt-6">
|
||||
联系方式
|
||||
</uh-section-title>
|
||||
<view class="flex flex-col gap-3">
|
||||
<view
|
||||
v-for="item in result.filter(i => i.value)"
|
||||
:key="item.key"
|
||||
class="item uh-global-card-glass flex items-center gap-3 rounded-2xl px-4 py-3"
|
||||
class="box-border uh-global-card-glass uh-shadow-xs flex items-center gap-3 rounded-2xl px-4 py-3"
|
||||
@click="handleOnClick(item)"
|
||||
>
|
||||
<view class="tile h-[76rpx] w-[76rpx] flex shrink-0 items-center justify-center rounded-xl" :style="{ backgroundColor: platformMeta[item.key]?.color || '#8a8a7a' }">
|
||||
<text class="text-[30rpx] text-white font-bold">{{ platformMeta[item.key]?.letter || item.name.slice(0, 1) }}</text>
|
||||
</view>
|
||||
<view class="min-w-0 flex flex-1 flex-col">
|
||||
<text class="text-[24rpx] text-gray-400">{{ item.name }}</text>
|
||||
<text class="text-xs text-gray-500">{{ item.name }}</text>
|
||||
<view class="mt-1 break-all text-[26rpx] text-gray-900 leading-snug">
|
||||
{{ item.value }}
|
||||
</view>
|
||||
</view>
|
||||
<wd-icon name="copy" size="28rpx" color="#c8c2b4" class="shrink-0" />
|
||||
<wd-icon name="copy" size="28rpx" class="shrink-0 text-gray-400" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
/**
|
||||
* 免责声明页(源自旧项目 pagesA/disclaimers,新建复刻)
|
||||
*/
|
||||
import { computed } from 'vue'
|
||||
import { useAppConfigStore } from '@/store/appConfig'
|
||||
import { computed } from 'vue'
|
||||
import { useAppConfigStore } from '@/store/appConfig'
|
||||
|
||||
definePage({
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '免责声明',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
const appConfigStore = useAppConfigStore()
|
||||
const haloConfigs = computed(() => appConfigStore.configs)
|
||||
const appConfigStore = useAppConfigStore()
|
||||
const haloConfigs = computed(() => appConfigStore.configs)
|
||||
|
||||
const disclaimersContent = computed(() => {
|
||||
const basicConfig = haloConfigs.value.basicConfig as { disclaimers?: { content?: string } } | undefined
|
||||
const disclaimersContent = computed(() => {
|
||||
const basicConfig = haloConfigs.value.basicConfig as { disclaimers ?: { content ?: string } } | undefined
|
||||
return basicConfig?.disclaimers?.content || ''
|
||||
})
|
||||
})
|
||||
|
||||
const bloggerInfo = computed(() => {
|
||||
const blogger = haloConfigs.value.authorConfig?.blogger as { nickname?: string, email?: string } | undefined
|
||||
const bloggerInfo = computed(() => {
|
||||
const blogger = haloConfigs.value.authorConfig?.blogger as { nickname ?: string, email ?: string } | undefined
|
||||
return {
|
||||
nickname: blogger?.nickname || '',
|
||||
email: blogger?.email || '',
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
function copyText(content: string, tips = '复制成功') {
|
||||
function copyText(content : string, tips = '复制成功') {
|
||||
uni.setClipboardData({
|
||||
data: content,
|
||||
showToast: false,
|
||||
@@ -36,48 +36,51 @@ function copyText(content: string, tips = '复制成功') {
|
||||
uni.showToast({ icon: 'none', title: tips })
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="box-border min-h-screen bg-page ">
|
||||
<view class="box-border min-h-screen bg-page p-3 pt-2">
|
||||
<!-- 自定义导航 -->
|
||||
<uh-navbar default-title="免责声明" title-color="text-gray-900" />
|
||||
|
||||
<view class="w-full h-full uh-global-card-glass uh-shadow-xs rounded-xl">
|
||||
<!-- 通过配置 -->
|
||||
<view v-if="disclaimersContent" style="min-height: 100%;" v-html="disclaimersContent" />
|
||||
<view v-if="disclaimersContent" v-html="disclaimersContent" />
|
||||
|
||||
<!-- 静态写法 -->
|
||||
<view v-else class="box-border p-4 pt-0 text-sm text-gray-900 leading-7">
|
||||
<view class="item mt-6">
|
||||
<view v-else class="box-border p-4 pt-0 text-sm text-gray-900 leading-6">
|
||||
<view class="item mt-4">
|
||||
1、本应用属于个人非盈利性质的网站,所有转载的文章都以遵循原作者的版权声明注明了文章来源。
|
||||
</view>
|
||||
<view class="item mt-6">
|
||||
<view class="item mt-4">
|
||||
2、如果原文没有版权声明,按照目前互联网开放的原则,本博客将在不通知作者的情况下转载文章。
|
||||
</view>
|
||||
<view class="item mt-6">
|
||||
<view class="item mt-4">
|
||||
3、如果原文明确注明"禁止转载",本博客将不会转载。
|
||||
</view>
|
||||
<view class="item mt-6">
|
||||
<view class="item mt-4">
|
||||
4、如果本博客转载的文章不符合作者的版权声明或者作者不想让本博客转载您的文章,请邮件告知
|
||||
<text class="email mx-3 text-primary" @click="copyText(bloggerInfo.email, '电子邮箱已复制到剪贴板!')">{{ bloggerInfo.email }}</text>
|
||||
<text class="email mx-3 text-primary"
|
||||
@click="copyText(bloggerInfo.email, '电子邮箱已复制到剪贴板!')">{{ bloggerInfo.email }}</text>
|
||||
,博主将会在第一时间删除相关信息!
|
||||
</view>
|
||||
<view class="item mt-6">
|
||||
<view class="item mt-4">
|
||||
5、本博客转载文章仅为留做备份和知识点分享的目的。
|
||||
</view>
|
||||
<view class="item mt-6">
|
||||
<view class="item mt-4">
|
||||
6、本博客将尽力确保所提供信息的准确性及可靠性,但不保证信息的正确性和完整性,且不对因信息的不正确或遗漏导致的任何损失或损害承担相关责任。
|
||||
</view>
|
||||
<view class="item mt-6">
|
||||
<view class="item mt-4">
|
||||
7、本博客所发布、转载的文章,其版权均归原作者所有。如其他自媒体、网站或个人从本博客下载使用,请在转载有关文章时务必尊重该文章的著作权,保留本博客注明的"原文来源"或者自行去原文处复制版权声明,并自负版权等法律责任。
|
||||
</view>
|
||||
<view class="item mt-6">
|
||||
<view class="item mt-4">
|
||||
8、本博客的所有原创文章皆可以任意转载,但转载时务必请注明出处。
|
||||
</view>
|
||||
<view class="item mt-6">
|
||||
<view class="item mt-4">
|
||||
9、尊重原创,知识共享!
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -402,10 +402,10 @@
|
||||
</view>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<uh-glass-popup v-model="detail.show" position="center" custom-class="w-[90vw] rounded-xl !border">
|
||||
<uh-glass-popup v-model="detail.show" position="bottom" :z-index="999" custom-class="rounded-xl !border">
|
||||
<view class="relative w-full flex items-center justify-around box-border px-4 pt-4">
|
||||
<view class="w-full flex flex-col gap-y-1">
|
||||
<text class="text-md font-bold">小程序详情</text>
|
||||
<text class="text-md font-bold">站点详情</text>
|
||||
</view>
|
||||
<view
|
||||
class="absolute right-4 top-4 w-6 h-6 uh-global-card-glass shadow-none border rounded-lg text-center"
|
||||
@@ -416,26 +416,27 @@
|
||||
<scroll-view v-if="detail.data" :scroll-y="true" :show-scrollbar="false"
|
||||
class="box-border p-4 max-h-[60vh]">
|
||||
<view class="flex">
|
||||
<image class="poup-logo h-[140rpx] w-[140rpx] shrink-0 rounded-full"
|
||||
<image class="h-20 w-20 shrink-0 rounded-2xl uh-global-card-glass"
|
||||
:src="checkImageUrl(detail.data.spec.logo)" mode="aspectFill" />
|
||||
<view class="ml-4 flex flex-1 flex-col gap-y-1 justify-center">
|
||||
<view class="poup-name text-[34rpx] text-gray-900 font-bold">
|
||||
<view class="text-lg text-gray-900 font-bold">
|
||||
{{ detail.data.spec.displayName }}
|
||||
</view>
|
||||
<view class="text-xs text-gray-500">
|
||||
{{ detail.data.spec.groupName }}
|
||||
<view class="flex items-center gap-x-2">
|
||||
<text class="uh-global-card-glass border uh-shadow-xs text-xs text-gray-500 rounded-lg bg-secondary px-2 py-0.5 text-gray-900">{{ detail.data.spec.groupName }}</text>
|
||||
<text class="uh-global-card-glass border uh-shadow-xs text-xs text-gray-500 rounded-lg bg-secondary px-2 py-0.5 text-gray-900">
|
||||
复制地址
|
||||
</text>
|
||||
</view>
|
||||
<view @click="handleCopyLink(detail.data)">
|
||||
<text
|
||||
class="rounded-lg bg-secondary px-2 py-1 text-xs text-gray-900">{{ detail.data.spec.url }}</text>
|
||||
class="text-xs text-gray-900">{{ detail.data.spec.url }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="poup-desc mt-4 text-[28rpx] text-gray-600 leading-[1.6]">
|
||||
{{ detail.data.spec.description || '这个博主很懒,没写简介~' }}
|
||||
</view>
|
||||
<image class="poup-img mt-4 h-[320rpx] w-full rounded-xl"
|
||||
:src="calcSiteThumbnail(detail.data.spec.url)" mode="aspectFill" />
|
||||
</scroll-view>
|
||||
</uh-glass-popup>
|
||||
|
||||
@@ -500,7 +501,7 @@
|
||||
</view>
|
||||
|
||||
<!-- 小程序详情弹窗 -->
|
||||
<uh-glass-popup v-model="miniDetail.show" position="center" custom-class="w-[90vw] rounded-xl !border">
|
||||
<uh-glass-popup v-model="miniDetail.show" :z-index="999" position="bottom" custom-class="rounded-xl !border">
|
||||
<view class="relative w-full flex items-center justify-around box-border px-4 pt-4">
|
||||
<view class="w-full flex flex-col gap-y-1">
|
||||
<text class="text-md font-bold">小程序详情</text>
|
||||
|
||||
@@ -184,18 +184,18 @@
|
||||
|
||||
<wd-sticky>
|
||||
<scroll-view scroll-x class="w-full whitespace-nowrap" :show-scrollbar="false">
|
||||
<view class="flex gap-2 px-3 pb-1 pt-2">
|
||||
<view class="uh-global-card-glass uh-shadow-xs inline-block border rounded-2xl px-4 py-1.5 text-sm"
|
||||
<view class="box-border flex gap-2 px-3 pb-1 pt-2">
|
||||
<view class="flex-1 uh-global-card-glass uh-shadow-xs inline-flex border rounded-2xl px-4 py-1.5 text-sm"
|
||||
:class="activeType === '' ? 'bg-primary font-bold' : 'text-gray-500'" @click="activeType = ''">
|
||||
全部
|
||||
</view>
|
||||
<view v-for="(type) in typeOptions" :key="type.typeName || '__none__'"
|
||||
class="uh-global-card-glass uh-shadow-xs inline-flex items-center gap-1 border rounded-2xl px-4 py-1.5 text-sm"
|
||||
<view v-for="(type) in typeOptions" :key="type.typeDisplayName"
|
||||
class="flex-1 box-border uh-global-card-glass uh-shadow-xs inline-flex items-center gap-1 border rounded-2xl px-4 py-1.5 text-sm"
|
||||
:class="activeType === type.typeName ? 'bg-primary font-bold' : 'text-gray-500'"
|
||||
@click="activeType = activeType === type.typeName ? '' : type.typeName">
|
||||
<view v-if="type.typeColor" class="shrink-0 h-2 w-2 rounded-full"
|
||||
:style="{ backgroundColor: type.typeColor }" />
|
||||
<text class="shrink-0">{{ type.typeDisplayName }}</text>
|
||||
<view class="shrink-0">{{ type.typeDisplayName }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
@@ -1,83 +1,79 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 投票详情页(源自旧项目 pagesA/vote-detail,新建复刻)
|
||||
* 支持 single/multiple/pk 三种投票类型,已投票展示结果
|
||||
*/
|
||||
import { computed, ref } from 'vue'
|
||||
import { onLoad, onPullDownRefresh, onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
import { getVoteDetail, submitVote } from '@/api/uni-halo'
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
import { calcVotePercent, calcVoteState, VOTE_TYPES, voteCacheUtil } from '@/utils/vote'
|
||||
import { formatTime as formatTimeUtil } from '@/utils/formatTime'
|
||||
import type { IVote, IVoteDetail, IVoteOption } from '@/api/types/uni-halo'
|
||||
import { computed, ref } from 'vue'
|
||||
import { onLoad, onPullDownRefresh, onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
import { getVoteDetail, submitVote } from '@/api/uni-halo'
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
import { calcVotePercent, calcVoteState, VOTE_TYPES, voteCacheUtil } from '@/utils/vote'
|
||||
import { formatTime as formatTimeUtil } from '@/utils/formatTime'
|
||||
import type { IVote, IVoteDetail, IVoteOption } from '@/api/types/uni-halo'
|
||||
|
||||
definePage({
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '投票详情',
|
||||
enablePullDownRefresh: true,
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
/* ---------------- 状态 ---------------- */
|
||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||
const submitLoading = ref(false)
|
||||
const pageTitle = ref('加载中...')
|
||||
const safeAreaBottom = ref(24)
|
||||
const name = ref('')
|
||||
const detail = ref<unknown>(null)
|
||||
const vote = ref<(IVote & {
|
||||
spec?: {
|
||||
title?: string
|
||||
remark?: string
|
||||
type?: string
|
||||
maxVotes?: number
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
timeLimit?: string
|
||||
canAnonymously?: boolean
|
||||
options?: (IVoteOption & {
|
||||
id?: string
|
||||
title?: string
|
||||
count?: number
|
||||
checked?: boolean
|
||||
isVoted?: boolean
|
||||
disabled?: boolean
|
||||
_uh_percent?: number
|
||||
/* ---------------- 状态 ---------------- */
|
||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||
const submitLoading = ref(false)
|
||||
const pageTitle = ref('加载中...')
|
||||
const safeAreaBottom = ref(24)
|
||||
const name = ref('')
|
||||
const detail = ref<unknown>(null)
|
||||
const vote = ref<(IVote & {
|
||||
spec ?: {
|
||||
title ?: string
|
||||
remark ?: string
|
||||
type ?: string
|
||||
maxVotes ?: number
|
||||
startDate ?: string
|
||||
endDate ?: string
|
||||
timeLimit ?: string
|
||||
canAnonymously ?: boolean
|
||||
options ?: (IVoteOption & {
|
||||
id ?: string
|
||||
title ?: string
|
||||
count ?: number
|
||||
checked ?: boolean
|
||||
isVoted ?: boolean
|
||||
disabled ?: boolean
|
||||
_uh_percent ?: number
|
||||
})[]
|
||||
isVoted?: boolean
|
||||
hasEnded?: boolean
|
||||
disabled?: boolean
|
||||
_uh_type?: string
|
||||
_uh_state?: { state: string, color: string }
|
||||
isVoted ?: boolean
|
||||
hasEnded ?: boolean
|
||||
disabled ?: boolean
|
||||
_uh_type ?: string
|
||||
_uh_state ?: { state : string, color : string }
|
||||
}
|
||||
stats?: { voteCount?: number }
|
||||
}) | null>(null)
|
||||
const submitForm = ref<{ voteData: string[] }>({ voteData: [] })
|
||||
stats ?: { voteCount ?: number }
|
||||
}) | null>(null)
|
||||
const submitForm = ref<{ voteData : string[] }>({ voteData: [] })
|
||||
|
||||
/* ---------------- 计算属性 ---------------- */
|
||||
const isVoted = computed(() => voteCacheUtil.has(name.value))
|
||||
const isEnded = computed(() => vote.value?.spec?.hasEnded || false)
|
||||
/* ---------------- 计算属性 ---------------- */
|
||||
const isVoted = computed(() => voteCacheUtil.has(name.value))
|
||||
const isEnded = computed(() => vote.value?.spec?.hasEnded || false)
|
||||
|
||||
/* ---------------- 工具 ---------------- */
|
||||
function formatTime(date?: string, fmt = 'yyyy-MM-dd HH:mm'): string {
|
||||
/* ---------------- 工具 ---------------- */
|
||||
function formatTime(date ?: string, fmt = 'yyyy-MM-dd HH:mm') : string {
|
||||
// 与旧项目一致:yyyy-MM-dd HH:mm
|
||||
return date ? formatTimeUtil({ d: date, f: fmt }) : ''
|
||||
}
|
||||
}
|
||||
|
||||
function showToast(content: string) {
|
||||
function showToast(content : string) {
|
||||
uni.showToast({ icon: 'none', title: content, mask: true })
|
||||
}
|
||||
}
|
||||
|
||||
function handleCalcIsChecked(option: { id?: string }): boolean {
|
||||
function handleCalcIsChecked(option : { id ?: string }) : boolean {
|
||||
const data = voteCacheUtil.get(name.value)
|
||||
if (!data)
|
||||
return false
|
||||
return data.selected.includes(option.id || '')
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 数据加载 ---------------- */
|
||||
async function handleGetData() {
|
||||
/* ---------------- 数据加载 ---------------- */
|
||||
async function handleGetData() {
|
||||
updateLoadingStatus(DataLoadingStatusEnum.Loading)
|
||||
pageTitle.value = '加载中...'
|
||||
try {
|
||||
@@ -99,8 +95,8 @@ async function handleGetData() {
|
||||
|
||||
// 选项计算
|
||||
// 插件选项为 {id,title},票数在 VoteDetail.voteDataList / Vote.stats.voteDataList
|
||||
const countList = (detailRes.voteDataList || tempVote.stats?.voteDataList || []) as { id?: string, voteCount?: number }[]
|
||||
const countMap: Record<string, number> = {}
|
||||
const countList = (detailRes.voteDataList || tempVote.stats?.voteDataList || []) as { id ?: string, voteCount ?: number }[]
|
||||
const countMap : Record<string, number> = {}
|
||||
countList.forEach((item) => {
|
||||
if (item.id)
|
||||
countMap[item.id] = item.voteCount || 0
|
||||
@@ -141,10 +137,10 @@ async function handleGetData() {
|
||||
uni.stopPullDownRefresh()
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 交互 ---------------- */
|
||||
function handleSelectSingleOption(option: { id?: string }) {
|
||||
/* ---------------- 交互 ---------------- */
|
||||
function handleSelectSingleOption(option : { id ?: string }) {
|
||||
if (vote.value?.spec?._uh_state?.state === '未开始') {
|
||||
showToast('投票未开始')
|
||||
return
|
||||
@@ -157,9 +153,9 @@ function handleSelectSingleOption(option: { id?: string }) {
|
||||
item.checked = option.id === item.id
|
||||
})
|
||||
submitForm.value.voteData = vote.value!.spec!.options!.filter(x => x.checked).map(item => item.id || '')
|
||||
}
|
||||
}
|
||||
|
||||
function handleSelectCheckboxOption(option: { id?: string }) {
|
||||
function handleSelectCheckboxOption(option : { id ?: string }) {
|
||||
if (vote.value?.spec?._uh_state?.state === '未开始') {
|
||||
showToast('投票未开始')
|
||||
return
|
||||
@@ -181,13 +177,13 @@ function handleSelectCheckboxOption(option: { id?: string }) {
|
||||
}
|
||||
})
|
||||
submitForm.value.voteData = vote.value!.spec!.options!.filter(x => x.checked).map(item => item.id || '')
|
||||
}
|
||||
}
|
||||
|
||||
function handleSubmitTip(text: string) {
|
||||
function handleSubmitTip(text : string) {
|
||||
showToast(text)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
async function handleSubmit() {
|
||||
if (!vote.value?.spec?.canAnonymously) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
@@ -232,64 +228,57 @@ async function handleSubmit() {
|
||||
finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 生命周期 ---------------- */
|
||||
onLoad((options) => {
|
||||
/* ---------------- 生命周期 ---------------- */
|
||||
onLoad((options) => {
|
||||
name.value = options?.name || ''
|
||||
// #ifndef H5
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
safeAreaBottom.value = systemInfo.safeAreaInsets?.bottom ? systemInfo.safeAreaInsets.bottom + 12 : 24
|
||||
// #endif
|
||||
handleGetData()
|
||||
})
|
||||
})
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
onPullDownRefresh(() => {
|
||||
handleGetData()
|
||||
})
|
||||
})
|
||||
|
||||
onShareAppMessage(() => ({
|
||||
onShareAppMessage(() => ({
|
||||
path: `/pages-blog/vote-detail/vote-detail?name=${name.value}`,
|
||||
title: vote.value?.spec?.title || '来投个票吧',
|
||||
imageUrl: '',
|
||||
}))
|
||||
}))
|
||||
|
||||
onShareTimeline(() => ({
|
||||
onShareTimeline(() => ({
|
||||
title: vote.value?.spec?.title || '来投个票吧',
|
||||
query: name.value ? `name=${name.value}` : '',
|
||||
imageUrl: '',
|
||||
}))
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="app-page box-border min-h-screen w-screen flex flex-col bg-page pb-[160rpx]">
|
||||
<view class="box-border min-h-screen w-screen flex flex-col bg-page pb-safe">
|
||||
<!-- 自定义导航 -->
|
||||
<uh-navbar :default-title="pageTitle" title-color="text-gray-900" />
|
||||
|
||||
<!-- 加载/错误/空占位(状态机) -->
|
||||
<view v-if="loadingStatus !== 'success'">
|
||||
<uh-data-loading
|
||||
:loading-status="loadingStatus"
|
||||
empty-text="未查询到数据"
|
||||
@refresh="handleGetData"
|
||||
/>
|
||||
</view>
|
||||
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus"
|
||||
empty-text="未查询到数据" @refresh="handleGetData" />
|
||||
|
||||
<block v-else>
|
||||
<template v-if="vote">
|
||||
<view v-else class="box-border px-3 pt-2 pb-16 flex flex-col gap-y-3">
|
||||
<!-- 投票信息 -->
|
||||
<view class="uh-global-card-glass box-border mx-6 mb-6 flex flex-col rounded-2xl p-6">
|
||||
<view class="sub-title relative box-border pl-6 text-[30rpx]">
|
||||
投票信息
|
||||
</view>
|
||||
<view class="vote-card-body mt-3 flex flex-col gap-3 rounded-xl bg-page p-6 text-[28rpx] text-[#3f3f3f]">
|
||||
<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="info-row">
|
||||
<text>投票类型:</text>
|
||||
<text class="tag">{{ vote.spec?._uh_type }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text>投票状态:</text>
|
||||
<text class="tag" :style="{ color: vote.spec?._uh_state?.color }">{{ vote.spec?._uh_state?.state }}</text>
|
||||
<text class="tag"
|
||||
:style="{ color: vote.spec?._uh_state?.color }">{{ vote.spec?._uh_state?.state }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text>投票方式:</text>
|
||||
@@ -308,45 +297,43 @@ onShareTimeline(() => ({
|
||||
</view>
|
||||
|
||||
<!-- 投票内容 -->
|
||||
<view class="uh-global-card-glass box-border mx-6 mb-6 flex flex-col rounded-2xl p-6">
|
||||
<view class="sub-title relative box-border pl-6 text-[30rpx]">
|
||||
投票内容
|
||||
</view>
|
||||
<view class="sub-content mb-3 pt-3 text-[30rpx] text-[#2b2f33] font-bold">
|
||||
<view class="uh-global-card-glass box-border flex flex-col rounded-2xl p-3 gap-3">
|
||||
<uh-section-title> 投票内容 </uh-section-title>
|
||||
<view class="box-border flex flex-col gap-y-2 p-4 rounded-xl bg-gray-100">
|
||||
<view class="text-sm text-gray-900 font-bold">
|
||||
{{ vote.spec?.title }}
|
||||
</view>
|
||||
<view v-if="vote.spec?.remark" class="sub-remark mb-9 pt-3 text-[28rpx] text-[#3f3f3f]">
|
||||
<view v-if="vote.spec?.remark" class="text-xs text-gray-500">
|
||||
{{ vote.spec.remark }}
|
||||
</view>
|
||||
<view class="sub-title relative box-border pl-6 text-[30rpx]">
|
||||
投票选项
|
||||
<text v-if="vote.spec?.type === 'multiple'" class="sub-title-count text-[24rpx] font-normal">(最多选择 {{ vote.spec?.maxVotes }} 项)</text>
|
||||
</view>
|
||||
<view class="options mt-6 flex flex-col gap-4">
|
||||
<view class="w-full flex flex-col gap-y-2">
|
||||
<view class="relative box-border text-sm flex items-center gap-x-2">
|
||||
投票选项
|
||||
<text v-if="vote.spec?.type === 'multiple'"
|
||||
class="text-xs font-normal">(最多选择
|
||||
{{ vote.spec?.maxVotes }} 项)
|
||||
</text>
|
||||
</view>
|
||||
<view class="options flex flex-col gap-3">
|
||||
<!-- PK 对抗条(与旧项目 pk-container 一致) -->
|
||||
<view v-if="vote.spec?.type === 'pk'" class="pk-container box-border flex w-full">
|
||||
<view
|
||||
v-for="(option, optionIndex) in vote.spec?.options"
|
||||
:key="optionIndex"
|
||||
class="radio-item flex-grow"
|
||||
:class="optionIndex === 0 ? 'radio-left' : 'radio-right'"
|
||||
:style="{ width: `${option._uh_percent}%` }"
|
||||
>
|
||||
<view class="option-item box-border w-full rounded-xl p-6" :class="optionIndex === 0 ? 'option-item-left' : 'option-item-right'">
|
||||
<view v-for="(option, optionIndex) in vote.spec?.options" :key="optionIndex"
|
||||
class="radio-item flex-grow" :class="optionIndex === 0 ? 'radio-left' : 'radio-right'"
|
||||
:style="{ width: `${option._uh_percent}%` }">
|
||||
<view class="option-item box-border w-full rounded-xl py-3 px-3"
|
||||
:class="optionIndex === 0 ? 'option-item-left' : 'option-item-right'">
|
||||
{{ option._uh_percent }}%
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<template v-if="isVoted || isEnded">
|
||||
<view
|
||||
v-for="(option, optionIndex) in vote.spec?.options"
|
||||
:key="optionIndex"
|
||||
class="is-voted-item relative box-border min-h-[72rpx] overflow-hidden rounded-xl text-[24rpx]"
|
||||
:class="option.checked ? 'bg-primary/40 text-[#4d7c0f] font-bold' : 'bg-[#e5e5e5]/75'"
|
||||
:style="{ '--percent': `${option._uh_percent}%` }"
|
||||
>
|
||||
<view class="is-voted-item-content relative z-2 box-border min-h-[72rpx] px-6 py-3">
|
||||
<view v-for="(option, optionIndex) in vote.spec?.options" :key="optionIndex"
|
||||
class="is-voted-item relative box-border overflow-hidden rounded-xl text-xs"
|
||||
:class="option.checked ? 'bg-primary text-gray-900 font-bold' : 'bg-gray-100'"
|
||||
:style="{ '--percent': `${option._uh_percent}%` }">
|
||||
<view class="is-voted-item-content relative z-2 box-border px-4 py-3">
|
||||
<view class="flex items-center justify-between">
|
||||
<view class="flex-1 text-left">
|
||||
{{ vote.spec?.type === 'pk' ? `选项${optionIndex + 1}:` : '' }}{{ option.title }}
|
||||
@@ -359,57 +346,59 @@ onShareTimeline(() => ({
|
||||
</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<view
|
||||
v-for="(option, optionIndex) in vote.spec?.options"
|
||||
:key="optionIndex"
|
||||
class="vote-select-option box-border rounded-xl bg-[#f3f4f6] px-6 py-5 text-[24rpx]"
|
||||
<view v-for="(option, optionIndex) in vote.spec?.options" :key="optionIndex"
|
||||
class="vote-select-option box-border rounded-xl bg-gray-100 px-6 py-5 text-xs"
|
||||
:class="option.checked ? 'border-2 border-primary bg-primary/15 text-primary font-bold' : ''"
|
||||
@click="vote.spec?.type === 'multiple' ? handleSelectCheckboxOption(option) : handleSelectSingleOption(option)"
|
||||
>
|
||||
@click="vote.spec?.type === 'multiple' ? handleSelectCheckboxOption(option) : handleSelectSingleOption(option)">
|
||||
{{ vote.spec?.type === 'pk' ? `选项${optionIndex + 1}:` : '' }}{{ option.title }}
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 投票统计 -->
|
||||
<view class="uh-global-card-glass box-border mx-6 mb-6 flex flex-col rounded-2xl p-6">
|
||||
<view class="sub-title relative box-border pl-6 text-[30rpx]">
|
||||
投票统计
|
||||
</view>
|
||||
<view class="stat-text mt-3 text-[26rpx] text-[#606266]">
|
||||
<view class="uh-global-card-glass box-border flex flex-col rounded-2xl p-3">
|
||||
<uh-section-title> 投票统计 </uh-section-title>
|
||||
<view class="stat-text mt-3 text-xs text-gray-600">
|
||||
{{ vote.stats?.voteCount || 0 }} 人已参与
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<view class="vote-submit fixed bottom-0 left-0 z-99 box-border w-screen border-t border-black/5 bg-white/90 px-9 py-6 shadow-sm backdrop-blur" :style="{ paddingBottom: `${safeAreaBottom}rpx` }">
|
||||
<wd-button v-if="isVoted" disabled block>
|
||||
<view class="fixed bottom-0 left-0 z-99 box-border w-screen pb-safe px-3">
|
||||
<view
|
||||
class="uh-global-card-glass border rounded-xl w-full flex items-center justify-center gap-x-2 mb-2">
|
||||
<uh-button v-if="isVoted" custom-class="flex-1 py-2 !rounded-xl">
|
||||
您已参与投票
|
||||
</wd-button>
|
||||
<wd-button v-else-if="vote.spec?._uh_state?.state === '未开始'" plain block type="warning" @click="handleSubmitTip('投票未开始')">
|
||||
</uh-button>
|
||||
<uh-button v-else-if="vote.spec?._uh_state?.state === '未开始'" custom-class="flex-1 py-2 !rounded-xl"
|
||||
@click="handleSubmitTip('投票未开始')">
|
||||
投票未开始
|
||||
</wd-button>
|
||||
<wd-button v-else-if="vote.spec?._uh_state?.state === '已结束'" plain block type="danger" @click="handleSubmitTip('投票已结束')">
|
||||
</uh-button>
|
||||
<uh-button v-else-if="vote.spec?._uh_state?.state === '已结束'" custom-class="flex-1 py-2 !rounded-xl"
|
||||
@click="handleSubmitTip('投票已结束')">
|
||||
投票已结束
|
||||
</wd-button>
|
||||
<wd-button v-else-if="!vote.spec?.canAnonymously" plain block type="danger" @click="handleSubmit()">
|
||||
</uh-button>
|
||||
<uh-button v-else-if="!vote.spec?.canAnonymously" custom-class="flex-1 py-2 !rounded-xl"
|
||||
@click="handleSubmit()">
|
||||
不支持匿名投票
|
||||
</wd-button>
|
||||
<wd-button v-else-if="submitForm.voteData.length === 0" plain block @click="handleSubmitTip('请选择选项')">
|
||||
</uh-button>
|
||||
<uh-button v-else-if="submitForm.voteData.length === 0" custom-class="flex-1 py-2 !rounded-xl"
|
||||
@click="handleSubmitTip('请选择选项')">
|
||||
提交投票(请选择选项)
|
||||
</wd-button>
|
||||
<wd-button v-else block type="primary" :loading="submitLoading" :disabled="submitLoading" @click="handleSubmit()">
|
||||
</uh-button>
|
||||
<uh-button v-else custom-class="flex-1 py-2 !rounded-xl" @click="handleSubmit()">
|
||||
提交投票
|
||||
</wd-button>
|
||||
</uh-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.vote-card {
|
||||
.vote-card {
|
||||
.sub-title {
|
||||
&::before {
|
||||
content: '';
|
||||
@@ -456,5 +445,5 @@ onShareTimeline(() => ({
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+102
-137
@@ -1,72 +1,68 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 投票列表页(源自旧项目 pagesA/votes,新建复刻)
|
||||
* 展示投票列表,支持搜索 + 类型/状态/排序/是否已投筛选,每个投票项用 uh-vote-card 渲染
|
||||
*/
|
||||
import { computed, ref } from 'vue'
|
||||
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
|
||||
import { getVoteList } from '@/api/uni-halo'
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
import { NeedPluginIds } from '@/hooks/usePluginAvailable'
|
||||
import { useAppConfigStore } from '@/store/appConfig'
|
||||
import { debounce } from '@/utils/debounce'
|
||||
import { calcVoteState, VOTE_TYPES, voteCacheUtil } from '@/utils/vote'
|
||||
import type { IVoteItem } from '@/api/types/uni-halo'
|
||||
import { computed, ref } from 'vue'
|
||||
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
|
||||
import { getVoteList } from '@/api/uni-halo'
|
||||
import { DataLoadingStatusEnum, useDataLoadingStatus } from '@/hooks/useDataLoadingStatus'
|
||||
import { NeedPluginIds } from '@/hooks/usePluginAvailable'
|
||||
import { useAppConfigStore } from '@/store/appConfig'
|
||||
import { debounce } from '@/utils/debounce'
|
||||
import { calcVoteState, VOTE_TYPES, voteCacheUtil } from '@/utils/vote'
|
||||
import type { IVoteItem } from '@/api/types/uni-halo'
|
||||
|
||||
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-vote,参考 gallery 对象传参模式) */
|
||||
const { pluginId, checking, tips, available: uniHaloPluginAvailable, check: checkPluginAvailable } = usePluginAvailable({
|
||||
/** 依赖插件(plugin-vote,参考 gallery 对象传参模式) */
|
||||
const { pluginId, checking, tips, available: uniHaloPluginAvailable, check: checkPluginAvailable } = usePluginAvailable({
|
||||
pluginId: NeedPluginIds.PluginVote,
|
||||
tips: '检测到当前插件没有安装或者启用,无法使用投票功能哦,请联系管理员',
|
||||
})
|
||||
})
|
||||
|
||||
/** 重新检测插件:可用则拉取数据(供 uh-plugin-unavailable 刷新按钮) */
|
||||
async function handlePluginRefresh() {
|
||||
/** 重新检测插件:可用则拉取数据(供 uh-plugin-unavailable 刷新按钮) */
|
||||
async function handlePluginRefresh() {
|
||||
if (await checkPluginAvailable())
|
||||
handleGetData()
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 状态 ---------------- */
|
||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||
const dataList = ref<IVoteItem[]>([])
|
||||
const hasNext = ref(false)
|
||||
const isLoadMore = ref(false)
|
||||
const loadMoreText = ref('加载中...')
|
||||
/** 是否已投过滤(前端过滤,接口无此参数) */
|
||||
const filterIsVoted = ref<boolean | undefined>(undefined)
|
||||
const queryParams = ref<Record<string, unknown>>({
|
||||
/* ---------------- 状态 ---------------- */
|
||||
const { loadingStatus, updateLoadingStatus } = useDataLoadingStatus()
|
||||
const dataList = ref<IVoteItem[]>([])
|
||||
const hasNext = ref(false)
|
||||
const isLoadMore = ref(false)
|
||||
const loadMoreText = ref('加载中...')
|
||||
/** 是否已投过滤(前端过滤,接口无此参数) */
|
||||
const filterIsVoted = ref<boolean | undefined>(undefined)
|
||||
const queryParams = ref<Record<string, unknown>>({
|
||||
keyword: '',
|
||||
page: 1,
|
||||
size: 10,
|
||||
sort: undefined,
|
||||
type: undefined,
|
||||
hasEnded: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
/* ---------------- 筛选 ---------------- */
|
||||
interface IFilterOption {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
/* ---------------- 筛选 ---------------- */
|
||||
interface IFilterOption {
|
||||
label : string
|
||||
value : string
|
||||
}
|
||||
|
||||
interface IFilterItem {
|
||||
key: 'type' | 'hasEnded' | 'sort' | 'isVoted'
|
||||
label: string
|
||||
options: IFilterOption[]
|
||||
}
|
||||
interface IFilterItem {
|
||||
key : 'type' | 'hasEnded' | 'sort' | 'isVoted'
|
||||
label : string
|
||||
options : IFilterOption[]
|
||||
}
|
||||
|
||||
/** 筛选维度(与旧项目 tm-dropDownMenu 一致) */
|
||||
const filterConfig: IFilterItem[] = [
|
||||
/** 筛选维度(与旧项目 tm-dropDownMenu 一致) */
|
||||
const filterConfig : IFilterItem[] = [
|
||||
{
|
||||
key: 'type',
|
||||
label: '类型',
|
||||
@@ -104,29 +100,29 @@ const filterConfig: IFilterItem[] = [
|
||||
{ label: '已投票', value: 'true' },
|
||||
],
|
||||
},
|
||||
]
|
||||
]
|
||||
|
||||
/** 各维度当前选中值(空串 = 全部) */
|
||||
const filterValues = ref<Record<string, string>>({ type: '', hasEnded: '', sort: '', isVoted: '' })
|
||||
/** 各维度当前选中值(空串 = 全部) */
|
||||
const filterValues = ref<Record<string, string>>({ type: '', hasEnded: '', sort: '', isVoted: '' })
|
||||
|
||||
/** 当前选中中文标签(用于筛选栏展示) */
|
||||
const filterLabels = computed(() => {
|
||||
const map: Record<string, string> = {}
|
||||
/** 当前选中中文标签(用于筛选栏展示) */
|
||||
const filterLabels = computed(() => {
|
||||
const map : Record<string, string> = {}
|
||||
for (const f of filterConfig) {
|
||||
const cur = filterValues.value[f.key]
|
||||
map[f.key] = f.options.find(o => o.value === cur)?.label || '全部'
|
||||
}
|
||||
return map
|
||||
})
|
||||
})
|
||||
|
||||
/** 筛选弹层 */
|
||||
const filterPopup = ref<{ show: boolean, item: IFilterItem | null }>({ show: false, item: null })
|
||||
/** 筛选弹层 */
|
||||
const filterPopup = ref<{ show : boolean, item : IFilterItem | null }>({ show: false, item: null })
|
||||
|
||||
function handleOpenFilter(item: IFilterItem) {
|
||||
function handleOpenFilter(item : IFilterItem) {
|
||||
filterPopup.value = { show: true, item }
|
||||
}
|
||||
}
|
||||
|
||||
function handleSelectFilter(option: IFilterOption) {
|
||||
function handleSelectFilter(option : IFilterOption) {
|
||||
const item = filterPopup.value.item
|
||||
if (!item)
|
||||
return
|
||||
@@ -146,24 +142,24 @@ function handleSelectFilter(option: IFilterOption) {
|
||||
queryParams.value.page = 1
|
||||
isLoadMore.value = false
|
||||
handleGetData()
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 搜索 ---------------- */
|
||||
/** 实时搜索:输入防抖 400ms 后触发(对应旧版 tm-search 的 @input) */
|
||||
const handleOnInput = debounce(() => {
|
||||
/* ---------------- 搜索 ---------------- */
|
||||
/** 实时搜索:输入防抖 400ms 后触发(对应旧版 tm-search 的 @input) */
|
||||
const handleOnInput = debounce(() => {
|
||||
queryParams.value.page = 1
|
||||
isLoadMore.value = false
|
||||
handleGetData()
|
||||
}, 400)
|
||||
}, 400)
|
||||
|
||||
function handleOnSearch() {
|
||||
function handleOnSearch() {
|
||||
queryParams.value.page = 1
|
||||
isLoadMore.value = false
|
||||
handleGetData()
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 数据加载 ---------------- */
|
||||
async function handleGetData() {
|
||||
/* ---------------- 数据加载 ---------------- */
|
||||
async function handleGetData() {
|
||||
if (calcAuditModeEnabled.value) {
|
||||
updateLoadingStatus(
|
||||
dataList.value.length === 0 ? DataLoadingStatusEnum.Empty : DataLoadingStatusEnum.Success,
|
||||
@@ -223,19 +219,19 @@ async function handleGetData() {
|
||||
uni.stopPullDownRefresh()
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 卡片点击跳详情(与旧项目 VoteCard @on-click 一致) */
|
||||
function handleOnVoteClick(vote: IVoteItem) {
|
||||
/** 卡片点击跳详情(与旧项目 VoteCard @on-click 一致) */
|
||||
function handleOnVoteClick(vote : IVoteItem) {
|
||||
const name = vote.metadata?.name
|
||||
if (!name)
|
||||
return
|
||||
uni.navigateTo({
|
||||
url: `/pages-blog/vote-detail/vote-detail?name=${name}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function handleToTopPage(duration = 500) {
|
||||
function handleToTopPage(duration = 500) {
|
||||
uni.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration,
|
||||
@@ -243,19 +239,19 @@ function handleToTopPage(duration = 500) {
|
||||
console.error('回顶失败', err)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 生命周期 ---------------- */
|
||||
onLoad(async () => {
|
||||
/* ---------------- 生命周期 ---------------- */
|
||||
onLoad(async () => {
|
||||
await checkPluginAvailable()
|
||||
if (!uniHaloPluginAvailable.value) {
|
||||
uni.stopPullDownRefresh()
|
||||
return
|
||||
}
|
||||
handleGetData()
|
||||
})
|
||||
})
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
onPullDownRefresh(() => {
|
||||
if (!uniHaloPluginAvailable.value) {
|
||||
uni.stopPullDownRefresh()
|
||||
return
|
||||
@@ -263,9 +259,9 @@ onPullDownRefresh(() => {
|
||||
isLoadMore.value = false
|
||||
queryParams.value.page = 1
|
||||
handleGetData()
|
||||
})
|
||||
})
|
||||
|
||||
onReachBottom(() => {
|
||||
onReachBottom(() => {
|
||||
if (!uniHaloPluginAvailable.value)
|
||||
return
|
||||
if (calcAuditModeEnabled.value) {
|
||||
@@ -280,7 +276,7 @@ onReachBottom(() => {
|
||||
else {
|
||||
uni.showToast({ icon: 'none', title: '没有更多数据了' })
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -288,95 +284,64 @@ onReachBottom(() => {
|
||||
<!-- 自定义导航 -->
|
||||
<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>
|
||||
<!-- 顶部搜索框 -->
|
||||
<view class="box-border w-screen px-3 pt-2">
|
||||
<view class="uh-global-card-glass flex h-9 items-center gap-3 rounded-full px-5">
|
||||
<wd-icon name="search" size="16px" />
|
||||
<input
|
||||
v-model="queryParams.keyword as string"
|
||||
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="flex items-center" @click="queryParams.keyword = ''; handleOnSearch()">
|
||||
<input v-model="queryParams.keyword as string" 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="flex items-center"
|
||||
@click="queryParams.keyword = ''; handleOnSearch()">
|
||||
<wd-icon name="close" size="14px" />
|
||||
</view>
|
||||
</view>
|
||||
<!-- 筛选栏 -->
|
||||
<view class="flex items-center justify-between py-2">
|
||||
<view
|
||||
v-for="f in filterConfig"
|
||||
:key="f.key"
|
||||
class="flex flex-1 items-center justify-center gap-1"
|
||||
@click="handleOpenFilter(f)"
|
||||
>
|
||||
<text class="text-[24rpx]" :class="filterValues[f.key] ? 'text-primary font-bold' : 'text-gray-600'">
|
||||
<view class="box-border flex items-center justify-between mt-1 py-2 gap-x-2">
|
||||
<view v-for="f in filterConfig" :key="f.key"
|
||||
class="uh-global-card-glass border rounded-full box-border flex flex-1 items-center justify-center gap-1 px-2 py-1 text-gray-500"
|
||||
:class="[filterValues[f.key]?'bg-secondary text-gray-900 font-bold':'bg-white/80 text-gray-600']" @click="handleOpenFilter(f)">
|
||||
<text class="text-xs truncate" >
|
||||
{{ filterLabels[f.key] }}
|
||||
</text>
|
||||
<wd-icon name="arrow-down" size="10px" color="#9ca3af" />
|
||||
<wd-icon name="arrow-down" size="10px" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载/错误/空占位(状态机) -->
|
||||
<view v-if="loadingStatus !== 'success'">
|
||||
<uh-data-loading
|
||||
:loading-status="loadingStatus"
|
||||
empty-text="博主还未发布投票~"
|
||||
@refresh="handleGetData"
|
||||
/>
|
||||
</view>
|
||||
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus" empty-text="博主还未发布投票~" min-height="65vh" @refresh="handleGetData" />
|
||||
|
||||
<view v-else class="content flex flex-col gap-4 p-3">
|
||||
<view v-else class="box-border flex flex-col gap-4 p-3">
|
||||
<block v-if="dataList.length !== 0">
|
||||
<uh-vote-card
|
||||
v-for="vote in dataList"
|
||||
:key="vote.metadata?.name"
|
||||
:vote="vote"
|
||||
@click="handleOnVoteClick(vote)"
|
||||
/>
|
||||
<view class="load-text py-5 text-center text-[24rpx] text-gray-400">
|
||||
<uh-vote-card v-for="vote in dataList" :key="vote.metadata?.name" :vote="vote"
|
||||
@click="handleOnVoteClick(vote)" />
|
||||
<view class="box-border py-5 text-center text-xs text-gray-400">
|
||||
{{ loadMoreText }}
|
||||
</view>
|
||||
<view
|
||||
class="to-top-btn uh-global-card-glass fixed bottom-[100rpx] right-6 z-6 flex h-[72rpx] w-[72rpx] items-center justify-center rounded-full"
|
||||
@click="handleToTopPage()"
|
||||
>
|
||||
<wd-icon name="arrow-up" size="20px" color="#6b7280" />
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 筛选弹层 -->
|
||||
<wd-popup v-model="filterPopup.show" position="bottom" custom-style="border-radius: 24rpx 24rpx 0 0;">
|
||||
<view v-if="filterPopup.item" class="box-border p-6 pb-10">
|
||||
<view class="mb-4 text-center text-[30rpx] font-bold text-gray-900">
|
||||
<uh-glass-popup v-model="filterPopup.show" :z-index="99" position="bottom" custom-class="rounded-2xl">
|
||||
<view v-if="filterPopup.item" class="box-border p-4">
|
||||
<view class="mb-4 text-center text-md font-bold text-gray-900">
|
||||
{{ filterPopup.item.label }}
|
||||
</view>
|
||||
<view class="flex flex-col gap-2">
|
||||
<view
|
||||
v-for="opt in filterPopup.item.options"
|
||||
:key="opt.label"
|
||||
class="box-border rounded-xl px-5 py-3 text-center text-[28rpx]"
|
||||
:class="filterValues[filterPopup.item.key] === opt.value ? 'bg-primary/15 text-primary font-bold' : 'bg-[#f6f3ee] text-gray-700'"
|
||||
@click="handleSelectFilter(opt)"
|
||||
>
|
||||
<view v-for="opt in filterPopup.item.options" :key="opt.label"
|
||||
class="uh-global-card-glass border box-border rounded-xl px-5 py-2 text-center text-sm"
|
||||
:class="filterValues[filterPopup.item.key] === opt.value ? 'bg-primary text-gray-900 font-bold' : 'text-gray-700'"
|
||||
@click="handleSelectFilter(opt)">
|
||||
{{ opt.label }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</uh-glass-popup>
|
||||
</view>
|
||||
</template>
|
||||
@@ -12,6 +12,7 @@
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '分类',
|
||||
navigationStyle: 'custom',
|
||||
enablePullDownRefresh: true,
|
||||
backgroundColor: '#f6f3ee',
|
||||
},
|
||||
@@ -151,11 +152,13 @@
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="box-border min-h-screen w-screen flex flex-col bg-page p-3">
|
||||
<view class="box-border min-h-screen w-screen flex flex-col bg-page">
|
||||
<uh-navbar :use-back="false" default-title="分类" title-color="text-gray-900"></uh-navbar>
|
||||
|
||||
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus" />
|
||||
|
||||
<block v-else>
|
||||
<view class="grid grid-cols-2 gap-2.5">
|
||||
<view class="box-border grid grid-cols-2 gap-2.5 p-3 pt-0">
|
||||
<view v-for="(item, index) in dataList" :key="index"
|
||||
class="uh-global-card-glass relative box-border w-full overflow-hidden rounded-xl"
|
||||
@click="handleToCategory(item)">
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '图库',
|
||||
navigationStyle: 'custom',
|
||||
enablePullDownRefresh: true,
|
||||
},
|
||||
})
|
||||
@@ -191,11 +192,13 @@
|
||||
|
||||
<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>
|
||||
<wd-sticky v-if="category.list.length!==0">
|
||||
<scroll-view :scroll-x="true" class="w-full whitespace-nowrap pt-3">
|
||||
<wd-sticky v-if="category.list.length!==0" class="w-full">
|
||||
<scroll-view :scroll-x="true" class="w-full whitespace-nowrap pt-2">
|
||||
<view v-for="(cate, index) in category.list" :key="cate.spec.displayName"
|
||||
class="uh-global-card-glass uh-shadow-xs mb-1 ml-3 inline-block border rounded-2xl px-4 py-1 text-sm"
|
||||
:class="{ 'bg-primary text-gray-900 font-bold': index === category.activeIndex }"
|
||||
|
||||
@@ -210,17 +210,14 @@
|
||||
min-height="36vh" @refresh="handleQuery" />
|
||||
|
||||
<block v-else>
|
||||
<view class="flex flex-col gap-y-3 p-3 pt-0" :class="globalAppSettings.layout.home">
|
||||
<view class="box-border flex flex-col gap-y-3 p-3 pt-0" :class="globalAppSettings.layout.home">
|
||||
<uh-article-card v-for="(article, index) in articleList" :key="index"
|
||||
from="home" :article="article" :audit-mode="calcAuditModeEnabled"
|
||||
/>
|
||||
</view>
|
||||
<view class="load-text mt-3 pb-5 text-center text-xs text-gray-400">
|
||||
<view class="box-border mt-3 pb-5 text-center text-xs text-gray-400">
|
||||
{{ loadMoreText }}
|
||||
</view>
|
||||
<view v-if="articleList.length > 10" class="to-top-btn" @click="handleToTopPage()">
|
||||
<wd-icon name="arrow-up" size="20px" color="#03a9f4" />
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
<uh-notify-dialog />
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '瞬间',
|
||||
navigationStyle: 'custom',
|
||||
enablePullDownRefresh: true,
|
||||
},
|
||||
})
|
||||
@@ -287,30 +288,27 @@
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="box-border min-h-screen w-screen flex flex-col bg-page py-3">
|
||||
<view class="box-border min-h-screen w-screen flex flex-col bg-page ">
|
||||
<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="handlePluginRefresh" />
|
||||
|
||||
<template v-else>
|
||||
<!-- 加载失败(可重试) -->
|
||||
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus"
|
||||
min-height="60vh" @refresh="handleGetData" />
|
||||
|
||||
<view v-else class="flex flex-col gap-3 px-3">
|
||||
<view v-if="dataList.length === 0"
|
||||
class="min-h-[70vh] w-full flex items-center justify-center content-empty">
|
||||
<wd-empty :description="t('common.empty')" />
|
||||
</view>
|
||||
<view v-else class="box-border flex flex-col gap-3 p-3 pt-0">
|
||||
|
||||
<block v-else>
|
||||
<!-- 瞬间卡片-->
|
||||
<view v-for="moment in dataList" :key="moment.metadata.name"
|
||||
class="moment-card uh-shadow-xs overflow-hidden rounded-[24rpx] bg-white">
|
||||
class="uh-global-card-glass uh-shadow-xs overflow-hidden rounded-xl">
|
||||
<!-- 作者 -->
|
||||
<view class="box-border flex items-center px-4 pt-4">
|
||||
<view class="flex-1 flex items-center">
|
||||
<image class="avatar h-[72rpx] w-[72rpx] shrink-0 rounded-full"
|
||||
:src="checkAvatarUrl(moment.owner?.avatar || bloggerInfo.avatar)"
|
||||
mode="aspectFill" />
|
||||
:src="checkAvatarUrl(moment.owner?.avatar || bloggerInfo.avatar)" mode="aspectFill" />
|
||||
<view class="ml-3 flex flex-col">
|
||||
<view class="text-sm text-gray-900 font-bold">
|
||||
{{ moment.owner?.displayName || bloggerInfo.nickname }}
|
||||
@@ -327,7 +325,7 @@
|
||||
|
||||
<!-- 正文-->
|
||||
<view class="box-border px-4 pt-3">
|
||||
<view class="relative box-border bg-gray-100 p-2 rounded-lg">
|
||||
<view class="relative box-border bg-page p-3 rounded-lg">
|
||||
<mp-html lazy-load :domain="markdownConfig.domain ?? ''"
|
||||
:loading-img="markdownConfig.loadingGif" scroll-table selectable
|
||||
:tag-style="markdownConfig.tagStyle" :container-style="markdownConfig.containStyle"
|
||||
@@ -339,7 +337,7 @@
|
||||
|
||||
<!-- 图片 -->
|
||||
<view v-if="moment.images && moment.images.length !== 0"
|
||||
class="images flex flex-wrap items-start px-4 pt-3">
|
||||
class="box-border flex flex-wrap items-start px-3 pt-3">
|
||||
<view v-for="(image, mediumIndex) in moment.images" :key="mediumIndex"
|
||||
class="image-item box-border p-1"
|
||||
:class="moment.images && moment.images.length === 1 ? 'h-[350rpx] w-full' : (moment.images && moment.images.length === 2 ? 'h-[250rpx] w-1/2' : 'h-[200rpx] w-1/3')">
|
||||
@@ -350,15 +348,14 @@
|
||||
|
||||
<!-- 音频 -->
|
||||
<view v-if="moment.audios && moment.audios.length !== 0"
|
||||
class="audio-list flex flex-col gap-3 px-4 pt-3">
|
||||
class="box-border flex flex-col gap-3 px-4 pt-3">
|
||||
<uh-audio-player v-for="audio in moment.audios" :key="audio.url" :src="audio.url"
|
||||
:poster="bloggerInfo.avatar" :name="`来自${siteName}的声音`"
|
||||
:author="bloggerInfo.nickname" />
|
||||
:poster="bloggerInfo.avatar" :name="`来自${siteName}的声音`" :author="bloggerInfo.nickname" />
|
||||
</view>
|
||||
|
||||
<!-- 视频 -->
|
||||
<view v-if="moment.videos && moment.videos.length !== 0"
|
||||
class="video-list flex flex-col gap-3 px-4 pt-3">
|
||||
class="box-border flex flex-col gap-3 px-4 pt-3">
|
||||
<video v-for="(video, index) in moment.videos" :id="`video_${video.id}`" :key="index"
|
||||
class="video-src h-[400rpx] w-full rounded-xl" :src="video.url" :show-mute-btn="true"
|
||||
:controls="true" :show-center-play-btn="true" :enable-progress-gesture="true"
|
||||
@@ -367,7 +364,7 @@
|
||||
</view>
|
||||
|
||||
<view v-if="moment.spec.tags && moment.spec.tags.length !== 0"
|
||||
class="box-border px-4 mt-4 flex flex-wrap gap-2">
|
||||
class="box-border px-4 mt-3 flex flex-wrap gap-2">
|
||||
<text v-for="(tag, tagIndex) in moment.spec.tags" :key="tagIndex"
|
||||
class="py-1 px-2 text-xs rounded-xl bg-secondary">
|
||||
# {{ tag }}
|
||||
@@ -396,7 +393,6 @@
|
||||
<view class="load-text pb-5 pt-1 text-center text-xs text-gray-500">
|
||||
{{ loadMoreText }}
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
Reference in New Issue
Block a user