| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569 |
- <template>
- <view class="vote-card" :class="[loading]">
- <view v-if="loading !=='success'" class="vote-error" @click="fnGetData()">
- {{loadingText}}
- </view>
- <template v-else>
- <view class="vote-card-head flex flex-col">
- <view class="flex justify-between">
- <view class="flex">
- <tm-tags color="orange" style="min-width:40rpx;" :shadow="0" rounded size="s"
- model="fill">{{ index + 1 }}</tm-tags>
- <tm-tags color="light-blue" :shadow="0" rounded size="s"
- model="fill">{{ vote.spec._uh_type }}</tm-tags>
- <tm-tags :color="vote.spec._uh_state.color" size="s" rounded :shadow="0"
- model="fill">{{vote.spec._uh_state.state}}</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 class="title">
- {{ vote.spec.title }}
- </view>
- </view>
- <view class="vote-card-body">
- <view v-if="vote.spec.remark" class="remark text-size-s">
- {{vote.spec.remark}}
- </view>
- <template>
- <!-- 单选 -->
- <view v-if="vote.spec.type==='single'" class="single">
- <view class="w-full flex flex-col uh-gap-8">
- <template v-if="vote.spec.isVoted || vote.spec.hasEnded">
- <view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
- class="is-voted-item" :class="[option.checked?'selected':'']" :style="{
- '--percent': option._uh_percent + '%'
- }">
- <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 }}
- </view>
- <view class="flex-shrink ">
- {{option._uh_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 v-else-if="vote.spec.type==='multiple'" class="multiple">
- <view class="w-full flex flex-col uh-gap-8">
- <template v-if="vote.spec.isVoted || vote.spec.hasEnded">
- <view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
- class="is-voted-item" :class="[option.checked?'selected':'']" :style="{
- '--percent': option._uh_percent + '%'
- }">
- <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 }}
- </view>
- <view class="flex-shrink ">
- {{option._uh_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>
- <!-- PK -->
- <view v-else-if="vote.spec.type==='pk'" class="pk">
- <view class="pk-container">
- <view class="radio-item" v-for="(option,optionIndex) in vote.spec.options"
- :key="optionIndex" :class="[optionIndex==0?'radio-left':'radio-right']"
- :style="{width:option._uh_percent + '%'}">
- <view class="option-item"
- :class="[optionIndex==0?'option-item-left':'option-item-right']">
- {{option._uh_percent }}%
- </view>
- </view>
- </view>
- <view class="option-foot w-full flex flex-between uh-mt-12">
- <view class="w-full flex flex-col uh-gap-8">
- <template v-if="vote.spec.isVoted || vote.spec.hasEnded">
- <view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
- class="is-voted-item" :class="[option.checked?'selected':'']" :style="{
- '--percent': option._uh_percent + '%'
- }">
- <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 }}
- </view>
- <view class="flex-shrink ">
- {{option._uh_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>
- </template>
- </view>
- <view class="vote-card-foot flex flex-between">
- <view class="left flex">
- <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 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="vote.spec.isVoted" color="blue" rounded size="s" model="text">已投票</tm-tags>
- </view>
- </view>
- <view v-if="submitForm.voteData.length!==0" class="box-border uh-mt-12 w-full uh-px-2">
- <tm-button v-if="fnCalcIsVoted()" theme="white" text :block="true" class="w-full">您已参与投票</tm-button>
- <tm-button v-else-if="vote.spec._uh_state.state==='未开始'" theme="orange" text class="w-full" :height="72"
- :block="true" @click="handleSubmitTip('投票未开始')">投票未开始</tm-button>
- <tm-button v-else-if="vote.spec._uh_state.state==='已结束'" theme="red" text class="w-full" :height="72"
- :block="true" @click="handleSubmitTip('投票已结束')">投票已结束</tm-button>
- <tm-button v-else-if="!vote.spec.canAnonymously" theme="red" :shadow="0" class="w-full" :height="72"
- text :block="true" @click="handleSubmit()">不支持匿名投票</tm-button>
- <tm-button v-else-if="submitForm.voteData.length===0" theme="white" text class="w-full" :height="72"
- :block="true" @click="handleSubmitTip('请选择选项')">提交投票(请选择选项)</tm-button>
- <tm-button v-else theme="light-blue" class="w-full" :height="72" :block="true" :loading="submitLoading"
- :disabled="submitLoading" @click="handleSubmit()">提交投票</tm-button>
- </view>
- </template>
- </view>
- </template>
- <script>
- import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
- import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
- import {
- VOTE_TYPES,
- calcVoteState,
- calcVotePercent,
- voteCacheUtil
- } from '@/utils/vote.js'
- export default {
- name: "ArticleVote",
- components: {
- tmButton,
- tmTags
- },
- props: {
- voteId: {
- type: String,
- default: ""
- },
- index: {
- type: Number,
- default: 0
- },
- article: {
- type: Object,
- default: () => ({})
- }
- },
- data() {
- return {
- loading: "loading",
- loadingText: "加载中,请稍等...",
- detail: null,
- vote: null,
- submitForm: {
- voteData: []
- },
- submitLoading: false
- }
- },
- created() {
- this.fnGetData();
- },
- methods: {
- fnGetData() {
- this.loadingText = "加载中,请稍等...";
- this.loading = "loading";
- this.$httpApi.v2
- .getVoteDetail(this.voteId)
- .then(res => {
- this.pageTitle = "投票详情" + `(${VOTE_TYPES[res.vote.spec.type]})`
- const tempVoteRes = res;
- tempVoteRes.vote.spec.isVoted = this.fnCalcIsVoted()
- tempVoteRes.vote.spec.disabled = this.fnCalcIsVoted()
- tempVoteRes.vote.spec._uh_state = calcVoteState(tempVoteRes.vote)
- tempVoteRes.vote.spec._uh_type = VOTE_TYPES[tempVoteRes.vote.spec.type]
- tempVoteRes.vote.spec.options.map((option, index) => {
- option.value = option.id
- option.label = option.title
- option.isVoted = this.fnCalcIsVoted()
- option.checked = this.fnCalcIsChecked(option)
- option._uh_percent = calcVotePercent(tempVoteRes.vote, option);
- option.dataStr = JSON.stringify(option)
- return option
- })
- this.vote = tempVoteRes.vote
- this.detail = tempVoteRes;
- setTimeout(() => {
- this.loading = 'success';
- }, 200);
- })
- .catch(err => {
- console.error(err);
- this.loading = 'error';
- this.loadingText = "投票内容加载失败,点击重试"
- })
- },
- fnCalcIsVoted() {
- return voteCacheUtil.has(this.voteId)
- },
- fnCalcIsChecked(option) {
- const data = voteCacheUtil.get(this.voteId)
- if (!data) return false;
- const checked = data.selected.includes(option.id)
- return checked
- },
- formatJsonStr(jsonStr) {
- return jsonStr ? JSON.parse(jsonStr) : {}
- },
- handleSubmitTip(text) {
- uni.showToast({
- icon: "none",
- title: text
- })
- },
- handleSubmit() {
- if (!this.vote.spec.canAnonymously) {
- uni.showModal({
- icon: "none",
- title: "提示",
- content: "该投票不支持匿名,请到博主的 网站端 进行投票!",
- cancelColor: "#666666",
- cancelText: "关闭",
- confirmText: "复制地址",
- success: (res) => {
- if (res.confirm) {
- console.log("this.article", this.article)
- const articleUrl = this.$baseApiUrl + (this.article?.status?.permalink ?? "")
- this.$utils.copyText(articleUrl, "原文地址复制成功")
- }
- }
- })
- return
- }
- uni.showLoading({
- title: "正在保存..."
- })
- this.submitLoading = true
- this.$httpApi.v2
- .submitVote(this.voteId, this.submitForm, this.vote.spec.canAnonymously)
- .then(res => {
- uni.showToast({
- icon: "none",
- title: "提交成功"
- })
- voteCacheUtil.set(this.voteId, {
- selected: [...this.submitForm.voteData],
- data: this.vote
- })
- this.fnGetData()
- })
- .catch(err => {
- console.error(err);
- uni.showToast({
- icon: "none",
- title: "提交失败,请重试"
- })
- }).finally(() => {
- this.submitLoading = false
- })
- },
- handleSelectSingleOption(option) {
- if (this.vote.spec._uh_state.state == '未开始') {
- this.showToast(`投票未开始`)
- return
- }
- if (this.vote.spec.hasEnded) return
- if (this.vote.spec.disabled) return
- this.vote.spec.options.map(item => {
- if (option.id == item.id) {
- item.checked = true
- } else {
- item.checked = false
- }
- })
- this.submitForm.voteData = this.vote.spec.options.filter(x => x.checked).map(item => item.id)
- },
- handleSelectCheckboxOption(option) {
- if (this.vote.spec._uh_state.state == '未开始') {
- this.showToast(`投票未开始`)
- return
- }
- if (this.vote.spec.hasEnded) return
- if (this.vote.spec.disabled) return
- 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) {
- this.showToast(`最多选择 ${this.vote.spec.maxVotes} 项`)
- return
- }
- this.vote.spec.options.map(item => {
- if (option.id == item.id) {
- item.checked = !item.checked
- }
- })
- 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
- })
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- .w-full {
- width: 100%;
- }
- .wp-50 {
- width: 50%;
- }
- .vote-card {
- display: flex;
- flex-direction: column;
- box-sizing: border-box;
- padding: 24rpx;
- border-radius: 12rpx;
- background-color: #ffff;
- overflow: hidden;
- margin-bottom: 12rpx;
- border: 1px solid #eee;
- &.error {
- padding: 0;
- border-style: dashed;
- border-color: #e88080;
- color: #e88080;
- background-color: rgba(232, 128, 128, 0.075);
- }
- &.loading {
- padding: 0;
- border-style: dashed;
- border-color: rgba(3, 174, 252, 1);
- color: rgba(3, 174, 252, 1);
- background-color: rgba(3, 174, 252, 0.075);
- }
- }
- .vote-error {
- padding: 50rpx 24rpx;
- font-size: 24rpx;
- border-radius: 12rpx;
- text-align: center;
- }
- .vote-card-head {
- margin-bottom: 12rpx;
- .title {
- padding: 12rpx 0;
- font-size: 28rpx;
- font-weight: bold;
- }
- }
- .vote-card-body {
- .remark {
- box-sizing: border-box;
- color: rgba(0, 0, 0, 0.75);
- margin-bottom: 24rpx;
- }
- }
- .vote-card-foot {
- box-sizing: border-box;
- padding-top: 6px;
- margin-top: 12px;
- border-top: 2rpx solid #eee;
- .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 {
- ::v-deep {}
- }
- .multiple {
- ::v-deep {}
- }
- .pk {
- box-sizing: border-box;
- width: 100%;
- padding: 0 12rpx;
- ::v-deep {
- .pk-container {
- box-sizing: border-box;
- width: 100%;
- display: flex;
- }
- .radio-item {
- flex-grow: 1;
- min-width: 30% !important;
- max-width: 70% !important;
- }
- .radio-left {}
- .radio-right {}
- .option-item {
- box-sizing: border-box;
- width: 100%;
- padding: 24rpx;
- border-radius: 12rpx;
- }
- .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;
- }
- .option-foot {
- width: 100%;
- margin-top: 6rpx;
- font-size: 24rpx;
- color: #666;
- .left {
- box-sizing: border-box;
- padding-right: 24rpx;
- }
- .right {
- box-sizing: border-box;
- padding-left: 24rpx;
- }
- }
- }
- }
- </style>
|