1
0
mirror of https://github.com/ialley-workshop-open/uni-halo.git synced 2026-09-12 16:40:40 +08:00

feat: 添加恋爱故事模块功能

1. 新增uhlove图标字体库与相关样式
2. 新增恋爱故事页面与恋爱主页
3. 修复投票组件多选限制逻辑bug
4. 优化导航栏组件自定义样式能力
5. 更新滚动置顶组件黑名单与样式
6. 开启本地开发调试模式
This commit is contained in:
小莫唐尼
2026-09-08 17:38:56 +08:00
parent d8d08f9689
commit 2541f5a75e
10 changed files with 770 additions and 766 deletions
@@ -104,8 +104,10 @@ function handleSelectCheckboxOption(option: IVoteOption) {
return return
const checkedList = (spec.options || []).filter(x => x.checked && x.id !== option.id) const checkedList = (spec.options || []).filter(x => x.checked && x.id !== option.id)
if (spec.type === 'multiple' && checkedList.length >= (spec.maxVotes || 0)) { // maxVotes 缺失(0/undefined)时不限制多选数量,避免 0 >= 0 恒真导致无法选择
showToast(`最多选择 ${spec.maxVotes}`) const maxVotes = spec.maxVotes
if (spec.type === 'multiple' && maxVotes && maxVotes > 0 && checkedList.length >= maxVotes) {
showToast(`最多选择 ${maxVotes}`)
return return
} }
@@ -225,7 +227,7 @@ defineExpose({ refresh: handleGetData })
</view> </view>
<text class="shrink-0 text-[22rpx] text-gray-400" @click="handleToVoteDetail">查看投票详情 ></text> <text class="shrink-0 text-[22rpx] text-gray-400" @click="handleToVoteDetail">查看投票详情 ></text>
</view> </view>
<view class="title mt-2 text-[30rpx] font-bold text-gray-900"> <view class="title mt-2 text-[30rpx] text-gray-900 font-bold">
{{ voteData.spec?.title }} {{ voteData.spec?.title }}
</view> </view>
</view> </view>
@@ -247,8 +249,12 @@ defineExpose({ refresh: handleGetData })
> >
<view class="is-voted-item-content relative z-2 box-border min-h-[72rpx] px-6 py-3"> <view class="is-voted-item-content relative z-2 box-border min-h-[72rpx] px-6 py-3">
<view class="flex items-center justify-between"> <view class="flex items-center justify-between">
<view class="flex-1 text-left">{{ option.title }}</view> <view class="flex-1 text-left">
<view class="shrink-0">{{ handleCalcPercent(option) }}%</view> {{ option.title }}
</view>
<view class="shrink-0">
{{ handleCalcPercent(option) }}%
</view>
</view> </view>
</view> </view>
</view> </view>
@@ -278,8 +284,12 @@ defineExpose({ refresh: handleGetData })
> >
<view class="is-voted-item-content relative z-2 box-border min-h-[72rpx] px-6 py-3"> <view class="is-voted-item-content relative z-2 box-border min-h-[72rpx] px-6 py-3">
<view class="flex items-center justify-between"> <view class="flex items-center justify-between">
<view class="flex-1 text-left">{{ option.title }}</view> <view class="flex-1 text-left">
<view class="shrink-0">{{ handleCalcPercent(option) }}%</view> {{ option.title }}
</view>
<view class="shrink-0">
{{ handleCalcPercent(option) }}%
</view>
</view> </view>
</view> </view>
</view> </view>
@@ -300,7 +310,7 @@ defineExpose({ refresh: handleGetData })
<!-- PK --> <!-- PK -->
<view v-else-if="voteData.spec?.type === 'pk'" class="flex flex-col gap-2"> <view v-else-if="voteData.spec?.type === 'pk'" class="flex flex-col gap-2">
<!-- PK 对抗条 --> <!-- PK 对抗条 -->
<view class="pk-container box-border flex w-full"> <view class="pk-container box-border w-full flex">
<view <view
v-for="(option, optionIndex) in voteData.spec?.options || []" v-for="(option, optionIndex) in voteData.spec?.options || []"
:key="optionIndex" :key="optionIndex"
@@ -324,8 +334,12 @@ defineExpose({ refresh: handleGetData })
> >
<view class="is-voted-item-content relative z-2 box-border min-h-[72rpx] px-6 py-3"> <view class="is-voted-item-content relative z-2 box-border min-h-[72rpx] px-6 py-3">
<view class="flex items-center justify-between"> <view class="flex items-center justify-between">
<view class="flex-1 text-left">选项{{ optionIndex + 1 }}{{ option.title }}</view> <view class="flex-1 text-left">
<view class="shrink-0">{{ handleCalcPercent(option) }}%</view> 选项{{ optionIndex + 1 }}{{ option.title }}
</view>
<view class="shrink-0">
{{ handleCalcPercent(option) }}%
</view>
</view> </view>
</view> </view>
</view> </view>
+12 -7
View File
@@ -10,12 +10,15 @@
titleColor ?: string; titleColor ?: string;
scrollTitle ?: string; scrollTitle ?: string;
needPlaceholder ?: boolean; needPlaceholder ?: boolean;
backClass ?: string;
backStyle ?: string;
} }
const props = withDefaults(defineProps<IProps>(), { const props = withDefaults(defineProps<IProps>(), {
useBack: true, useBack: true,
useTitle: true, useTitle: true,
needPlaceholder: true, needPlaceholder: true,
backClass: 'text-gray-900'
}) })
const slots = useSlots() const slots = useSlots()
@@ -35,10 +38,6 @@
const customCalss = computed(() => { const customCalss = computed(() => {
const _class = [] const _class = []
if (props.titleColor) {
_class.push(props.titleColor)
return
}
if (scrollThreshold.value) { if (scrollThreshold.value) {
_class.push('text-white') _class.push('text-white')
} }
@@ -48,6 +47,10 @@
return _class; return _class;
}) })
const titleColorClass = computed(() => {
return [props.titleColor]
})
const visibleTitle = computed(() => { const visibleTitle = computed(() => {
if (!props.scrollTitle) { if (!props.scrollTitle) {
return props.defaultTitle; return props.defaultTitle;
@@ -65,7 +68,7 @@
return [ return [
homePage, homePage,
'pages/maintenance/maintenance', 'pages/maintenance/maintenance',
...tabbarList.map(item => item.pagePath), ...tabbarList.map((item : any) => item.pagePath),
] as string[]; ] as string[];
}) })
@@ -92,14 +95,16 @@
<!-- 左边 --> <!-- 左边 -->
<view class="shrink-0 min-w-18" @click="handleBack()"> <view class="shrink-0 min-w-18" @click="handleBack()">
<view v-if="props.useBack" <view v-if="props.useBack"
class="uh-global-card-glass h-7 px-3 rounded-full border flex items-center gap-x-2 text-gray-900 text-sm"> class="uh-global-card-glass h-7 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="32rpx"></wd-icon>
<view class="w-[1px] h-4 bg-white/60" /> <view class="w-[1px] h-4 bg-white/60" />
<text class="text-xs font-bold">返回</text> <text class="text-xs font-bold">返回</text>
</view> </view>
</view> </view>
<!-- 中间 --> <!-- 中间 -->
<view class="flex-1 truncate text-center font-bold transition-colors duration-300"> <view class="flex-1 truncate text-center font-bold transition-colors duration-300"
:class="titleColorClass">
<slot> {{visibleTitle}} </slot> <slot> {{visibleTitle}} </slot>
</view> </view>
<!-- 右边 --> <!-- 右边 -->
@@ -13,7 +13,7 @@
} }
// 获取当前页面,并且设置黑名单模式,因为有的页面可能不需要滚动到顶部 // 获取当前页面,并且设置黑名单模式,因为有的页面可能不需要滚动到顶部
const balckList = ['pages/maintenance/maintenance','pages-blog/setting/setting'] const balckList = ['pages/maintenance/maintenance', 'pages-blog/setting/setting', 'pages-blog/love/love']
const pages = getCurrentPages() const pages = getCurrentPages()
const currentPage = pages[pages.length - 1] const currentPage = pages[pages.length - 1]
const visible = computed(() => { const visible = computed(() => {
@@ -23,8 +23,7 @@
<template> <template>
<view v-if="visible" class="fixed bottom-22 right-3 z-50 pb-safe"> <view v-if="visible" class="fixed bottom-22 right-3 z-50 pb-safe">
<view <view class="uh-global-card-glass border h-11 w-11 flex items-center justify-center rounded-full text-primary"
class="uh-global-card-glass border h-11 w-11 flex items-center justify-center rounded-full text-primary"
:class="props.customClass" @click="handleScrollTop"> :class="props.customClass" @click="handleScrollTop">
<wd-icon name="arrow-up" size="20px" /> <wd-icon name="arrow-up" size="20px" />
</view> </view>
+49 -103
View File
@@ -1,8 +1,4 @@
<script lang="ts" setup> <script lang="ts" setup>
/**
* 恋爱主页(源自旧项目 pagesA/love/love.vue,新建复刻)
* 情侣信息 + 恋爱计时 + 功能导航(恋爱故事/相册/清单)
*/
import { computed, onBeforeUnmount, ref } from 'vue' import { computed, onBeforeUnmount, ref } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app' import { onLoad, onShow } from '@dcloudio/uni-app'
import { useAppConfigStore } from '@/store/appConfig' import { useAppConfigStore } from '@/store/appConfig'
@@ -11,6 +7,7 @@ import { checkAvatarUrl, checkImageUrl } from '@/utils/url'
definePage({ definePage({
style: { style: {
navigationBarTitleText: '恋爱日记', navigationBarTitleText: '恋爱日记',
navigationStyle: 'custom'
}, },
}) })
@@ -61,21 +58,10 @@ const loveConfig = ref<ILoveConfigPage>({
const loveDayCount = ref({ d: 0, h: 0, m: 0, s: 0 }) const loveDayCount = ref({ d: 0, h: 0, m: 0, s: 0 })
let loveDayTimer : ReturnType<typeof setTimeout> | null = null let loveDayTimer : ReturnType<typeof setTimeout> | null = null
const navList = ref<{ key: string, use: boolean, iconImageUrl: string, title: string, desc: string }[]>([]) const navList = ref<{ key : string, use : boolean, iconPrefix : string, icon : string, title : string, desc : string }[]>([])
/* ---------------- 计算属性 ---------------- */
const loveWrapStyle = computed(() => ({
backgroundImage: `url(${checkImageUrl(loveConfig.value.pageImages.bgImageUrl)})`,
}))
/* ---------------- 数据加载 ---------------- */ /* ---------------- 数据加载 ---------------- */
/**
* 恋爱配置数据统一来自 appConfig store(静态配置一次性拉取,设计见
* .docs/static-config-unified-fetch-design.md):
* - loveConfig(公开 /love-config 内容: enabled/纪念日/恋人信息, bootstrap 已拉取)
* - configs.loveConfig(模块开关与页面图片, getConfigs 合成下发)
* 页面不再自行发起 /love-config 请求,也不再需要接口失败降级分支(store 兜底默认)。
*/
function syncLoveConfigFromStore() { function syncLoveConfigFromStore() {
const storeLove = appConfigStore.loveConfig const storeLove = appConfigStore.loveConfig
const appConfigs = appConfigStore.configs const appConfigs = appConfigStore.configs
@@ -114,25 +100,28 @@ function initList() {
const configs = loveConfig.value const configs = loveConfig.value
navList.value = [ navList.value = [
{ {
key: 'journey', key: 'story',
use: configs.ourStory.enabled, use: configs.ourStory.enabled,
iconImageUrl: configs.ourStory.iconUrl,
title: '恋爱故事', title: '恋爱故事',
desc: '我们一起度过的那些经历', desc: '我们一起度过的那些经历',
iconPrefix: 'uhlove-icon',
icon: 'gushi',
}, },
{ {
key: 'album', key: 'album',
use: configs.lovePhoto.enabled, use: configs.lovePhoto.enabled,
iconImageUrl: configs.lovePhoto.iconUrl,
title: '恋爱相册', title: '恋爱相册',
desc: '定格了我们的那些小美好', desc: '定格了我们的那些小美好',
iconPrefix: 'uhlove-icon',
icon: 'xiangce'
}, },
{ {
key: 'list', key: 'list',
use: configs.loveDaily.enabled, use: configs.loveDaily.enabled,
iconImageUrl: configs.loveDaily.iconUrl,
title: '恋爱清单', title: '恋爱清单',
desc: '你我之间的约定我们都在努力实现', desc: '你我之间的约定我们都在努力实现',
iconPrefix: 'uhlove-icon',
icon: 'liebiao'
}, },
] ]
} }
@@ -171,12 +160,9 @@ function handleToPage(pageName: string) {
/* ---------------- 生命周期 ---------------- */ /* ---------------- 生命周期 ---------------- */
onLoad(() => { onLoad(() => {
uni.setNavigationBarTitle({ title: '恋爱日记' })
// 先用 store 已有数据立即渲染(index 启动已 bootstrap 或持久化缓存恢复)
syncLoveConfigFromStore() syncLoveConfigFromStore()
}) })
// 每次进入页面:静态配置 TTL 内不重复请求(bootstrap 内部判定),刷新后重新合成视图
onShow(async () => { onShow(async () => {
await appConfigStore.bootstrap() await appConfigStore.bootstrap()
syncLoveConfigFromStore() syncLoveConfigFromStore()
@@ -190,68 +176,80 @@ onBeforeUnmount(() => {
</script> </script>
<template> <template>
<view class="app-page min-h-screen w-screen"> <view class="bg-pink-100 min-h-screen w-screen">
<uh-navbar default-title="恋爱日记" :need-placeholder="false" back-class="text-love"
title-color="!text-love"></uh-navbar>
<!-- 情侣信息 --> <!-- 情侣信息 -->
<view class="lover-wrap relative h-[50vh] w-screen flex items-center justify-center" :style="[loveWrapStyle]"> <view class="relative z-10 h-92 w-screen flex flex-col items-center justify-center">
<view class="lover-card absolute left-1/2 top-[58%] z-2 w-[90vw] flex items-center justify-around rounded-xl -translate-x-1/2 -translate-y-1/2"> <view class="relative z-10 w-full h-full flex items-center justify-center rounded-xl">
<view class="boy"> <view class="boy flex flex-col items-center justify-center translate-x-1">
<image class="avatar box-border h-[180rpx] w-[180rpx] border-8 rounded-full" :style="{ borderColor: 'rgb(58 184 228 / 70%)' }" :src="checkAvatarUrl(loveConfig.loveInfo.boyAvatar)" mode="aspectFit" /> <image class="uh-global-card-glass border-3 box-border border-blue-400 h-26 w-26 rounded-full"
<view class="name mt-2 text-center text-[32rpx] text-white font-bold tracking-[2rpx]"> :src="checkAvatarUrl(loveConfig.loveInfo.boyAvatar)" mode="aspectFill" />
<view class="bg-blue-500 mt-2 text-center text-xs text-white font-bold px-2 py-1 rounded-lg">
{{ loveConfig.loveInfo.boyNickname }} {{ loveConfig.loveInfo.boyNickname }}
</view> </view>
</view> </view>
<image class="like h-[120rpx] w-[120rpx]" :src="checkImageUrl(loveConfig.pageImages.heartImageUrl)" mode="scaleToFill" /> <view class="girl flex flex-col items-center justify-center -translate-x-1">
<view class="girl"> <image class="uh-global-card-glass border-3 box-border border-love h-26 w-26 rounded-full"
<image class="avatar box-border h-[180rpx] w-[180rpx] border-8 rounded-full" :style="{ borderColor: 'rgb(245 122 179 / 70%)' }" :src="checkAvatarUrl(loveConfig.loveInfo.girlAvatar)" mode="aspectFit" /> :src="checkAvatarUrl(loveConfig.loveInfo.girlAvatar)" mode="aspectFill" />
<view class="name mt-2 text-center text-[32rpx] text-white font-bold tracking-[2rpx]"> <view class="bg-love mt-2 text-center text-xs text-white font-bold px-2 py-1 rounded-lg">
{{ loveConfig.loveInfo.girlNickname }} {{ loveConfig.loveInfo.girlNickname }}
</view> </view>
</view> </view>
</view> </view>
<image class="wave-image absolute bottom-0 left-0 h-[120rpx] w-full" :src="checkImageUrl(loveConfig.pageImages.waveImageUrl)" mode="scaleToFill" /> <image :src="checkImageUrl(loveConfig.pageImages.bgImageUrl)" class="absolute z-0 inset-0 w-full h-full"
mode="aspectFill" />
<view class="absolute z-2 left-0 bottom-0 w-full h-12 bg-gradient-to-b from-white/0 to-pink-100" />
</view> </view>
<!-- 恋爱记时 --> <!-- 恋爱记时 -->
<view class="love-time-wrap mt-20 w-screen flex flex-col items-center justify-center"> <view class="love-time-wrap mt-8 w-screen flex flex-col items-center justify-center">
<view class="title text-[42rpx] text-[#333] font-bold"> <view class="title text-xl text-love font-bold">
{{ loveConfig.loveDateTitle }} {{ loveConfig.loveDateTitle }}
</view> </view>
<view class="content mt-6 flex items-center justify-center"> <view class="content mt-6 flex items-center justify-center">
<text class="text text-[28rpx]"> <text class="text text-sm">
<text class="number mx-2 text-[46rpx] text-[#f83856] font-bold">{{ loveDayCount.d }}</text> <text class="number mx-2 text-2xl text-love font-bold">{{ loveDayCount.d }}</text>
</text> </text>
<text class="text text-[28rpx]"> <text class="text text-sm">
<text class="number mx-2 text-[46rpx] text-[#f83856] font-bold">{{ loveDayCount.h }}</text> <text class="number mx-2 text-2xl text-blue-400 font-bold">{{ loveDayCount.h }}</text>
小时 小时
</text> </text>
<text class="text text-[28rpx]"> <text class="text text-sm">
<text class="number mx-2 text-[46rpx] text-[#f83856] font-bold">{{ loveDayCount.m }}</text> <text class="number mx-2 text-2xl text-love font-bold">{{ loveDayCount.m }}</text>
分钟 分钟
</text> </text>
<text class="text text-[28rpx]"> <text class="text text-sm">
<text class="number mx-2 text-[46rpx] text-[#f83856] font-bold">{{ loveDayCount.s }}</text> <text class="number mx-2 text-2xl text-blue-400 font-bold">{{ loveDayCount.s }}</text>
</text> </text>
</view> </view>
</view> </view>
<!-- 功能导航 --> <!-- 功能导航 -->
<view class="list-wrap mt-[75rpx] box-border flex flex-col items-center justify-center px-9"> <view class="mt-6 box-border flex flex-col items-center justify-center gap-y-4 px-4">
<block v-for="(nav, index) in navList" :key="index"> <block v-for="(nav, index) in navList" :key="index">
<view v-if="nav.use" class="mb-8 box-border list-item w-full flex items-center justify-around rounded-[50rpx] bg-white px-8 py-7 shadow-sm" :class="`list-item-${index + 1}`" @click="handleToPage(nav.key)"> <view v-if="nav.use"
<view class="left h-[120rpx] w-[120rpx]"> class="box-border list-item uh-global-card-glass bg-white/60 p-3 w-full flex items-center justify-around gap-x-4 rounded-2xl"
<image class="icon h-full w-full" :src="checkImageUrl(nav.iconImageUrl)" mode="aspectFit" /> :class="`list-item-${index + 1}`" @click="handleToPage(nav.key)">
<view class="flex items-center justify-center h-12 w-12 rounded-xl opacity-70" :class="[index%2===0?'bg-pink-100':'bg-blue-100']">
<wd-icon :class-prefix="nav.iconPrefix" :name="nav.icon" size="66rpx" />
</view> </view>
<view class="right box-border flex flex-1 flex-col justify-center pl-10"> <view class="box-border flex flex-1 flex-col justify-center gap-y-1">
<view class="name text-[32rpx] text-[#333] font-bold"> <view class="name text-md font-bold" :class="[index%2===0?'text-love':'text-blue-400']">
{{ nav.title }} {{ nav.title }}
</view> </view>
<view class="desc mt-2 text-[26rpx] text-[#777]"> <view class="text-xs truncate" :class="[index%2===0?'text-love/60':'text-blue-300']">
{{ nav.desc }} {{ nav.desc }}
</view> </view>
</view> </view>
<view class="shrink-0">
<wd-icon name="arrow-right" :class="[index%2===0?'text-love':'text-blue-400']"
size="32rpx"></wd-icon>
</view>
</view> </view>
</block> </block>
</view> </view>
@@ -259,58 +257,6 @@ onBeforeUnmount(() => {
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.app-page {
background: linear-gradient(
-45deg,
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%)
);
}
.lover-wrap {
background-size: cover;
background-repeat: no-repeat;
background-position: 50% 50%;
&::before {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
content: '';
background-color: rgb(255 255 255 / 10%);
z-index: 0;
backdrop-filter: blur(4rpx);
overflow: hidden;
}
&::after {
content: '';
position: absolute;
left: 0;
bottom: -60rpx;
width: 100vw;
height: 60rpx;
background-image: linear-gradient(to bottom, rgb(255 255 255), rgb(255 255 255 / 0%));
}
.like {
animation: likeani 1s ease-in-out infinite;
}
.wave-image {
mix-blend-mode: screen;
}
}
/* 列表项漂浮动画(无法用 UnoCSS 表达;模板已生成 list-item-N 类,避开 WXSS 不支持的 :nth-child) */
.list-item-1 { .list-item-1 {
animation: listItemAni1 3s ease-in-out infinite; animation: listItemAni1 3s ease-in-out infinite;
} }
@@ -1,8 +1,4 @@
<script lang="ts" setup> <script lang="ts" setup>
/**
* 恋爱故事页(源自旧项目 pagesA/love/journey.vue,新建复刻)
* 时间轴展示恋爱故事,点击查看故事详情弹窗
*/
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { onLoad, onPullDownRefresh } from '@dcloudio/uni-app' import { onLoad, onPullDownRefresh } from '@dcloudio/uni-app'
import { getLoveStories } from '@/api/uni-halo' import { getLoveStories } from '@/api/uni-halo'
@@ -13,6 +9,7 @@ import type { ILoveStory } from '@/api/types/uni-halo'
definePage({ definePage({
style: { style: {
navigationBarTitleText: '恋爱故事', navigationBarTitleText: '恋爱故事',
navigationStyle: 'custom',
enablePullDownRefresh: true, enablePullDownRefresh: true,
}, },
}) })
+63 -48
View File
@@ -166,8 +166,10 @@
return return
const checkedList = vote.value!.spec!.options!.filter(x => x.checked && x.id !== option.id) const checkedList = vote.value!.spec!.options!.filter(x => x.checked && x.id !== option.id)
if (vote.value?.spec?.type === 'multiple' && checkedList.length >= (vote.value.spec.maxVotes || 0)) { // maxVotes 缺失(0/undefined)时不限制多选数量,避免 0 >= 0 恒真导致无法选择
showToast(`最多选择 ${vote.value.spec.maxVotes}`) const maxVotes = vote.value?.spec?.maxVotes
if (vote.value?.spec?.type === 'multiple' && maxVotes && maxVotes > 0 && checkedList.length >= maxVotes) {
showToast(`最多选择 ${maxVotes}`)
return return
} }
@@ -263,10 +265,12 @@
<uh-navbar :default-title="pageTitle" title-color="text-gray-900" /> <uh-navbar :default-title="pageTitle" title-color="text-gray-900" />
<!-- 加载/错误/空占位(状态机) --> <!-- 加载/错误/空占位(状态机) -->
<uh-data-loading v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus" <uh-data-loading
empty-text="未查询到数据" @refresh="handleGetData" /> v-if="loadingStatus !== DataLoadingStatusEnum.Success" :loading-status="loadingStatus"
empty-text="未查询到数据" @refresh="handleGetData"
/>
<view v-else class="box-border px-3 pt-2 pb-16 flex flex-col gap-y-3"> <view v-else class="box-border flex flex-col gap-y-3 px-3 pb-16 pt-2">
<!-- 投票信息 --> <!-- 投票信息 -->
<view class="uh-global-card-glass box-border flex flex-col gap-y-3 rounded-2xl p-3"> <view class="uh-global-card-glass box-border flex flex-col gap-y-3 rounded-2xl p-3">
<uh-section-title> 投票信息 </uh-section-title> <uh-section-title> 投票信息 </uh-section-title>
@@ -277,8 +281,12 @@
</view> </view>
<view class="info-row"> <view class="info-row">
<text>投票状态</text> <text>投票状态</text>
<text class="tag" <text
:style="{ color: vote.spec?._uh_state?.color }">{{ vote.spec?._uh_state?.state }}</text> class="tag"
:style="{ color: vote.spec?._uh_state?.color }"
>
{{ vote.spec?._uh_state?.state }}
</text>
</view> </view>
<view class="info-row"> <view class="info-row">
<text>投票方式</text> <text>投票方式</text>
@@ -297,9 +305,9 @@
</view> </view>
<!-- 投票内容 --> <!-- 投票内容 -->
<view class="uh-global-card-glass box-border flex flex-col rounded-2xl p-3 gap-3"> <view class="uh-global-card-glass box-border flex flex-col gap-3 rounded-2xl p-3">
<uh-section-title> 投票内容 </uh-section-title> <uh-section-title> 投票内容 </uh-section-title>
<view class="box-border flex flex-col gap-y-2 p-4 rounded-xl bg-gray-100"> <view class="box-border flex flex-col gap-y-2 rounded-xl bg-gray-100 p-4">
<view class="text-sm text-gray-900 font-bold"> <view class="text-sm text-gray-900 font-bold">
{{ vote.spec?.title }} {{ vote.spec?.title }}
</view> </view>
@@ -308,31 +316,40 @@
</view> </view>
</view> </view>
<view class="w-full flex flex-col gap-y-2"> <view class="w-full flex flex-col gap-y-2">
<view class="relative box-border text-sm flex items-center gap-x-2"> <view class="relative box-border flex items-center gap-x-2 text-sm">
投票选项 投票选项
<text v-if="vote.spec?.type === 'multiple'" <text
class="text-xs font-normal">最多选择 v-if="vote.spec?.type === 'multiple'"
class="text-xs font-normal"
>
最多选择
{{ vote.spec?.maxVotes }} {{ vote.spec?.maxVotes }}
</text> </text>
</view> </view>
<view class="options flex flex-col gap-3"> <view class="options flex flex-col gap-3">
<!-- PK 对抗条(与旧项目 pk-container 一致) --> <!-- PK 对抗条(与旧项目 pk-container 一致;样式需顶层定义,勿嵌套在 .vote-card ) -->
<view v-if="vote.spec?.type === 'pk'" class="pk-container box-border flex w-full"> <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" <view
class="radio-item flex-grow" :class="optionIndex === 0 ? 'radio-left' : 'radio-right'" v-for="(option, optionIndex) in vote.spec?.options" :key="optionIndex"
:style="{ width: `${option._uh_percent}%` }"> class="radio-item" :class="optionIndex === 0 ? 'radio-left' : 'radio-right'"
<view class="option-item box-border w-full rounded-xl py-3 px-3" :style="{ width: `${option._uh_percent}%` }"
:class="optionIndex === 0 ? 'option-item-left' : 'option-item-right'"> >
<view
class="option-item box-border w-full rounded-xl px-3 py-3"
:class="optionIndex === 0 ? 'option-item-left' : 'option-item-right'"
>
{{ option._uh_percent }}% {{ option._uh_percent }}%
</view> </view>
</view> </view>
</view> </view>
<template v-if="isVoted || isEnded"> <template v-if="isVoted || isEnded">
<view v-for="(option, optionIndex) in vote.spec?.options" :key="optionIndex" <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="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'" :class="option.checked ? 'bg-primary text-gray-900 font-bold' : 'bg-gray-100'"
:style="{ '--percent': `${option._uh_percent}%` }"> :style="{ '--percent': `${option._uh_percent}%` }"
>
<view class="is-voted-item-content relative z-2 box-border px-4 py-3"> <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 items-center justify-between">
<view class="flex-1 text-left"> <view class="flex-1 text-left">
@@ -346,10 +363,12 @@
</view> </view>
</template> </template>
<template v-else> <template v-else>
<view v-for="(option, optionIndex) in vote.spec?.options" :key="optionIndex" <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="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' : ''" :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 }} {{ vote.spec?.type === 'pk' ? `选项${optionIndex + 1}` : '' }}{{ option.title }}
</view> </view>
</template> </template>
@@ -366,26 +385,35 @@
</view> </view>
<!-- 提交按钮 --> <!-- 提交按钮 -->
<view class="fixed bottom-0 left-0 z-99 box-border w-screen pb-safe px-3"> <view class="fixed bottom-0 left-0 z-99 box-border w-screen px-3 pb-safe">
<view <view
class="uh-global-card-glass border rounded-xl w-full flex items-center justify-center gap-x-2 mb-2"> class="uh-global-card-glass mb-2 w-full flex items-center justify-center gap-x-2 border rounded-xl"
>
<uh-button v-if="isVoted" custom-class="flex-1 py-2 !rounded-xl"> <uh-button v-if="isVoted" custom-class="flex-1 py-2 !rounded-xl">
您已参与投票 您已参与投票
</uh-button> </uh-button>
<uh-button v-else-if="vote.spec?._uh_state?.state === '未开始'" custom-class="flex-1 py-2 !rounded-xl" <uh-button
@click="handleSubmitTip('投票未开始')"> v-else-if="vote.spec?._uh_state?.state === '未开始'" custom-class="flex-1 py-2 !rounded-xl"
@click="handleSubmitTip('投票未开始')"
>
投票未开始 投票未开始
</uh-button> </uh-button>
<uh-button v-else-if="vote.spec?._uh_state?.state === '已结束'" custom-class="flex-1 py-2 !rounded-xl" <uh-button
@click="handleSubmitTip('投票已结束')"> v-else-if="vote.spec?._uh_state?.state === '已结束'" custom-class="flex-1 py-2 !rounded-xl"
@click="handleSubmitTip('投票已结束')"
>
投票已结束 投票已结束
</uh-button> </uh-button>
<uh-button v-else-if="!vote.spec?.canAnonymously" custom-class="flex-1 py-2 !rounded-xl" <uh-button
@click="handleSubmit()"> v-else-if="!vote.spec?.canAnonymously" custom-class="flex-1 py-2 !rounded-xl"
@click="handleSubmit()"
>
不支持匿名投票 不支持匿名投票
</uh-button> </uh-button>
<uh-button v-else-if="submitForm.voteData.length === 0" custom-class="flex-1 py-2 !rounded-xl" <uh-button
@click="handleSubmitTip('请选择选项')"> v-else-if="submitForm.voteData.length === 0" custom-class="flex-1 py-2 !rounded-xl"
@click="handleSubmitTip('请选择选项')"
>
提交投票请选择选项 提交投票请选择选项
</uh-button> </uh-button>
<uh-button v-else custom-class="flex-1 py-2 !rounded-xl" @click="handleSubmit()"> <uh-button v-else custom-class="flex-1 py-2 !rounded-xl" @click="handleSubmit()">
@@ -398,20 +426,7 @@
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.vote-card { /* 已投票结果项:百分比进度条(此前嵌套在 .vote-card 下导致失效,现顶层定义) */
.sub-title {
&::before {
content: '';
width: 8rpx;
height: 28rpx;
position: absolute;
left: 0;
top: 6rpx;
background: var(--wot-color-theme, #b9e424);
border-radius: 6rpx;
}
}
.is-voted-item { .is-voted-item {
&::before { &::before {
content: ''; content: '';
@@ -426,6 +441,7 @@
} }
} }
/* PK 对抗条:宽度按选项票数占比,两侧斜切渐变(顶层定义,勿嵌套) */
.pk-container { .pk-container {
.radio-item { .radio-item {
min-width: 30%; min-width: 30%;
@@ -445,5 +461,4 @@
text-align: right; text-align: right;
} }
} }
}
</style> </style>
+2 -2
View File
@@ -20,9 +20,9 @@
const articleDetailPath = '/pages-blog/article-detail/article-detail' const articleDetailPath = '/pages-blog/article-detail/article-detail'
// 本地开发快速跳转页面,发布请置为 false // 本地开发快速跳转页面,发布请置为 false
const DEV_MODE = false const DEV_MODE = true
const DEV_TO_TYPE = 'page' as 'page' | 'tabbar' const DEV_TO_TYPE = 'page' as 'page' | 'tabbar'
const DEV_TO_PATH = `/pages-blog/test/test` const DEV_TO_PATH = `/pages-blog/love/love`
/* ---------------- 状态 ---------------- */ /* ---------------- 状态 ---------------- */
const appConfigStore = useAppConfigStore() const appConfigStore = useAppConfigStore()
+1
View File
@@ -2,6 +2,7 @@
@import './iconfont.css'; @import './iconfont.css';
@import './uhemoji-iconfont.css'; @import './uhemoji-iconfont.css';
@import './uhemoji2-iconfont.css'; @import './uhemoji2-iconfont.css';
@import './uhlove-iconfont.css';
:root, :root,
page { page {
+26
View File
@@ -0,0 +1,26 @@
@font-face {
font-family: "uhlove-icon"; /* Project id 5231616 */
/* Color fonts */
src:
url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAARwAAwAAAAACOgAAAQiAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIlgjHgZgAEwKiXSHFgE2AiQDKgsoAAQgBYIUByAbjwYRFZs62eNDeNa/mSSLTIoFyGwPTI9MD/C3mOv3W5kQKcEiIRMSrf7vy3uoZXFL0JhOSKLN/CwRj3Sl3gN2Wy23sREAB0APwBeiKQABHAm8KbSFYKOwIi9bA7ABhS0AEQSM8EsAdC03cTAAMAKwI7z2AMADoeh+MwFNu1CsbTjvagueo3TgS5S7Riigh+gwgCpvHDKMBJIAQ4EY8ZXqmuoiPAjfz905RVMATgHsAAEACvrcGugYvSpwpBHxHDxS502uAGjrZdLnzp+7axoQFWifP3L73e07t6/7qjSQA3UnGIATQGYA8ijIDn6O9UzE0ZgCYchIohcLlxz0W/Ilo9FZ8NMvX0L/Yot5P9tqgaKKW/b1tOw/7zRlOejYunyrS8fha8HT5550bVQPOlnOu7TsL9Q7tsa+IBPOvE/Ub1FBF2+9D7xyL+G8n5BesqAww1YimLMG0C6JusMPsOVjbO0wUPWMc/O5wMYzwRCtnnNevHU/6VAgjapYWF8iaulWl/MeAk8w+bzq2Hx3aqPqDF6yhVmbB1aiDoG3EfP9E4i+F1YHg86GzN3edDED+i2Lp2Gb2frK1OBlzIYXQZWXmGDYetGHCym4lKu7vLwPsgqLAyOm+CzL2xub90d6/2Hn9kPiGWvzoUDArbv0z46mg2yrS0ubpXvOf5+5tihVNiNFzon+REHGzx+nfPwocvewHxjTGPufse/xSUHd413PvBb8yWv3dM9IiDVO2s8cX3sgZ2nwJ0u5/cVRafLevSllnmdv8Cd7y5Q3pMt78/BXEATxezbBvhcFvNLtUeTu4VHo0eDe6BHHvu90UxxjDRLzYFIDi3QaG21DnHuTOxL9O8QfVL3I4CWcwt1Mf/UOU0FF2cqc8oyprPL0LcVPXNfjekApgcLdrL7raJfwfZxyB7uOkYVINObob7+mZ92+JAILxdS4MkeWp7IotpSAUCLZ2izY2JZOixctb6lbgtuCHStJO49f3VKCQT9jE0zFt2P543OlFrETP3pF3BOuht8TUR7u8eFsvtOzjqrjs07ljmQW0mvfETW2IR+PtoBR5dIGpiG+t+Hj0IkDR7563hS9M812YVFQy+uuDzrl3igfk699dmL34/e3lDWq64va7qpdescNJ8cHKqeelZa+K4/q/IEFHA/70ih7j26sZ32PIfM3G+4Ruj9/bMPzXn6edhf3JFcE/F5MC/rPWJsH8pDKPfnKKQzVjy0Pm+3IPHyKJ9FB/oDLx2mSis10BfLoG5hAdbuwA/LBzaam8cgK1eBAeFsQOAORjULP06SIQyYCRTyC0SsS4I4NX7VCME7AhFFMYgaLMIQojGGAsE2jnrw1AhMm0Yd5xCEasaY3A9WosaEoaKBVryi+AYQ8X1szhsAlY6yiYrQWjEDv00TQbkECDAA=') format('woff2');
}
.uhlove-icon {
font-family: "uhlove-icon" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.uhlove-icon-gushi:before {
content: "\e60c";
}
.uhlove-icon-liebiao:before {
content: "\e616";
}
.uhlove-icon-xiangce:before {
content: "\e61a";
}
+1
View File
@@ -97,6 +97,7 @@ export default defineConfig({
primary: 'var(--wot-color-theme,#B9E424)', primary: 'var(--wot-color-theme,#B9E424)',
secondary: 'var(--wot-color-secondary,#D7F94C)', secondary: 'var(--wot-color-secondary,#D7F94C)',
page: 'var(--wot-color-page,#f6f3ee)', page: 'var(--wot-color-page,#f6f3ee)',
love: '#f83856'
}, },
fontSize: { fontSize: {
/** 提供更小号的字体,用法如:text-2xs */ /** 提供更小号的字体,用法如:text-2xs */