diff --git a/src/components/uh-article-vote-item/uh-article-vote-item.vue b/src/components/uh-article-vote-item/uh-article-vote-item.vue
new file mode 100644
index 0000000..5034223
--- /dev/null
+++ b/src/components/uh-article-vote-item/uh-article-vote-item.vue
@@ -0,0 +1,421 @@
+
+
+
+
+
+
+ {{ loadingText }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ props.index + 1 }}
+
+
+ {{ voteTypeLabel }}
+
+
+ {{ voteState.state }}
+
+
+ 查看投票详情 >
+
+
+ {{ voteData.spec?.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ option.title }}
+ {{ handleCalcPercent(option) }}%
+
+
+
+
+
+
+ {{ option.title }}
+
+
+
+
+
+
+
+
+
+
+ {{ option.title }}
+ {{ handleCalcPercent(option) }}%
+
+
+
+
+
+
+ {{ option.title }}
+
+
+
+
+
+
+
+
+
+
+ {{ handleCalcPercent(option) }}%
+
+
+
+
+
+
+
+
+ 选项{{ optionIndex + 1 }}:{{ option.title }}
+ {{ handleCalcPercent(option) }}%
+
+
+
+
+
+
+ 选项{{ optionIndex + 1 }}:{{ option.title }}
+
+
+
+
+
+
+
+
+
+
+
+ 您已参与投票
+
+
+ 投票未开始
+
+
+ 投票已结束
+
+
+ 不支持匿名投票
+
+
+ 提交投票
+
+
+
+
+
+
+
diff --git a/src/components/uh-article-vote/uh-article-vote.vue b/src/components/uh-article-vote/uh-article-vote.vue
new file mode 100644
index 0000000..a3309b9
--- /dev/null
+++ b/src/components/uh-article-vote/uh-article-vote.vue
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+ 相关投票
+
+ {{ voteIsOpen ? '收起' : '展开' }}
+
+
+
+
+
+
+
+
+
+
+ 投票已收起,点击展开 {{ voteIds.length }} 个投票项
+
+
+
+
diff --git a/src/components/uh-data-loading/uh-data-loading.vue b/src/components/uh-data-loading/uh-data-loading.vue
index 85344f6..3440849 100644
--- a/src/components/uh-data-loading/uh-data-loading.vue
+++ b/src/components/uh-data-loading/uh-data-loading.vue
@@ -1,256 +1,265 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
- {{ statusScene.mainText }}
-
-
-
-
-
- {{ statusScene.subText }}
-
-
- 刷新试试
-
-
-
+
+
+
+ {{ statusScene.mainText }}
+
+
+
+
+
+ {{ statusScene.subText }}
+
+
+
+ 刷新试试
+
+
+
+
+ 50% {
+ transform: rotate(4deg);
+ }
+ }
+
+ /* 缓慢左右摇头(error) */
+ @keyframes head-shake {
+
+ 0%,
+ 100% {
+ transform: rotate(0);
+ }
+
+ 25% {
+ transform: rotate(6deg);
+ }
+
+ 75% {
+ transform: rotate(-6deg);
+ }
+ }
+
+ /* 轻轻叹气缩肩(empty) */
+ @keyframes sigh {
+
+ 0%,
+ 100% {
+ transform: scaleY(1);
+ }
+
+ 30%,
+ 70% {
+ transform: scaleY(0.92);
+ }
+ }
+
\ No newline at end of file
diff --git a/src/components/uh-navbar/uh-navbar.vue b/src/components/uh-navbar/uh-navbar.vue
index b917a3c..89cd43a 100644
--- a/src/components/uh-navbar/uh-navbar.vue
+++ b/src/components/uh-navbar/uh-navbar.vue
@@ -1,6 +1,7 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/uh-vote-card/uh-vote-card.vue b/src/components/uh-vote-card/uh-vote-card.vue
index 29c20f6..2a0ac3c 100644
--- a/src/components/uh-vote-card/uh-vote-card.vue
+++ b/src/components/uh-vote-card/uh-vote-card.vue
@@ -1,175 +1,99 @@
-
-
-
+
+
+
+
+
+ {{ vote.spec._uh_type }}
+
+
+ {{ vote.spec._uh_state.state }}
+
+
+ 已投票
+
+
+
-
-
- {{ voteData.spec?.title }}
-
-
- {{ voteData.spec.remark }}
-
+
+
+ {{ vote.spec?.title }}
+
+
+ {{ vote.spec.remark }}
+
-
-
-
- {{ option.title }}
-
-
-
-
-
-
-
-
-
- {{ voteLabel }}
-
-
-
- {{ voteLabel }}{{ voteResultLabel }}
-
+
+
diff --git a/src/pages-blog/article-detail/article-detail.vue b/src/pages-blog/article-detail/article-detail.vue
index d2c5322..c68f0fc 100644
--- a/src/pages-blog/article-detail/article-detail.vue
+++ b/src/pages-blog/article-detail/article-detail.vue
@@ -456,6 +456,11 @@
})
const globalAppSettings = computed(() => settingStore.settings)
+
+ onMounted(() => {
+ console.log('获取当前所有的页面')
+ console.log(getCurrentPages())
+ })
@@ -566,6 +571,9 @@
+
+
+
diff --git a/src/pages-blog/notice/detail.vue b/src/pages-blog/notice/detail.vue
index 41e68d9..20cfee1 100644
--- a/src/pages-blog/notice/detail.vue
+++ b/src/pages-blog/notice/detail.vue
@@ -1,154 +1,137 @@
-
-
-
+
+
+
-
-
+
+
-
-
-
-
- {{ title }}
-
+
+
+
+
+ {{ title }}
+
-
-
- {{ typeDisplayName }}
-
-
- {{ publishTime }}
-
-
+
+
+ {{ typeDisplayName }}
+
+
+ {{ publishTime }}
+
+
-
-
-
+
+
+
-
-
- 查看原文 ↗
-
-
-
-
-
-
-
+
+
+
+ 复制原文地址
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/pages-blog/vote-detail/vote-detail.vue b/src/pages-blog/vote-detail/vote-detail.vue
index 3d01e51..9991afe 100644
--- a/src/pages-blog/vote-detail/vote-detail.vue
+++ b/src/pages-blog/vote-detail/vote-detail.vue
@@ -7,7 +7,7 @@ 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, VOTE_TYPES, voteCacheUtil } from '@/utils/vote'
+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'
@@ -85,27 +85,17 @@ async function handleGetData() {
const detailRes = res.data as IVoteDetail
const tempVote = detailRes.vote as typeof vote.value
if (tempVote) {
- const typeKey = ((tempVote.spec?.type || 'SINGLE') as string).toUpperCase()
- pageTitle.value = `投票详情(${VOTE_TYPES[typeKey as keyof typeof VOTE_TYPES] || tempVote.spec?.type})`
+ const typeKey = ((tempVote.spec?.type || 'single') as string).toLowerCase()
+ pageTitle.value = `投票详情(${VOTE_TYPES[typeKey] || tempVote.spec?.type})`
tempVote.spec = tempVote.spec || {}
tempVote.spec.isVoted = isVoted.value
tempVote.spec.disabled = isVoted.value
- tempVote.spec._uh_type = VOTE_TYPES[typeKey as keyof typeof VOTE_TYPES] || tempVote.spec.type
+ tempVote.spec._uh_type = VOTE_TYPES[typeKey] || tempVote.spec.type
- // 计算状态
- const startTime = tempVote.spec.startDate ? new Date(tempVote.spec.startDate).getTime() : Date.now()
- const endTime = tempVote.spec.endDate ? new Date(tempVote.spec.endDate).getTime() : Date.now()
- const now = Date.now()
- if (endTime < now) {
- tempVote.spec._uh_state = { state: '已结束', color: 'red' }
+ // 计算状态(与旧项目 calcVoteState 一致,含 timeLimit 非 custom 时 hasEnded 兜底)
+ tempVote.spec._uh_state = calcVoteState(tempVote)
+ if (tempVote.spec._uh_state.state === '已结束')
tempVote.spec.hasEnded = true
- }
- else if (startTime > now) {
- tempVote.spec._uh_state = { state: '未开始', color: 'orange' }
- }
- else {
- tempVote.spec._uh_state = { state: '进行中', color: 'green' }
- }
// 选项计算
// 插件选项为 {id,title},票数在 VoteDetail.voteDataList / Vote.stats.voteDataList
@@ -272,7 +262,7 @@ onShareTimeline(() => ({
-
+
@@ -288,11 +278,11 @@ onShareTimeline(() => ({
-
+
投票信息
-
+
投票类型:
{{ vote.spec?._uh_type }}
@@ -303,7 +293,7 @@ onShareTimeline(() => ({
投票方式:
-
+
{{ vote.spec?.canAnonymously ? '匿名' : '不匿名' }}
@@ -318,7 +308,7 @@ onShareTimeline(() => ({
-
+
投票内容
@@ -333,18 +323,33 @@ onShareTimeline(() => ({
(最多选择 {{ vote.spec?.maxVotes }} 项)
+
+
+
+
+ {{ option._uh_percent }}%
+
+
+
+
- {{ option.title }}
+ {{ vote.spec?.type === 'pk' ? `选项${optionIndex + 1}:` : '' }}{{ option.title }}
{{ option._uh_percent }}%
@@ -358,7 +363,7 @@ onShareTimeline(() => ({
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]"
- :class="option.checked ? 'border-2 border-[#03a9f4] bg-[#03a9f4]/15 text-[#03a9f4]' : ''"
+ :class="option.checked ? 'border-2 border-primary bg-primary/15 text-primary font-bold' : ''"
@click="vote.spec?.type === 'multiple' ? handleSelectCheckboxOption(option) : handleSelectSingleOption(option)"
>
{{ vote.spec?.type === 'pk' ? `选项${optionIndex + 1}:` : '' }}{{ option.title }}
@@ -368,7 +373,7 @@ onShareTimeline(() => ({
-
+
投票统计
@@ -378,7 +383,7 @@ onShareTimeline(() => ({
-
+
您已参与投票
@@ -413,7 +418,7 @@ onShareTimeline(() => ({
position: absolute;
left: 0;
top: 6rpx;
- background: #03a9f4;
+ background: var(--wot-color-theme, #b9e424);
border-radius: 6rpx;
}
}
@@ -431,5 +436,25 @@ onShareTimeline(() => ({
border-radius: 6rpx;
}
}
+
+ .pk-container {
+ .radio-item {
+ min-width: 30%;
+ max-width: 70%;
+ }
+
+ .option-item-left {
+ background: linear-gradient(90deg, #3b82f6, #60a5fa);
+ color: white;
+ clip-path: polygon(0 0, calc(100% - 40rpx) 0, 100% 100%, 0 100%);
+ }
+
+ .option-item-right {
+ background: linear-gradient(90deg, #f87171, #ef4444);
+ color: white;
+ clip-path: polygon(0 0, 100% 0, 100% 100%, 40rpx 100%);
+ text-align: right;
+ }
+ }
}
diff --git a/src/pages-blog/votes/votes.vue b/src/pages-blog/votes/votes.vue
index be446c9..870d852 100644
--- a/src/pages-blog/votes/votes.vue
+++ b/src/pages-blog/votes/votes.vue
@@ -1,7 +1,7 @@
-
+
-
+
-
+
-
+
-
+
{{ getI18nText(item.text) }}
-
+
-
+
{{ item.badge > 99 ? '99+' : item.badge }}
diff --git a/src/tabbar/TabbarItem.vue.bak b/src/tabbar/TabbarItem.vue.bak
new file mode 100644
index 0000000..c34ca12
--- /dev/null
+++ b/src/tabbar/TabbarItem.vue.bak
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ getI18nText(item.text) }}
+
+
+
+
+
+
+
+
+ {{ item.badge > 99 ? '99+' : item.badge }}
+
+
+
+
+
diff --git a/src/tabbar/config.ts b/src/tabbar/config.ts
index d89c3ab..50db1e4 100644
--- a/src/tabbar/config.ts
+++ b/src/tabbar/config.ts
@@ -1,5 +1,5 @@
-import type { TabBar } from '@uni-helper/vite-plugin-uni-pages'
-import type { CustomTabBarItem, NativeTabBarItem } from './types'
+import type { TabBar } from '@uni-helper/vite-plugin-uni-pages';
+import type { CustomTabBarItem, NativeTabBarItem } from './types';
/**
* tabbar 选择的策略,更详细的介绍见 tabbar.md 文件
@@ -10,163 +10,213 @@ import type { CustomTabBarItem, NativeTabBarItem } from './types'
* 温馨提示:本文件的任何代码更改了之后,都需要重新运行,否则 pages.json 不会更新导致配置不生效
*/
export const TABBAR_STRATEGY_MAP = {
- NO_TABBAR: 0,
- NATIVE_TABBAR: 1,
- CUSTOM_TABBAR: 2,
-}
+ NO_TABBAR: 0,
+ NATIVE_TABBAR: 1,
+ CUSTOM_TABBAR: 2
+};
// TODO: 1/3. 通过这里切换使用tabbar的策略
// 如果是使用 NO_TABBAR(0),nativeTabbarList 和 customTabbarList 都不生效
// 如果是使用 NATIVE_TABBAR(1),只需要配置 nativeTabbarList,customTabbarList 不生效
// 如果是使用 CUSTOM_TABBAR(2),只需要配置 customTabbarList,nativeTabbarList 不生效
-export const selectedTabbarStrategy = TABBAR_STRATEGY_MAP.NATIVE_TABBAR
+export const selectedTabbarStrategy = TABBAR_STRATEGY_MAP.CUSTOM_TABBAR;
// TODO: 2/3. 使用 NATIVE_TABBAR 时,更新下面的 tabbar 配置
export const nativeTabbarList: NativeTabBarItem[] = [
- {
- iconPath: 'static/tabbar/select_home.png',
- selectedIconPath: 'static/tabbar/select_home_active.png',
- pagePath: 'pages/tabbar/home/home',
- text: '%tabbar.home%',
- },
- {
- iconPath: 'static/tabbar/select_category.png',
- selectedIconPath: 'static/tabbar/select_category_active.png',
- pagePath: 'pages/tabbar/category/category',
- text: '%tabbar.category%',
- },
- {
- iconPath: 'static/tabbar/select_gallery.png',
- selectedIconPath: 'static/tabbar/select_gallery_active.png',
- pagePath: 'pages/tabbar/gallery/gallery',
- text: '%tabbar.gallery%',
- },
- {
- iconPath: 'static/tabbar/select_links.png',
- selectedIconPath: 'static/tabbar/select_links_active.png',
- pagePath: 'pages/tabbar/moments/moments',
- text: '%tabbar.moments%',
- },
- {
- iconPath: 'static/tabbar/select_mine.png',
- selectedIconPath: 'static/tabbar/select_mine_active.png',
- pagePath: 'pages/tabbar/about/about',
- text: '%tabbar.about%',
- },
-]
+ {
+ iconPath: 'static/tabbar/select_home.png',
+ selectedIconPath: 'static/tabbar/select_home_active.png',
+ pagePath: 'pages/tabbar/home/home',
+ text: '%tabbar.home%'
+ },
+ {
+ iconPath: 'static/tabbar/select_category.png',
+ selectedIconPath: 'static/tabbar/select_category_active.png',
+ pagePath: 'pages/tabbar/category/category',
+ text: '%tabbar.category%'
+ },
+ {
+ iconPath: 'static/tabbar/select_gallery.png',
+ selectedIconPath: 'static/tabbar/select_gallery_active.png',
+ pagePath: 'pages/tabbar/gallery/gallery',
+ text: '%tabbar.gallery%'
+ },
+ {
+ iconPath: 'static/tabbar/select_links.png',
+ selectedIconPath: 'static/tabbar/select_links_active.png',
+ pagePath: 'pages/tabbar/moments/moments',
+ text: '%tabbar.moments%'
+ },
+ {
+ iconPath: 'static/tabbar/select_mine.png',
+ selectedIconPath: 'static/tabbar/select_mine_active.png',
+ pagePath: 'pages/tabbar/about/about',
+ text: '%tabbar.about%'
+ }
+];
// TODO: 3/3. 使用 CUSTOM_TABBAR 时,更新下面的 tabbar 配置
// 如果需要配置鼓包,需要在 'tabbar/store.ts' 里面设置,最后在 `tabbar/index.vue` 里面更改鼓包的图片
export const customTabbarList: CustomTabBarItem[] = [
- {
- text: '%tabbar.home%',
- pagePath: 'pages/index/index',
- // 注意 unocss 图标需要如下处理:(二选一)
- // 1)在fg-tabbar.vue页面上引入一下并注释掉(见tabbar/index.vue代码第2行)
- // 2)配置到 unocss.config.ts 的 safelist 中
- iconType: 'unocss',
- icon: 'i-carbon-home',
- // badge: 'dot',
- },
- // 鼓包配置示例(2025-12-31)
- // 中间鼓包tabbarItem配置:通常是扫描按钮、发布按钮、更多按钮等,点击触发业务逻辑
- // {
- // pagePath: 'pages/me/me',
- // text: '我的',
- // // 1)在fg-tabbar.vue页面上引入一下并注释掉(见tabbar/index.vue代码第2行)
- // // 2)配置到 unocss.config.ts 的 safelist 中
- // iconType: 'image',
- // icon: '/static/tabbar/scan.png',
- // isBulge: true,
- // },
- {
- pagePath: 'pages/tabbar/home/home',
- text: '%tabbar.i18n%',
- iconType: 'unocss',
- icon: 'i-carbon-ibm-watson-language-translator',
- // badge: 10,
- },
- {
- pagePath: 'pages/tabbar/about/about',
- text: '%tabbar.about%',
- // 1)在fg-tabbar.vue页面上引入一下并注释掉(见tabbar/index.vue代码第2行)
- // 2)配置到 unocss.config.ts 的 safelist 中
- iconType: 'unocss',
- icon: 'i-carbon-menu',
- // badge: 10,
- roles: ['admin'],
- },
- {
- pagePath: 'pages/tabbar/moments/moments',
- text: '%tabbar.me%',
- iconType: 'unocss',
- icon: 'i-carbon-user',
- // badge: 10,
- },
+ // {
+ // text: '%tabbar.home%',
+ // pagePath: 'pages/index/index',
+ // // 注意 unocss 图标需要如下处理:(二选一)
+ // // 1)在fg-tabbar.vue页面上引入一下并注释掉(见tabbar/index.vue代码第2行)
+ // // 2)配置到 unocss.config.ts 的 safelist 中
+ // iconType: 'unocss',
+ // icon: 'i-carbon-home',
+ // // badge: 'dot',
+ // },
+ // // 鼓包配置示例(2025-12-31)
+ // // 中间鼓包tabbarItem配置:通常是扫描按钮、发布按钮、更多按钮等,点击触发业务逻辑
+ // // {
+ // // pagePath: 'pages/me/me',
+ // // text: '我的',
+ // // // 1)在fg-tabbar.vue页面上引入一下并注释掉(见tabbar/index.vue代码第2行)
+ // // // 2)配置到 unocss.config.ts 的 safelist 中
+ // // iconType: 'image',
+ // // icon: '/static/tabbar/scan.png',
+ // // isBulge: true,
+ // // },
+ // {
+ // pagePath: 'pages/tabbar/home/home',
+ // text: '%tabbar.i18n%',
+ // iconType: 'unocss',
+ // icon: 'i-carbon-ibm-watson-language-translator',
+ // // badge: 10,
+ // },
+ // {
+ // pagePath: 'pages/tabbar/about/about',
+ // text: '%tabbar.about%',
+ // // 1)在fg-tabbar.vue页面上引入一下并注释掉(见tabbar/index.vue代码第2行)
+ // // 2)配置到 unocss.config.ts 的 safelist 中
+ // iconType: 'unocss',
+ // icon: 'i-carbon-menu',
+ // // badge: 10,
+ // roles: ['admin'],
+ // },
+ // {
+ // pagePath: 'pages/tabbar/moments/moments',
+ // text: '%tabbar.me%',
+ // iconType: 'unocss',
+ // icon: 'i-carbon-user',
+ // // badge: 10,
+ // },
- // 其他类型演示
- // 1、uiLib
- // {
- // pagePath: 'pages/index/index',
- // text: '首页',
- // iconType: 'uiLib',
- // icon: 'home',
- // },
- // 2、iconfont
- // {
- // pagePath: 'pages/index/index',
- // text: '首页',
- // // 注意 iconfont 图标需要额外加上 'iconfont',如下
- // iconType: 'iconfont',
- // icon: 'iconfont icon-my',
- // },
- // 3、image
- // {
- // pagePath: 'pages/index/index',
- // text: '首页',
- // // 使用 ‘image’时,需要配置 icon + iconActive 2张图片
- // iconType: 'image',
- // icon: '/static/tabbar/home.png',
- // iconActive: '/static/tabbar/homeHL.png',
- // },
-]
+ // 其他类型演示
+ // 1、uiLib
+ // {
+ // pagePath: 'pages/index/index',
+ // text: '首页',
+ // iconType: 'uiLib',
+ // icon: 'home',
+ // },
+ // 2、iconfont
+ // {
+ // pagePath: 'pages/index/index',
+ // text: '首页',
+ // // 注意 iconfont 图标需要额外加上 'iconfont',如下
+ // iconType: 'iconfont',
+ // icon: 'iconfont icon-my',
+ // },
+ // 3、image
+ // {
+ // pagePath: 'pages/index/index',
+ // text: '首页',
+ // // 使用 ‘image’时,需要配置 icon + iconActive 2张图片
+ // iconType: 'image',
+ // icon: '/static/tabbar/home.png',
+ // iconActive: '/static/tabbar/homeHL.png',
+ // },
+ {
+ icon: '/static/tabbar/select_home.png',
+ iconActive: '/static/tabbar/select_home_active.png',
+ iconType: 'image',
+ pagePath: 'pages/tabbar/home/home',
+ text: '%tabbar.home%'
+ },
+ {
+ icon: '/static/tabbar/select_category.png',
+ iconActive: '/static/tabbar/select_category_active.png',
+ iconType: 'image',
+ pagePath: 'pages/tabbar/category/category',
+ text: '%tabbar.category%'
+ },
+
+ {
+ icon: '/static/tabbar/select_gallery.png',
+ iconActive: '/static/tabbar/select_gallery_active.png',
+ iconType: 'image',
+ pagePath: 'pages/tabbar/gallery/gallery',
+ text: '%tabbar.gallery%'
+ },
+ {
+ icon: '/static/tabbar/select_links.png',
+ iconActive: '/static/tabbar/select_links_active.png',
+ iconType: 'image',
+ pagePath: 'pages/tabbar/moments/moments',
+ text: '%tabbar.moments%',
+ },
+ {
+ icon: '/static/tabbar/select_mine.png',
+ iconActive: '/static/tabbar/select_mine_active.png',
+ iconType: 'image',
+ pagePath: 'pages/tabbar/about/about',
+ text: '%tabbar.about%'
+ },
+
+ // 搜索按钮
+ // {
+ // icon: 'search-line',
+ // iconType: 'uiLib',
+ // isRightButton: true,
+ // pagePath: 'pages-blog/search/search',
+ // text: '%tabbar.search%',
+ // onClick: (item: CustomTabBarItem) => {
+ // uni.navigateTo({
+ // url: item.pagePath
+ // });
+ // }
+ // }
+];
/**
* 是否启用 tabbar 缓存
* NATIVE_TABBAR(1) 和 CUSTOM_TABBAR(2) 时,需要tabbar缓存
*/
-export const tabbarCacheEnable = [TABBAR_STRATEGY_MAP.NATIVE_TABBAR, TABBAR_STRATEGY_MAP.CUSTOM_TABBAR].includes(selectedTabbarStrategy)
+export const tabbarCacheEnable = [TABBAR_STRATEGY_MAP.NATIVE_TABBAR, TABBAR_STRATEGY_MAP.CUSTOM_TABBAR].includes(selectedTabbarStrategy);
/**
* 是否启用自定义 tabbar
* CUSTOM_TABBAR(2) 时,启用自定义tabbar
*/
-export const customTabbarEnable = [TABBAR_STRATEGY_MAP.CUSTOM_TABBAR].includes(selectedTabbarStrategy)
+export const customTabbarEnable = [TABBAR_STRATEGY_MAP.CUSTOM_TABBAR].includes(selectedTabbarStrategy);
/**
* 是否需要隐藏原生 tabbar
* CUSTOM_TABBAR(2) 时,需要隐藏原生tabbar
*/
-export const needHideNativeTabbar = selectedTabbarStrategy === TABBAR_STRATEGY_MAP.CUSTOM_TABBAR
+export const needHideNativeTabbar = selectedTabbarStrategy === TABBAR_STRATEGY_MAP.CUSTOM_TABBAR;
-const _tabbarList = customTabbarEnable ? customTabbarList.map(item => ({ text: item.text, pagePath: item.pagePath })) : nativeTabbarList
-export const tabbarList = customTabbarEnable ? customTabbarList : nativeTabbarList
+const _tabbarList = customTabbarEnable ? customTabbarList.filter((item) => !item.isRightButton).map((item) => ({ text: item.text, pagePath: item.pagePath })) : nativeTabbarList;
+export const tabbarList = customTabbarEnable ? customTabbarList : nativeTabbarList;
// NATIVE_TABBAR(1) 时,显示原生Tabbar,在i18n的情况下需要 setTabbarItem (框架已经处理)
-export const isNativeTabbar = selectedTabbarStrategy === TABBAR_STRATEGY_MAP.NATIVE_TABBAR
+export const isNativeTabbar = selectedTabbarStrategy === TABBAR_STRATEGY_MAP.NATIVE_TABBAR;
const _tabbar: TabBar = {
- // 只有微信小程序支持 custom。App 和 H5 不生效
- custom: selectedTabbarStrategy === TABBAR_STRATEGY_MAP.CUSTOM_TABBAR,
- color: '#303133',
- selectedColor: '#03a9f4',
- backgroundColor: '#ffffff',
- borderStyle: 'white',
- // height: '50px',
- // fontSize: '10px',
- // iconWidth: '24px',
- // spacing: '3px',
- list: _tabbarList as unknown as TabBar['list'],
-}
+ // 只有微信小程序支持 custom。App 和 H5 不生效
+ custom: selectedTabbarStrategy === TABBAR_STRATEGY_MAP.CUSTOM_TABBAR,
+ color: '#303133',
+ selectedColor: '#03a9f4',
+ backgroundColor: '#ffffff',
+ borderStyle: 'white',
+ // height: '50px',
+ // fontSize: '10px',
+ // iconWidth: '24px',
+ // spacing: '3px',
+ list: _tabbarList as unknown as TabBar['list']
+};
-export const tabBar = tabbarCacheEnable ? _tabbar : undefined
+export const tabBar = tabbarCacheEnable ? _tabbar : undefined;
diff --git a/src/tabbar/index.vue b/src/tabbar/index.vue
index 4eff1e0..830005e 100644
--- a/src/tabbar/index.vue
+++ b/src/tabbar/index.vue
@@ -1,144 +1,149 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ // 中间鼓包的样式
+ .bulge {
+ position: absolute;
+ top: -20px;
+ left: 50%;
+ transform-origin: top center;
+ transform: translateX(-50%) scale(0.5) translateY(-33%);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 250rpx;
+ height: 250rpx;
+ border-radius: 50%;
+ background-color: #fff;
+ box-shadow: inset 0 0 0 1px #fefefe;
+ }
+
\ No newline at end of file
diff --git a/src/tabbar/index.vue.bak b/src/tabbar/index.vue.bak
new file mode 100644
index 0000000..4eff1e0
--- /dev/null
+++ b/src/tabbar/index.vue.bak
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tabbar/types.ts b/src/tabbar/types.ts
index cfa6a6e..e6ddce1 100644
--- a/src/tabbar/types.ts
+++ b/src/tabbar/types.ts
@@ -1,37 +1,41 @@
-import type { TabBar } from '@uni-helper/vite-plugin-uni-pages'
-import type { UserRole } from '@/api/types/login'
-import type { RemoveLeadingSlashFromUnion } from '@/typings'
+import type { TabBar } from '@uni-helper/vite-plugin-uni-pages';
+import type { UserRole } from '@/api/types/login';
+import type { RemoveLeadingSlashFromUnion } from '@/typings';
/**
* 原生 tabbar 的单个选项配置
*/
export type NativeTabBarItem = TabBar['list'][number] & {
- pagePath: RemoveLeadingSlashFromUnion<_LocationUrl>
-}
+ pagePath: RemoveLeadingSlashFromUnion<_LocationUrl>;
+};
/** badge 显示一个数字或 小红点(样式可以直接在 tabbar/index.vue 里面修改) */
-export type CustomTabBarItemBadge = number | 'dot'
+export type CustomTabBarItemBadge = number | 'dot';
/** 自定义 tabbar 的单个选项配置 */
export interface CustomTabBarItem {
- text: string
- pagePath: RemoveLeadingSlashFromUnion<_LocationUrl>
- /** 图标类型,不建议用 image 模式,因为需要配置 2 张图,更麻烦 */
- iconType: 'uiLib' | 'unocss' | 'iconfont' | 'image'
- /**
- * icon 的路径
- * - uiLib: wot-design-uni 图标的 icon prop
- * - unocss: unocss 图标的类名
- * - iconfont: iconfont 图标的类名
- * - image: 图片的路径
- */
- icon: string
- /** 只有在 image 模式下才需要,传递的是高亮的图片 */
- iconActive?: string
- /** badge 显示一个数字或 小红点 */
- badge?: CustomTabBarItemBadge
- /** 是否是中间的鼓包tabbarItem */
- isBulge?: boolean
- // roles 不写 → 所有用户都能看到;roles 写了 → 只有匹配角色可见
- roles?: UserRole[]
+ text: string;
+ pagePath: RemoveLeadingSlashFromUnion<_LocationUrl>;
+ /** 图标类型,不建议用 image 模式,因为需要配置 2 张图,更麻烦 */
+ iconType: 'uiLib' | 'unocss' | 'iconfont' | 'image';
+ /**
+ * icon 的路径
+ * - uiLib: wot-design-uni 图标的 icon prop
+ * - unocss: unocss 图标的类名
+ * - iconfont: iconfont 图标的类名
+ * - image: 图片的路径
+ */
+ icon: string;
+ /** 只有在 image 模式下才需要,传递的是高亮的图片 */
+ iconActive?: string;
+ /** badge 显示一个数字或 小红点 */
+ badge?: CustomTabBarItemBadge;
+ /** 是否是中间的鼓包tabbarItem */
+ isBulge?: boolean;
+ // roles 不写 → 所有用户都能看到;roles 写了 → 只有匹配角色可见
+ roles?: UserRole[];
+ // 自定义右侧按钮
+ isRightButton?: boolean;
+ // 点击事件回调
+ onClick?: (item: CustomTabBarItem) => void;
}
diff --git a/src/utils/vote.ts b/src/utils/vote.ts
index 9c75c83..a84dda9 100644
--- a/src/utils/vote.ts
+++ b/src/utils/vote.ts
@@ -7,16 +7,17 @@ import { getCache, setCache } from './storage'
/** 投票 UID 缓存 key */
const UnihaloVoteUid = 'unihalo_vote_uid'
-export type VoteType = 'SINGLE' | 'MULTIPLE'
+export type VoteType = 'single' | 'multiple' | 'pk'
export type VoteState = 'not-voted' | 'voting' | 'voted' | 'vote-ended'
-/** 投票类型常量 */
-export const VOTE_TYPES: { SINGLE: VoteType, MULTIPLE: VoteType } = {
- SINGLE: 'SINGLE',
- MULTIPLE: 'MULTIPLE',
+/** 投票类型中文映射(与旧项目一致:key 为插件小写 type) */
+export const VOTE_TYPES: Record = {
+ pk: '双选PK',
+ multiple: '多选',
+ single: '单选',
}
-/** 投票状态常量 */
+/** 投票状态常量(内部状态机) */
export const VOTE_STATES: { NOT_VOTED: VoteState, VOTING: VoteState, VOTED: VoteState, VOTE_ENDED: VoteState } = {
NOT_VOTED: 'not-voted',
VOTING: 'voting',
@@ -24,6 +25,13 @@ export const VOTE_STATES: { NOT_VOTED: VoteState, VOTING: VoteState, VOTED: Vote
VOTE_ENDED: 'vote-ended',
}
+/** 投票展示状态(与旧项目 VOTE_STATES 一致:中文 + 颜色) */
+export const VOTE_STATE_LABELS: Record = {
+ 未开始: { state: '未开始', color: 'orange' },
+ 进行中: { state: '进行中', color: 'green' },
+ 已结束: { state: '已结束', color: 'red' },
+}
+
/**
* 获取投票 UID(不存在则生成)
*/
@@ -37,33 +45,31 @@ export function getOrCreateVoteUid(): string {
}
/**
- * 计算投票状态
- * @param vote 投票对象(含 startTime/endTime/options)
- * @param voteTypes 已投票项
- * @param canAnonymously 是否允许匿名
+ * 计算投票展示状态(与旧项目 calcVoteState 一致)
+ * 非 custom 期限(permanent 等)直接看 hasEnded;custom 按起止时间判断
+ * @param vote 投票对象(含 spec.timeLimit/hasEnded/startDate/endDate)
+ * @returns { state: '未开始' | '进行中' | '已结束', color: 'orange' | 'green' | 'red' }
*/
export function calcVoteState(
- vote: { startTime?: string, endTime?: string, [key: string]: unknown },
- voteTypes: string[],
- canAnonymously: boolean,
-): VoteState {
- const now = Date.now()
- const startTime = vote.startTime ? new Date(vote.startTime).getTime() : now
- const endTime = vote.endTime ? new Date(vote.endTime).getTime() : now
+ vote: { spec?: { timeLimit?: string, hasEnded?: boolean, startDate?: string, endDate?: string, [key: string]: unknown } },
+): { state: string, color: string } {
+ if (vote.spec?.timeLimit !== 'custom') {
+ return vote.spec?.hasEnded ? VOTE_STATE_LABELS['已结束'] : VOTE_STATE_LABELS['进行中']
+ }
- if (endTime < now)
- return VOTE_STATES.VOTE_ENDED
- if (startTime > now)
- return VOTE_STATES.NOT_VOTED
- if (voteTypes.length !== 0)
- return VOTE_STATES.VOTED
- if (!canAnonymously)
- return VOTE_STATES.NOT_VOTED
- return VOTE_STATES.VOTING
+ const nowTime = new Date().getTime()
+ const startTime = vote.spec?.startDate ? new Date(vote.spec.startDate).getTime() : nowTime
+ const endTime = vote.spec?.endDate ? new Date(vote.spec.endDate).getTime() : nowTime
+
+ if (nowTime < startTime)
+ return VOTE_STATE_LABELS['未开始']
+ if (nowTime < endTime)
+ return VOTE_STATE_LABELS['进行中']
+ return vote.spec?.hasEnded ? VOTE_STATE_LABELS['已结束'] : VOTE_STATE_LABELS['进行中']
}
/**
- * 计算选项票数占比
+ * 计算选项票数占比(与旧项目一致:整数百分比)
* @param vote 投票对象(含 stats.voteCount)
* @param option 选项(含 count)
*/
@@ -72,7 +78,7 @@ export function calcVotePercent(vote: { stats?: { voteCount?: number } }, option
const count = option.count || 0
if (total === 0)
return 0
- return Number(((count / total) * 100).toFixed(2))
+ return Math.round((count / total) * 100)
}
/** 投票缓存 key 前缀 */