1
0
miroir de https://github.com/ialley-workshop-open/uni-halo.git synchronisé 2026-06-12 13:19:31 +08:00

feat: 优化投票功能,调整API

Cette révision appartient à :
小莫唐尼
2025-08-21 19:32:22 +08:00
Parent 5606e8c59f
révision 31ade9908a
8 fichiers modifiés avec 474 ajouts et 227 suppressions
+1 -5
Voir le fichier
@@ -252,11 +252,7 @@ export default {
* 获取投票列表 * 获取投票列表
*/ */
getVoteList: (params) => { getVoteList: (params) => {
return HttpHandler.Get(`/apis/console.api.vote.kunkunyu.com/v1alpha1/votes`, params, { return HttpHandler.Get(`/apis/api.vote.kunkunyu.com/v1alpha1/votes`, params)
custom: {
personalToken: getPersonalToken()
}
})
}, },
/** /**
* 获取投票详情 * 获取投票详情
+5
Voir le fichier
@@ -342,3 +342,8 @@ $spacing-sizes: (
.justify-center { .justify-center {
justify-content: center; justify-content: center;
} }
.text-break {
overflow-wrap: break-word;
word-break: break-all;
}
+176 -83
Voir le fichier
@@ -5,6 +5,7 @@
</view> </view>
<template v-else> <template v-else>
<view class="vote-card-head flex flex-col"> <view class="vote-card-head flex flex-col">
<view class="flex justify-between">
<view class="flex"> <view class="flex">
<tm-tags color="orange" style="min-width:40rpx;" :shadow="0" rounded size="s" <tm-tags color="orange" style="min-width:40rpx;" :shadow="0" rounded size="s"
model="fill">{{ index+1 }}</tm-tags> model="fill">{{ index+1 }}</tm-tags>
@@ -16,14 +17,22 @@
<tm-tags v-else-if="vote.spec.type==='pk'" color="light-blue" :shadow="0" rounded size="s" <tm-tags v-else-if="vote.spec.type==='pk'" color="light-blue" :shadow="0" rounded size="s"
model="fill">双选PK</tm-tags> model="fill">双选PK</tm-tags>
<tm-tags v-if="vote.spec.hasEnded" color="red" rounded :shadow="0" size="s" <tm-tags v-if="vote.spec._state=='已结束'" color="red" size="s" rounded :shadow="0"
model="text">已结束</tm-tags> model="fill">已结束</tm-tags>
<tm-tags v-else color="green" rounded size="s" :shadow="0" model="text">进行中</tm-tags> <tm-tags v-else-if="vote.spec._state=='未开始'" color="orange" size="s" rounded :shadow="0"
model="fill">未开始</tm-tags>
<tm-tags v-else-if="vote.spec._state=='进行中'" color="green" size="s" rounded :shadow="0"
model="fill">进行中</tm-tags>
</view>
<view class="flex-shrink">
<tm-button theme="light-blue" :shadow="0" dense size="s"
@click="handleToVoteDetail(vote)">查看投票详情</tm-button>
</view>
</view> </view>
<view class="title"> <view class="title">
{{ vote.spec.title }} {{ vote.spec.title }}
</view> </view>
</view> </view>
<view class="vote-card-body"> <view class="vote-card-body">
@@ -35,38 +44,58 @@
<!-- 单选 --> <!-- 单选 -->
<view v-if="vote.spec.type==='single'" class="single"> <view v-if="vote.spec.type==='single'" class="single">
<view class="w-full flex flex-col uh-gap-8"> <view class="w-full flex flex-col uh-gap-8">
<tm-button v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex" :shadow="0" <template v-if="vote.spec.isVoted">
:theme="option.checked?'light-blue':'grey-lighten-3'" :plan="false" size="m" <view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
:height="72" :block="true" class="flex-1 w-full" class="is-voted-item" :class="[option.checked?'selected':'']" :style="{
@click="handleSelectSingleOption(option)"> '--percent': option.percent + '%'
<view class="flex flex-between w-full"> }">
<text class="text-align-left text-overflow"> <view class="is-voted-item-content flex w-full flex-between uh-gap-4">
<view class="text-align-left flex-1 text-break">
{{option.title }} {{option.title }}
</text>
<text v-if="vote.spec.isVoted" class="flex-shrink ml-12">
{{option.percent }}%
</text>
</view> </view>
</tm-button> <view class="flex-shrink ">
{{option.percent }}%
</view>
</view>
</view>
</template>
<template v-else>
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
class="vote-select-option flex-1 w-full text-break"
:class="[option.checked?'light-blue':'grey-lighten-3']"
@click="handleSelectSingleOption(option)">
{{option.title }}
</view>
</template>
</view> </view>
</view> </view>
<!-- 多选 --> <!-- 多选 -->
<view v-else-if="vote.spec.type==='multiple'" class="multiple"> <view v-else-if="vote.spec.type==='multiple'" class="multiple">
<view class="w-full flex flex-col uh-gap-8"> <view class="w-full flex flex-col uh-gap-8">
<tm-button v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex" :shadow="0" <template v-if="vote.spec.isVoted">
:theme="option.checked?'light-blue':'grey-lighten-3'" :plan="false" size="m" <view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
:height="72" :block="true" class="flex-1 full" class="is-voted-item" :class="[option.checked?'selected':'']" :style="{
@click="handleSelectCheckboxOption(option)"> '--percent': option.percent + '%'
<view class="flex flex-between w-full"> }">
<text class="text-align-left text-overflow"> <view class="is-voted-item-content flex w-full flex-between uh-gap-4">
<view class="text-align-left flex-1 text-break">
{{option.title }} {{option.title }}
</text>
<text v-if="vote.spec.isVoted" class="flex-shrink ml-12">
{{option.percent }}%
</text>
</view> </view>
</tm-button> <view class="flex-shrink ">
{{option.percent }}%
</view>
</view>
</view>
</template>
<template v-else>
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
class="vote-select-option flex-1 w-full text-break"
:class="[option.checked?'light-blue':'grey-lighten-3']"
@click="handleSelectCheckboxOption(option)">
{{option.title }}
</view>
</template>
</view> </view>
</view> </view>
@@ -84,16 +113,29 @@
</view> </view>
<view class="option-foot w-full flex flex-between uh-mt-12"> <view class="option-foot w-full flex flex-between uh-mt-12">
<view class="w-full flex flex-col uh-gap-8"> <view class="w-full flex flex-col uh-gap-8">
<tm-button v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex" <template v-if="vote.spec.isVoted">
:shadow="0" :theme="option.checked?'light-blue':'grey-lighten-3'" :plan="false" <view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
size="m" :height="72" :block="true" class="flex-1" class="is-voted-item" :class="[option.checked?'selected':'']" :style="{
@click="handleSelectSingleOption(option)"> '--percent': option.percent + '%'
<view class="flex flex-between w-full"> }">
<text class="text-align-left text-overflow"> <view class="is-voted-item-content flex w-full flex-between uh-gap-4">
选项{{ optionIndex+1}}{{option.title }} <view class="text-align-left flex-1 text-break">
</text> {{option.title }}
</view> </view>
</tm-button> <view class="flex-shrink ">
{{option.percent }}%
</view>
</view>
</view>
</template>
<template v-else>
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
class="vote-select-option flex-1 w-full text-break"
:class="[option.checked?'light-blue':'grey-lighten-3']"
@click="handleSelectSingleOption(option)">
选项{{ optionIndex+1}}{{option.title }}
</view>
</template>
</view> </view>
</view> </view>
</view> </view>
@@ -101,8 +143,15 @@
</view> </view>
<view class="vote-card-foot flex flex-between"> <view class="vote-card-foot flex flex-between">
<view class="left flex"> <view class="left flex">
<tm-tags color="grey-darken-2" rounded size="s" <tm-tags v-if="vote.spec.timeLimit==='permanent'" color="grey-darken-2" rounded size="s"
model="text">{{ {d: vote.spec.endDate, f: 'yyyy-MM-dd HH:mm'} | formatTime }} 结束</tm-tags> model="text">结束永久有效 </tm-tags>
<tm-tags v-else color="grey-darken-2" rounded size="s" model="text">
<template
v-if="vote.spec._state=='未开始'">开始{{ {d: vote.spec.startDate, f: 'yyyy-MM-dd HH:mm'} | formatTime }}
</template>
<template v-else>结束{{ {d: vote.spec.endDate, f: 'yyyy-MM-dd HH:mm'} | formatTime }}
</template>
</tm-tags>
</view> </view>
<view class="right flex flex-end"> <view class="right flex flex-end">
@@ -114,7 +163,7 @@
<view v-if="submitForm.voteData.length!==0" class="box-border uh-mt-12 w-full uh-px-2"> <view v-if="submitForm.voteData.length!==0" class="box-border uh-mt-12 w-full uh-px-2">
<tm-button v-if="!vote.spec.canAnonymously" theme="red" :shadow="0" class="w-full" text :block="true" <tm-button v-if="!vote.spec.canAnonymously" theme="red" :shadow="0" class="w-full" text :block="true"
:height="72" @click="handleSubmit()">允许匿名投票</tm-button> :height="72" @click="handleSubmit()">支持匿名投票(点击)</tm-button>
<tm-button v-else-if="fnCalcIsVoted()" theme="white" text :block="true" :height="72" <tm-button v-else-if="fnCalcIsVoted()" theme="white" text :block="true" :height="72"
class="w-full">您已参与投票</tm-button> class="w-full">您已参与投票</tm-button>
<tm-button v-else theme="light-blue" class="w-full" :block="true" :height="72" <tm-button v-else theme="light-blue" class="w-full" :block="true" :height="72"
@@ -184,6 +233,7 @@
tempVoteRes.vote.spec.isVoted = this.fnCalcIsVoted() tempVoteRes.vote.spec.isVoted = this.fnCalcIsVoted()
tempVoteRes.vote.spec.disabled = this.fnCalcIsVoted() tempVoteRes.vote.spec.disabled = this.fnCalcIsVoted()
tempVoteRes.vote.spec._state = this.handleCalcVoteState(tempVoteRes.vote)
tempVoteRes.vote.spec.options.map((option, index) => { tempVoteRes.vote.spec.options.map((option, index) => {
option.value = option.id option.value = option.id
@@ -236,7 +286,23 @@
const checked = data.selected.includes(option.id) const checked = data.selected.includes(option.id)
return checked return checked
}, },
handleCalcVoteState(vote) {
if (vote.spec.timeLimit !== 'custom') {
return vote.spec.hasEnd ? "已结束" : "进行中"
}
const nowTime = new Date().getTime()
const startTime = new Date(vote.spec.startDate).getTime()
const endTime = new Date(vote.spec.endDate).getTime()
if (nowTime < startTime) {
return "未开始"
}
if (nowTime < endTime) {
return "进行中"
}
return vote.spec.hasEnd ? "已结束" : "进行中"
},
formatJsonStr(jsonStr) { formatJsonStr(jsonStr) {
return jsonStr ? JSON.parse(jsonStr) : {} return jsonStr ? JSON.parse(jsonStr) : {}
}, },
@@ -251,12 +317,13 @@
uni.showModal({ uni.showModal({
icon: "none", icon: "none",
title: "提示", title: "提示",
content: "该投票不允许匿名,请到博主的 网站端 进行投票!", content: "该投票不支持匿名,请到博主的 网站端 进行投票!",
cancelColor: "#666666", cancelColor: "#666666",
cancelText: "关闭", cancelText: "关闭",
confirmText: "复制地址", confirmText: "复制地址",
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
console.log("this.article",this.article)
const articleUrl = this.$baseApiUrl + (this.article?.status?.permalink ?? "") const articleUrl = this.$baseApiUrl + (this.article?.status?.permalink ?? "")
this.$utils.copyText(articleUrl, "原文地址复制成功") this.$utils.copyText(articleUrl, "原文地址复制成功")
} }
@@ -294,6 +361,10 @@
}, },
handleSelectSingleOption(option) { handleSelectSingleOption(option) {
if (this.vote.spec._state == '未开始') {
this.showToast(`投票未开始`)
return
}
if (this.vote.spec.disabled) return if (this.vote.spec.disabled) return
this.vote.spec.options.map(item => { this.vote.spec.options.map(item => {
if (option.id == item.id) { if (option.id == item.id) {
@@ -306,15 +377,16 @@
}, },
handleSelectCheckboxOption(option) { handleSelectCheckboxOption(option) {
if (this.vote.spec._state == '未开始') {
this.showToast(`投票未开始`)
return
}
if (this.vote.spec.disabled) return if (this.vote.spec.disabled) return
const checkedList = this.vote.spec.options.filter(x => x.checked && x.id != option.id) const checkedList = this.vote.spec.options.filter(x => x.checked && x.id != option.id)
if (this.vote.spec.type === 'multiple' && checkedList.length >= this.vote.spec.maxVotes) { if (this.vote.spec.type === 'multiple' && checkedList.length >= this.vote.spec.maxVotes) {
uni.showToast({ this.showToast(`最多选择 ${this.vote.spec.maxVotes}`)
icon: "none",
title: `最多选择 ${this.vote.spec.maxVotes}`
})
return return
} }
@@ -324,6 +396,18 @@
} }
}) })
this.submitForm.voteData = this.vote.spec.options.filter(x => x.checked).map(item => item.id) this.submitForm.voteData = this.vote.spec.options.filter(x => x.checked).map(item => item.id)
},
handleToVoteDetail(vote) {
uni.navigateTo({
url: `/pagesA/vote-detail/vote-detail?name=${vote.metadata.name}`
});
},
showToast(content) {
uni.showToast({
icon: "none",
title: content,
mask: true
})
} }
} }
} }
@@ -402,51 +486,59 @@
.left {} .left {}
} }
.is-voted-item {
min-height: 72rpx;
box-sizing: border-box;
position: relative;
border-radius: 12rpx;
background-color: rgba(229, 229, 229, 0.75);
font-size: 24rpx;
overflow: hidden;
&::before {
content: "";
width: var(--percent);
position: absolute;
left: 0;
top: 0;
bottom: 0;
background-color: rgba(208, 208, 208, 1);
z-index: 0;
border-radius: 6rpx;
}
&.selected {
background-color: rgba(3, 169, 244, 0.35);
color: #ffffff;
&::before {
background-color: rgba(3, 169, 244, 1);
}
}
}
.is-voted-item-content {
box-sizing: border-box;
min-height: 72rpx;
padding: 12rpx 24rpx;
position: relative;
z-index: 2;
}
.vote-select-option {
box-sizing: border-box;
padding: 20rpx 24rpx;
font-size: 24rpx;
border-radius: 12rpx;
transition: all 0.1s ease-in-out;
}
.single { .single {
::v-deep { ::v-deep {}
.tm-groupradio {
width: 100%;
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
gap: 12rpx 0;
}
.tm-checkbox {
box-sizing: border-box;
display: block;
padding: 0 12rpx;
width: 50%;
}
.tm-button-label {
width: 100%;
}
}
} }
.multiple { .multiple {
::v-deep { ::v-deep {}
.tm-groupcheckbox {
width: 100%;
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
gap: 12rpx 0;
}
.tm-checkbox {
box-sizing: border-box;
display: block;
padding: 0 12rpx;
width: 50%;
}
.tm-button-label {
width: 100%;
}
}
} }
.pk { .pk {
@@ -455,6 +547,7 @@
padding: 0 12rpx; padding: 0 12rpx;
::v-deep { ::v-deep {
.pk-container { .pk-container {
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
+26 -13
Voir le fichier
@@ -1,7 +1,7 @@
<template> <template>
<view class="vote-card" @click="$emit('on-click',vote)"> <view class="vote-card" @click="$emit('on-click',vote)">
<view class="vote-card-head flex"> <view class="vote-card-head flex">
<view class="left flex flex-center"> <view class="left flex flex-center w-full">
<view class="flex-shrink"> <view class="flex-shrink">
<tm-tags v-if="vote.spec.type==='single'" color="light-blue" :shadow="0" rounded size="s" <tm-tags v-if="vote.spec.type==='single'" color="light-blue" :shadow="0" rounded size="s"
model="fill">单选</tm-tags> model="fill">单选</tm-tags>
@@ -14,12 +14,12 @@
{{ vote.spec.title }} {{ vote.spec.title }}
</view> </view>
</view> </view>
<view class="flex-shrink right flex flex-end"> <view v-if="false" class="flex-shrink right flex flex-end">
<tm-tags v-if="vote.spec.hasEnded" color="red" rounded :shadow="0" size="s" model="text">已结束</tm-tags> <tm-tags v-if="vote.spec.hasEnded" color="red" rounded :shadow="0" size="s" model="text">已结束</tm-tags>
<tm-tags v-else color="green" rounded size="s" :shadow="0" model="text">进行中</tm-tags> <tm-tags v-else color="green" rounded size="s" :shadow="0" model="text">进行中</tm-tags>
</view> </view>
</view> </view>
<view class="vote-card-body"> <view class="vote-card-body w-full">
<view v-if="vote.spec.remark" class="remark text-overflow-2 text-size-s"> <view v-if="vote.spec.remark" class="remark text-overflow-2 text-size-s">
{{vote.spec.remark}} {{vote.spec.remark}}
@@ -94,13 +94,29 @@
</view> </view>
<view class="vote-card-foot flex flex-between"> <view class="vote-card-foot flex flex-between">
<view class="left flex"> <view class="left flex">
<tm-tags color="grey-darken-2" rounded size="s" <tm-tags v-if="vote.spec._state=='已结束'" color="red" size="s" rounded :shadow="0"
model="text">{{ {d: vote.spec.endDate, f: 'yyyy-MM-dd HH:mm'} | formatTime }} 结束</tm-tags> model="text">结束</tm-tags>
<tm-tags v-else-if="vote.spec._state=='未开始'" color="orange" size="s" rounded :shadow="0"
model="text">未开始</tm-tags>
<tm-tags v-else-if="vote.spec._state=='进行中'" color="green" size="s" rounded :shadow="0"
model="text">进行中</tm-tags>
<tm-tags v-if="vote.spec.isVoted" color="blue" rounded size="s" model="text">已投票</tm-tags>
<tm-tags v-if="vote.spec.timeLimit==='permanent'" color="grey-darken-2" rounded size="s"
model="text">结束永久有效 </tm-tags>
<tm-tags v-else color="grey-darken-2" rounded size="s" model="text">
<template
v-if="vote.spec._state=='未开始'">开始{{ {d: vote.spec.startDate, f: 'yyyy-MM-dd HH:mm'} | formatTime }}
</template>
<template v-else>结束{{ {d: vote.spec.endDate, f: 'yyyy-MM-dd HH:mm'} | formatTime }}
</template>
</tm-tags>
</view> </view>
<view class="right flex flex-end"> <view v-if="false" class="right flex flex-end">
<tm-tags color="grey-darken-2" rounded size="s" model="text">{{ vote.stats.voteCount }} 人已参与</tm-tags> <tm-tags v-if="false" color="grey-darken-2" rounded size="s" model="text">{{ vote.stats.voteCount }}
<tm-tags v-if="vote.spec.isVoted" color="blue" rounded size="s" model="text">已投票</tm-tags> 人已参与</tm-tags>
</view> </view>
</view> </view>
</view> </view>
@@ -141,7 +157,6 @@
default: false default: false
} }
}, },
methods: { methods: {
onOptionRadioChange(e) { onOptionRadioChange(e) {
console.log("onOptionRadioChange", e) console.log("onOptionRadioChange", e)
@@ -197,9 +212,9 @@
.remark { .remark {
box-sizing: border-box; box-sizing: border-box;
padding: 12rpx 6rpx;
padding-top: 0; padding-top: 0;
color: rgba(0, 0, 0, 0.75); color: rgba(0, 0, 0, 0.75);
margin-bottom: 12rpx;
} }
} }
@@ -209,9 +224,7 @@
margin-top: 6px; margin-top: 6px;
border-top: 2rpx solid #F7F7F7; border-top: 2rpx solid #F7F7F7;
.left{ .left {}
}
} }
+1 -1
Voir le fichier
@@ -15,7 +15,7 @@
const _DEV_ = false const _DEV_ = false
const _DEV_TO_TYPE_ = "page" const _DEV_TO_TYPE_ = "page"
const _DEV_TO_PATH_ = "" const _DEV_TO_PATH_ = "/pagesA/article-detail/article-detail?name=077efb85-3544-4307-b24c-537a9e53f116"
export default { export default {
mixins: [pluginAvailableMixin], mixins: [pluginAvailableMixin],
+21 -8
Voir le fichier
@@ -115,23 +115,27 @@
{{ voteIsOpen?'收起':'展开' }} {{ voteIsOpen?'收起':'展开' }}
</text> </text>
</view> </view>
<template v-if="reloadVote">
<view v-show="voteIsOpen" class="flex flex-col uh-gap-8 uh-mt-8"> <view v-show="voteIsOpen" class="flex flex-col uh-gap-8 uh-mt-8">
<PluginUnavailable v-if="!uniHaloPluginAvailable" :article="result" :pluginId="uniHaloPluginId" <PluginUnavailable v-if="!uniHaloPluginAvailable" :pluginId="uniHaloPluginId"
:error-text="uniHaloPluginAvailableError" :useDecoration="false" :customStyle="{ :error-text="uniHaloPluginAvailableError" :useDecoration="false" :customStyle="{
width:'100%',borderRadius:'16rpx' }" /> width:'100%',borderRadius:'16rpx' }" />
<template v-else> <template v-else>
<ArticleVote v-for="(voteId,voteIdIndex) in result._voteIds" :key="voteId" :voteId="voteId" <ArticleVote v-for="(voteId,voteIdIndex) in result._voteIds" :key="voteId"
:index="voteIdIndex"> :voteId="voteId" :article="result" :index="voteIdIndex">
</ArticleVote> </ArticleVote>
<view v-show="!voteIsOpen" class="vote-tip" @click="voteIsOpen=!voteIsOpen">
投票已收起点击展开 {{result._voteIds.length}} 个投票项
</view>
</template> </template>
</view> </view>
<view v-show="uniHaloPluginAvailable && !voteIsOpen" class="vote-tip"
@click="voteIsOpen=!voteIsOpen">
投票已收起点击展开 {{result._voteIds.length}} 个投票项
</view>
<view v-show="!uniHaloPluginAvailable && !voteIsOpen" class="vote-tip" <view v-show="!uniHaloPluginAvailable && !voteIsOpen" class="vote-tip"
@click="voteIsOpen=!voteIsOpen"> @click="voteIsOpen=!voteIsOpen">
提示区域已收起点击显示 提示区域已收起点击显示
</view> </view>
</template>
</view> </view>
<!-- 版权声明 --> <!-- 版权声明 -->
@@ -409,7 +413,8 @@
commentListScrollTop: 0, commentListScrollTop: 0,
voteIsOpen: true voteIsOpen: true,
reloadVote: false
}; };
}, },
computed: { computed: {
@@ -469,7 +474,14 @@
this.queryParams.name = e.name; this.queryParams.name = e.name;
this.fnGetData(); this.fnGetData();
}, },
onShow() {
if (this.reloadVote) {
this.reloadVote = false
setTimeout(() => {
this.reloadVote = true
}, 100);
}
},
onPullDownRefresh() { onPullDownRefresh() {
this.fnGetData(); this.fnGetData();
}, },
@@ -540,6 +552,7 @@
this.result = tempResult; this.result = tempResult;
this.fnSetPageTitle('文章详情'); this.fnSetPageTitle('文章详情');
this.loading = 'success'; this.loading = 'success';
this.reloadVote = true;
this.fnHandleSetFlotButtonItems(this.haloConfigs); this.fnHandleSetFlotButtonItems(this.haloConfigs);
this.handleQueryCommentListScrollTop() this.handleQueryCommentListScrollTop()
}) })
+201 -91
Voir le fichier
@@ -23,9 +23,16 @@
model="fill">双选PK</tm-tags> model="fill">双选PK</tm-tags>
</view> </view>
<view class=""> <view class="">
投票状态<tm-tags v-if="vote.spec.hasEnded" color="red" size="xs" :shadow="0" 投票状态<tm-tags v-if="vote.spec._state=='已结束'" color="red" size="xs" :shadow="0"
model="fill">已结束</tm-tags> model="fill">已结束</tm-tags>
<tm-tags v-else color="green" size="xs" :shadow="0" model="fill">进行中</tm-tags> <tm-tags v-else-if="vote.spec._state=='未开始'" color="orange" size="xs" :shadow="0"
model="fill">
未开始
</tm-tags>
<tm-tags v-else-if="vote.spec._state=='进行中'" color="green" size="xs" :shadow="0"
model="fill">
进行中
</tm-tags>
</view> </view>
<view class=""> <view class="">
投票方式<tm-tags v-if="vote.spec.canAnonymously" color="light-blue" size="xs" :shadow="0" 投票方式<tm-tags v-if="vote.spec.canAnonymously" color="light-blue" size="xs" :shadow="0"
@@ -33,7 +40,15 @@
<tm-tags v-else color="red" size="xs" :shadow="0" model="fill">不匿名</tm-tags> <tm-tags v-else color="red" size="xs" :shadow="0" model="fill">不匿名</tm-tags>
</view> </view>
<view class=""> <view class="">
截止时间{{ {d: vote.spec.endDate, f: 'yyyy-MM-dd HH:mm'} | formatTime }} 开始时间{{ {d: vote.spec.startDate, f: 'yyyy-MM-dd HH:mm'} | formatTime }}
</view>
<view class="">
<text v-if="vote.spec.timeLimit==='permanent'">
结束时间永久有效
</text>
<text v-else>
结束时间{{ {d: vote.spec.endDate, f: 'yyyy-MM-dd HH:mm'} | formatTime }}
</text>
</view> </view>
</view> </view>
</view> </view>
@@ -53,37 +68,57 @@
class="sub-title-count">最多选择 {{ vote.spec.maxVotes }} </text> </view> class="sub-title-count">最多选择 {{ vote.spec.maxVotes }} </text> </view>
<view v-if="vote.spec.type==='single'" class="single"> <view v-if="vote.spec.type==='single'" class="single">
<view class="w-full flex flex-col uh-gap-8"> <view class="w-full flex flex-col uh-gap-8">
<tm-button v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex" <template v-if="vote.spec.isVoted">
:shadow="0" :theme="option.checked?'light-blue':'grey-lighten-3'" :plan="false" <view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
size="m" :height="72" :block="true" class="flex-1 w-full" class="is-voted-item" :class="[option.checked?'selected':'']" :style="{
@click="handleSelectSingleOption(option)"> '--percent':option.percent+ '%'
<view class="flex flex-between w-full"> }">
<text class="text-align-left text-overflow"> <view class="is-voted-item-content flex w-full flex-between uh-gap-8">
<view class="flex-1 text-align-left text-break">
{{option.title }} {{option.title }}
</text>
<text v-if="vote.spec.isVoted" class="flex-shrink ml-12">
{{option.percent }}%
</text>
</view> </view>
</tm-button> <view class="flex-shrink">
{{option.percent }}%
</view>
</view>
</view>
</template>
<template v-else>
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
class="vote-select-option flex-1 w-full text-break"
:class="[option.checked?'light-blue':'grey-lighten-3']"
@click="handleSelectSingleOption(option)">
{{option.title }}
</view>
</template>
</view> </view>
</view> </view>
<view v-else-if="vote.spec.type==='multiple'" class="multiple"> <view v-else-if="vote.spec.type==='multiple'" class="multiple">
<view class="w-full flex flex-col uh-gap-8"> <view class="w-full flex flex-col uh-gap-8">
<tm-button v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex" <template v-if="vote.spec.isVoted">
:shadow="0" :theme="option.checked?'light-blue':'grey-lighten-3'" :plan="false" <view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
size="m" :height="72" :block="true" class="flex-1 full" class="is-voted-item" :class="[option.checked?'selected':'']" :style="{
@click="handleSelectCheckboxOption(option)"> '--percent':option.percent + '%'
<view class="flex flex-between w-full"> }">
<text class="text-align-left text-overflow"> <view class="is-voted-item-content flex w-full flex-between uh-gap-8">
<view class="flex-1 text-align-left text-break">
{{option.title }} {{option.title }}
</text>
<text v-if="vote.spec.isVoted" class="flex-shrink ml-12">
{{option.percent }}%
</text>
</view> </view>
</tm-button> <view class="flex-shrink">
{{option.percent }}%
</view>
</view>
</view>
</template>
<template v-else>
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
class="vote-select-option flex-1 w-full text-break"
:class="[option.checked?'light-blue':'grey-lighten-3']"
@click="handleSelectCheckboxOption(option)">
{{option.title }}
</view>
</template>
</view> </view>
</view> </view>
@@ -98,21 +133,32 @@
</view> </view>
</view> </view>
</view> </view>
<view class="option-foot w-full flex flex-between"> <view class="option-foot w-full flex flex-col uh-gap-8">
<view class="w-full flex flex-between uh-gap-8"> <template v-if="vote.spec.isVoted">
<tm-button v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex" <view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
:shadow="0" :theme="option.checked?'light-blue':'grey-lighten-3'" :plan="false" class="is-voted-item" :class="[option.checked?'selected':'']" :style="{
size="m" :height="72" :block="true" class="flex-1" '--percent': option.percent + '%'
@click="handleSelectSingleOption(option)"> }">
<view class="flex flex-between w-full"> <view class="is-voted-item-content flex w-full flex-between uh-gap-8">
<text class="text-align-left text-overflow"> <view class="flex-1 text-align-left text-break">
选项{{ optionIndex+1}}{{option.title }} 选项{{ optionIndex+1}}{{option.title }}
</text>
</view> </view>
</tm-button> <view class="flex-shrink">
{{option.percent }}%
</view> </view>
</view> </view>
</view> </view>
</template>
<template v-else>
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
class="vote-select-option flex-1 w-full text-break"
:class="[option.checked?'light-blue':'grey-lighten-3']"
@click="handleSelectSingleOption(option)">
选项{{ optionIndex+1}}{{option.title }}
</view>
</template>
</view>
</view>
</view> </view>
</view> </view>
@@ -122,21 +168,32 @@
<view class=""> <view class="">
<tm-tags color="grey-darken-4" size="s" model="text">{{ vote.stats.voteCount }} <tm-tags color="grey-darken-4" size="s" model="text">{{ vote.stats.voteCount }}
人已参与</tm-tags> 人已参与</tm-tags>
<tm-tags v-if="vote.spec.isVoted" color="blue" rounded size="s" model="text">已投票</tm-tags> <!-- <tm-tags v-if="vote.spec.isVoted" color="blue" rounded size="s" model="text">已投票</tm-tags> -->
</view> </view>
</view> </view>
</view> </view>
<!-- 暂时不做,目前没有意义 -->
<!-- <view v-if="vote.spec.canSeeVoters" class="vote-card">
<view class="vote-card-body">
<view class="sub-title"> 投票用户 </view>
<view class="">
</view>
</view>
</view> -->
<view class="vote-submit flex w-full flex-center" :style="{ <view class="vote-submit flex w-full flex-center" :style="{
paddingBottom:safeAreaBottom + 'rpx' paddingBottom:safeAreaBottom + 'rpx'
}"> }">
<tm-button v-if="!vote.spec.canAnonymously" theme="red" :shadow="0" class="w-full" text <tm-button v-if="!vote.spec.canAnonymously" theme="red" :shadow="0" class="w-full" text
:block="true" @click="handleSubmit()">允许匿名投票</tm-button> :block="true" @click="handleSubmit()">支持匿名投票</tm-button>
<tm-button v-else-if="fnCalcIsVoted()" theme="white" text :block="true" <tm-button v-else-if="fnCalcIsVoted()" theme="white" text :block="true"
class="w-full">您已参与投票</tm-button> class="w-full">您已参与投票</tm-button>
<tm-button v-else-if="submitForm.voteData.length===0" theme="white" text class="w-full" <tm-button v-else-if="submitForm.voteData.length===0" theme="white" text class="w-full"
:block="true" @click="handleSubmitTip()">提交投票请选择选项</tm-button> :block="true" @click="handleSubmitTip()">提交投票请选择选项</tm-button>
<tm-button v-else theme="light-blue" class="w-full" :block="true" <tm-button v-else theme="light-blue" class="w-full" :block="true" :loading="submitLoading"
@click="handleSubmit()">提交投票</tm-button> :disabled="submitLoading" @click="handleSubmit()">提交投票</tm-button>
</view> </view>
</block> </block>
</block> </block>
@@ -170,6 +227,7 @@
return { return {
safeAreaBottom: 24, safeAreaBottom: 24,
loading: 'loading', loading: 'loading',
submitLoading: false,
pageTitle: '加载中...', pageTitle: '加载中...',
name: '', name: '',
@@ -184,7 +242,7 @@
onLoad(e) { onLoad(e) {
this.name = e.name; this.name = e.name;
this.fnGetData(); this.fnGetData();
this.fnGetVoteUserList();
// #ifndef H5 // #ifndef H5
const systemInfo = uni.getSystemInfoSync(); const systemInfo = uni.getSystemInfoSync();
this.safeAreaBottom = systemInfo.safeAreaInsets.bottom + 12; this.safeAreaBottom = systemInfo.safeAreaInsets.bottom + 12;
@@ -192,6 +250,7 @@
}, },
onPullDownRefresh() { onPullDownRefresh() {
this.fnGetData(); this.fnGetData();
this.fnGetVoteUserList();
}, },
methods: { methods: {
fnGetData() { fnGetData() {
@@ -207,6 +266,7 @@
tempVoteRes.vote.spec.isVoted = this.fnCalcIsVoted() tempVoteRes.vote.spec.isVoted = this.fnCalcIsVoted()
tempVoteRes.vote.spec.disabled = this.fnCalcIsVoted() tempVoteRes.vote.spec.disabled = this.fnCalcIsVoted()
tempVoteRes.vote.spec._state = this.handleCalcVoteState(tempVoteRes.vote)
tempVoteRes.vote.spec.options.map((option, index) => { tempVoteRes.vote.spec.options.map((option, index) => {
option.value = option.id option.value = option.id
@@ -227,9 +287,7 @@
return option return option
}) })
this.vote = tempVoteRes.vote this.vote = tempVoteRes.vote
console.log("this.vote", this.vote)
this.detail = tempVoteRes; this.detail = tempVoteRes;
setTimeout(() => { setTimeout(() => {
@@ -249,6 +307,16 @@
}, 200); }, 200);
}); });
}, },
fnGetVoteUserList() {
this.$httpApi.v2
.getVoteUserList(this.name)
.then(res => {
console.log("查询投票用户列表")
})
.catch(err => {
console.log("err")
})
},
fnCalcPercent(voteOption, stats) { fnCalcPercent(voteOption, stats) {
if (!this.fnCalcIsVoted()) return 0; if (!this.fnCalcIsVoted()) return 0;
if (!stats?.voteDataList) return 0; if (!stats?.voteDataList) return 0;
@@ -268,7 +336,23 @@
const checked = data.selected.includes(option.id) const checked = data.selected.includes(option.id)
return checked return checked
}, },
handleCalcVoteState(vote) {
if (vote.spec.timeLimit !== 'custom') {
return vote.spec.hasEnd ? "已结束" : "进行中"
}
const nowTime = new Date().getTime()
const startTime = new Date(vote.spec.startDate).getTime()
const endTime = new Date(vote.spec.endDate).getTime()
if (nowTime < startTime) {
return "未开始"
}
if (nowTime < endTime) {
return "进行中"
}
return vote.spec.hasEnd ? "已结束" : "进行中"
},
onOptionRadioChange(e) { onOptionRadioChange(e) {
this.submitForm.voteData = e.map(item => this.vote.spec.options[item.index]?.id); this.submitForm.voteData = e.map(item => this.vote.spec.options[item.index]?.id);
}, },
@@ -282,17 +366,14 @@
return jsonStr ? JSON.parse(jsonStr) : {} return jsonStr ? JSON.parse(jsonStr) : {}
}, },
handleSubmitTip() { handleSubmitTip() {
uni.showToast({ this.showToast("请选择选项后继续")
icon: "none",
title: "请选择选项后继续"
})
}, },
handleSubmit() { handleSubmit() {
if (!this.vote.spec.canAnonymously) { if (!this.vote.spec.canAnonymously) {
uni.showModal({ uni.showModal({
icon: "none", icon: "none",
title: "提示", title: "提示",
content: "该投票不允许匿名,请到博主的 网站端 进行投票!", content: "该投票不支持匿名,请到博主的 网站端 进行投票!",
cancelColor: "#666666", cancelColor: "#666666",
cancelText: "关闭", cancelText: "关闭",
confirmText: "复制地址", confirmText: "复制地址",
@@ -305,6 +386,7 @@
return return
} }
this.submitLoading = true
uni.showLoading({ uni.showLoading({
title: "正在保存..." title: "正在保存..."
}) })
@@ -312,11 +394,8 @@
this.$httpApi.v2 this.$httpApi.v2
.submitVote(this.name, this.submitForm, this.vote.spec.canAnonymously) .submitVote(this.name, this.submitForm, this.vote.spec.canAnonymously)
.then(res => { .then(res => {
uni.showToast({
icon: "none",
title: "提交成功"
})
this.showToast("提交成功")
voteCacheUtil.set(this.name, { voteCacheUtil.set(this.name, {
selected: [...this.submitForm.voteData], selected: [...this.submitForm.voteData],
data: this.vote data: this.vote
@@ -324,18 +403,21 @@
setTimeout(() => { setTimeout(() => {
uni.startPullDownRefresh() uni.startPullDownRefresh()
this.submitLoading = false
}, 1500); }, 1500);
}) })
.catch(err => { .catch(err => {
console.error(err); console.error(err);
uni.showToast({ this.showToast("提交失败,请重试")
icon: "none", this.submitLoading = false
title: "提交失败,请重试"
})
}) })
}, },
handleSelectSingleOption(option) { handleSelectSingleOption(option) {
if (this.vote.spec._state == '未开始') {
this.showToast(`投票未开始`)
return
}
if (this.vote.spec.disabled) return if (this.vote.spec.disabled) return
this.vote.spec.options.map(item => { this.vote.spec.options.map(item => {
if (option.id == item.id) { if (option.id == item.id) {
@@ -349,15 +431,16 @@
}, },
handleSelectCheckboxOption(option) { handleSelectCheckboxOption(option) {
if (this.vote.spec._state == '未开始') {
this.showToast(`投票未开始`)
return
}
if (this.vote.spec.disabled) return if (this.vote.spec.disabled) return
const checkedList = this.vote.spec.options.filter(x => x.checked && x.id != option.id) const checkedList = this.vote.spec.options.filter(x => x.checked && x.id != option.id)
if (this.vote.spec.type === 'multiple' && checkedList.length >= this.vote.spec.maxVotes) { if (this.vote.spec.type === 'multiple' && checkedList.length >= this.vote.spec.maxVotes) {
uni.showToast({ this.showToast(`最多选择 ${this.vote.spec.maxVotes}`)
icon: "none",
title: `最多选择 ${this.vote.spec.maxVotes}`
})
return return
} }
@@ -368,6 +451,13 @@
}) })
this.submitForm.voteData = this.vote.spec.options.filter(x => x.checked).map(item => item.id) this.submitForm.voteData = this.vote.spec.options.filter(x => x.checked).map(item => item.id)
},
showToast(content) {
uni.showToast({
icon: "none",
title: content,
mask: true
})
} }
} }
}; };
@@ -460,47 +550,66 @@
border-top: 2rpx solid #eee; border-top: 2rpx solid #eee;
} }
.is-voted-item {
min-height: 72rpx;
box-sizing: border-box;
position: relative;
border-radius: 12rpx;
background-color: rgba(229, 229, 229, 0.75);
font-size: 24rpx;
overflow: hidden;
&::before {
content: "";
width: var(--percent);
position: absolute;
left: 0;
top: 0;
bottom: 0;
background-color: rgba(208, 208, 208, 1);
z-index: 0;
border-radius: 6rpx;
}
&.selected {
background-color: rgba(3, 169, 244, 0.35);
color: #ffffff;
&::before {
background-color: rgba(3, 169, 244, 1);
}
}
}
.is-voted-item-content {
box-sizing: border-box;
min-height: 72rpx;
padding: 12rpx 24rpx;
position: relative;
z-index: 2;
}
.vote-select-option {
box-sizing: border-box;
padding: 20rpx 24rpx;
font-size: 24rpx;
border-radius: 12rpx;
transition: all 0.1s ease-in-out;
}
.single {
::v-deep { ::v-deep {
.tm-groupradio {
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
gap: 16rpx 0;
}
.tm-checkbox {
box-sizing: border-box;
// display: block;
// width: 100%;
}
.tm-button-label { .tm-button-label {
width: 100%; width: 100%;
} }
} }
.single {
::v-deep {}
} }
.multiple { .multiple {
::v-deep { ::v-deep {}
.tm-groupcheckbox {
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
gap: 16rpx 0;
}
.tm-checkbox {
box-sizing: border-box;
// display: block;
// width: 100%;
}
.tm-button-label {
width: 100%;
}
}
} }
.pk { .pk {
@@ -508,6 +617,7 @@
width: 100%; width: 100%;
::v-deep { ::v-deep {
.pk-container { .pk-container {
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
+18 -1
Voir le fichier
@@ -260,7 +260,7 @@
const tempItems = res.items.map(item => { const tempItems = res.items.map(item => {
item.spec.disabled = true item.spec.disabled = true
item.spec.isVoted = this.fnCalcIsVoted(item.metadata.name) item.spec.isVoted = this.fnCalcIsVoted(item.metadata.name)
item.spec._state = this.handleCalcVoteState(item)
item.spec.options.map((option, index) => { item.spec.options.map((option, index) => {
option.checked = this.fnCalcIsChecked(item.metadata.name, option) option.checked = this.fnCalcIsChecked(item.metadata.name, option)
@@ -326,6 +326,23 @@
const checked = data.selected.includes(option.id) const checked = data.selected.includes(option.id)
return checked return checked
}, },
handleCalcVoteState(vote) {
if (vote.spec.timeLimit !== 'custom') {
return vote.spec.hasEnd ? "已结束" : "进行中"
}
const nowTime = new Date().getTime()
const startTime = new Date(vote.spec.startDate).getTime()
const endTime = new Date(vote.spec.endDate).getTime()
if (nowTime < startTime) {
return "未开始"
}
if (nowTime < endTime) {
return "进行中"
}
return vote.spec.hasEnd ? "已结束" : "进行中"
},
//跳转详情 //跳转详情
fnToDetail(item) { fnToDetail(item) {
if (this.calcAuditModeEnabled) return; if (this.calcAuditModeEnabled) return;