mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-06-10 20:29:28 +08:00
feat: 文章详情支持投票功能
This commit is contained in:
@@ -173,6 +173,9 @@
|
||||
|
||||
/* 文本省略样式 结束 */
|
||||
|
||||
.box-border{
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// 定义尺寸变量(单位:rpx)
|
||||
$spacing-sizes: (
|
||||
|
||||
@@ -0,0 +1,490 @@
|
||||
<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">
|
||||
<tm-tags color="orange" style="min-width:40rpx;" :shadow="0" rounded size="s"
|
||||
model="fill">{{ index+1 }}</tm-tags>
|
||||
|
||||
<tm-tags v-if="vote.spec.type==='single'" color="light-blue" :shadow="0" rounded size="s"
|
||||
model="fill">单选</tm-tags>
|
||||
<tm-tags v-else-if="vote.spec.type==='multiple'" color="light-blue" :shadow="0" rounded size="s"
|
||||
model="fill">多选</tm-tags>
|
||||
<tm-tags v-else-if="vote.spec.type==='pk'" color="light-blue" :shadow="0" rounded size="s"
|
||||
model="fill">双选PK</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>
|
||||
</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">
|
||||
<tm-button v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex" :shadow="0"
|
||||
:theme="option.checked?'light-blue':'grey-lighten-3'" :plan="false" size="m"
|
||||
:height="72" :block="true" class="flex-1 w-full"
|
||||
@click="handleSelectSingleOption(option)">
|
||||
<view class="flex flex-between w-full">
|
||||
<text class="text-align-left text-overflow">
|
||||
{{option.title }}
|
||||
</text>
|
||||
<text v-if="vote.spec.isVoted" class="flex-shrink ml-12">
|
||||
{{option.percent }}%
|
||||
</text>
|
||||
</view>
|
||||
</tm-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 多选 -->
|
||||
<view v-else-if="vote.spec.type==='multiple'" class="multiple">
|
||||
<view class="w-full flex flex-col uh-gap-8">
|
||||
<tm-button v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex" :shadow="0"
|
||||
:theme="option.checked?'light-blue':'grey-lighten-3'" :plan="false" size="m"
|
||||
:height="72" :block="true" class="flex-1 full"
|
||||
@click="handleSelectCheckboxOption(option)">
|
||||
<view class="flex flex-between w-full">
|
||||
<text class="text-align-left text-overflow">
|
||||
{{option.title }}
|
||||
</text>
|
||||
<text v-if="vote.spec.isVoted" class="flex-shrink ml-12">
|
||||
{{option.percent }}%
|
||||
</text>
|
||||
</view>
|
||||
</tm-button>
|
||||
</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.percent + '%'}">
|
||||
<view class="option-item"
|
||||
:class="[optionIndex==0?'option-item-left':'option-item-right']">
|
||||
{{option.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">
|
||||
<tm-button v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
|
||||
:shadow="0" :theme="option.checked?'light-blue':'grey-lighten-3'" :plan="false"
|
||||
size="m" :height="72" :block="true" class="flex-1"
|
||||
@click="handleSelectSingleOption(option)">
|
||||
<view class="flex flex-between w-full">
|
||||
<text class="text-align-left text-overflow">
|
||||
选项{{ optionIndex+1}}:{{option.title }}
|
||||
</text>
|
||||
</view>
|
||||
</tm-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
<view class="vote-card-foot flex flex-between">
|
||||
<view class="left flex">
|
||||
<tm-tags color="grey-darken-2" rounded size="s"
|
||||
model="text">{{ {d: vote.spec.endDate, f: 'yyyy-MM-dd HH:mm'} | formatTime }} 结束</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="!vote.spec.canAnonymously" theme="red" :shadow="0" class="w-full" text :block="true"
|
||||
:height="72" @click="handleSubmit()">不允许匿名投票</tm-button>
|
||||
<tm-button v-else-if="fnCalcIsVoted()" theme="white" text :block="true" :height="72"
|
||||
class="w-full">您已参与投票</tm-button>
|
||||
<tm-button v-else theme="light-blue" class="w-full" :block="true" :height="72"
|
||||
@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 {
|
||||
voteCacheUtil
|
||||
} from '@/utils/vote.js'
|
||||
|
||||
const types = {
|
||||
"pk": "双选PK",
|
||||
"multiple": "多选",
|
||||
"single": "单选"
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "ArticleVote",
|
||||
components: {
|
||||
tmButton,
|
||||
tmTags
|
||||
},
|
||||
props: {
|
||||
voteId: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: "loading",
|
||||
loadingText: "加载中,请稍等...",
|
||||
detail: null,
|
||||
vote: null,
|
||||
submitForm: {
|
||||
voteData: []
|
||||
},
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fnGetData();
|
||||
},
|
||||
methods: {
|
||||
fnGetData() {
|
||||
this.loadingText = "加载中,请稍等...";
|
||||
this.loading = "loading";
|
||||
this.$httpApi.v2
|
||||
.getVoteDetail(this.voteId)
|
||||
.then(res => {
|
||||
this.pageTitle = "投票详情" + `(${types[res.vote.spec.type]})`
|
||||
|
||||
const tempVoteRes = res;
|
||||
|
||||
tempVoteRes.vote.spec.isVoted = this.fnCalcIsVoted()
|
||||
tempVoteRes.vote.spec.disabled = this.fnCalcIsVoted()
|
||||
|
||||
tempVoteRes.vote.spec.options.map((option, index) => {
|
||||
option.value = option.id
|
||||
option.label = option.title
|
||||
option.isVoted = this.fnCalcIsVoted()
|
||||
option.checked = this.fnCalcIsChecked(option)
|
||||
|
||||
if (tempVoteRes.vote.spec.type === 'single') {
|
||||
option.percent = this.fnCalcPercent(option, tempVoteRes.vote.stats);
|
||||
} else if (tempVoteRes.vote.spec.type === 'multiple') {
|
||||
option.percent = this.fnCalcPercent(option, tempVoteRes.vote.stats);
|
||||
} else if (tempVoteRes.vote.spec.type === 'pk') {
|
||||
option.percent = this.fnCalcPercent(option, tempVoteRes.vote.stats);
|
||||
}
|
||||
|
||||
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 = "投票内容加载失败,点击重试"
|
||||
})
|
||||
},
|
||||
fnCalcPercent(voteOption, stats) {
|
||||
if (!this.fnCalcIsVoted()) return 0;
|
||||
if (!stats?.voteDataList) return 0;
|
||||
const option = stats.voteDataList.find(x => x.id == voteOption.id)
|
||||
if (!option) return 0;
|
||||
const percent = (option.voteCount / stats.voteCount) * 100
|
||||
return Math.round(percent)
|
||||
},
|
||||
|
||||
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) : {}
|
||||
},
|
||||
handleSubmit() {
|
||||
if (!this.vote.spec.canAnonymously) {
|
||||
uni.showModal({
|
||||
icon: "none",
|
||||
title: "提示",
|
||||
content: "该投票不允许匿名,请到博主的 网站端 进行投票!",
|
||||
cancelColor: "#666666",
|
||||
cancelText: "关闭",
|
||||
confirmText: "复制地址",
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.$utils.copyText(this.$baseApiUrl, "复制成功")
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({
|
||||
title: "正在保存..."
|
||||
})
|
||||
|
||||
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: "提交失败,请重试"
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
handleSelectSingleOption(option) {
|
||||
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.disabled) 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
</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 {}
|
||||
}
|
||||
|
||||
|
||||
.single {
|
||||
::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 {
|
||||
::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 {
|
||||
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>
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
const _DEV_ = false
|
||||
const _DEV_TO_TYPE_ = "page"
|
||||
const _DEV_TO_PATH_ = "/pagesA/votes/votes"
|
||||
const _DEV_TO_PATH_ = ""
|
||||
|
||||
export default {
|
||||
mixins: [pluginAvailable],
|
||||
@@ -71,7 +71,7 @@
|
||||
methods: {
|
||||
fnCheckShowStarted() {
|
||||
// 本地开发,快速跳转页面,发布请设置 _DEV_ = false
|
||||
if (_DEV_) {
|
||||
if (_DEV_ && _DEV_TO_PATH_) {
|
||||
if (_DEV_TO_TYPE_ == 'tabbar') {
|
||||
uni.switchTab({
|
||||
url: _DEV_TO_PATH_
|
||||
|
||||
@@ -79,46 +79,53 @@
|
||||
<view class="markdown-wrap">
|
||||
<view v-if="checkPostRestrictRead(result)">
|
||||
<view v-if="showContentArr.length == 0">
|
||||
<restrict-read-skeleton
|
||||
:loading="true"
|
||||
:lines="3"
|
||||
:tip-text="`此处内容已隐藏,「${getRestrictReadTypeName(result)}可见」`"
|
||||
button-text="查看更多"
|
||||
button-color="#1890ff"
|
||||
skeleton-color="#f0f0f0"
|
||||
skeleton-highlight="#e0e0e0"
|
||||
animation-duration="2"
|
||||
@refresh="readMore"
|
||||
/>
|
||||
<restrict-read-skeleton :loading="true" :lines="3"
|
||||
:tip-text="`此处内容已隐藏,「${getRestrictReadTypeName(result)}可见」`" button-text="查看更多"
|
||||
button-color="#1890ff" skeleton-color="#f0f0f0" skeleton-highlight="#e0e0e0"
|
||||
animation-duration="2" @refresh="readMore" />
|
||||
</view>
|
||||
<view v-else v-for="showContent in showContentArr">
|
||||
<mp-html class="evan-markdown" lazy-load :domain="markdownConfig.domain"
|
||||
:loading-img="markdownConfig.loadingGif" :scroll-table="true" :selectable="true"
|
||||
:tag-style="markdownConfig.tagStyle" :container-style="markdownConfig.containStyle"
|
||||
:content="showContent" :markdown="true" :showLineNumber="true" :showLanguageName="true"
|
||||
:copyByLongPress="true"/>
|
||||
<restrict-read-skeleton
|
||||
:loading="true"
|
||||
:lines="3"
|
||||
:tip-text="`此处内容已隐藏,「${getRestrictReadTypeName(result)}可见」`"
|
||||
button-text="查看更多"
|
||||
button-color="#1890ff"
|
||||
skeleton-color="#f0f0f0"
|
||||
skeleton-highlight="#e0e0e0"
|
||||
animation-duration="2"
|
||||
@refresh="readMore"
|
||||
/>
|
||||
:copyByLongPress="true" />
|
||||
<restrict-read-skeleton :loading="true" :lines="3"
|
||||
:tip-text="`此处内容已隐藏,「${getRestrictReadTypeName(result)}可见」`" button-text="查看更多"
|
||||
button-color="#1890ff" skeleton-color="#f0f0f0" skeleton-highlight="#e0e0e0"
|
||||
animation-duration="2" @refresh="readMore" />
|
||||
</view>
|
||||
</view>
|
||||
<view v-else>
|
||||
<mp-html class="evan-markdown" lazy-load :domain="markdownConfig.domain"
|
||||
:loading-img="markdownConfig.loadingGif" :scroll-table="true" :selectable="true"
|
||||
:tag-style="markdownConfig.tagStyle" :container-style="markdownConfig.containStyle"
|
||||
:content="result.content.raw" :markdown="true" :showLineNumber="true" :showLanguageName="true"
|
||||
:copyByLongPress="true"/>
|
||||
:content="result.content.raw" :markdown="true" :showLineNumber="true"
|
||||
:showLanguageName="true" :copyByLongPress="true" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 投票 -->
|
||||
<view v-if="result._voteIds.length!==0" class="vote-wrap copyright-wrap bg-white mt-24 pa-24 round-4">
|
||||
<PluginUnavailable v-if="!uniHaloPluginAvailable" :pluginId="uniHaloPluginId"
|
||||
:error-text="uniHaloPluginAvailableError" />
|
||||
<template v-else>
|
||||
<view class="copyright-title text-weight-b flex items-center justify-between">
|
||||
<text>相关投票</text>
|
||||
<text class="vote-opra" @click="voteIsOpen=!voteIsOpen">
|
||||
{{ voteIsOpen?'收起':'展开' }}
|
||||
</text>
|
||||
</view>
|
||||
<view v-show="voteIsOpen" class="flex flex-col uh-gap-8 uh-mt-8">
|
||||
<ArticleVote v-for="(voteId,voteIdIndex) in result._voteIds" :key="voteId" :voteId="voteId" :index="voteIdIndex">
|
||||
</ArticleVote>
|
||||
</view>
|
||||
<view v-show="!voteIsOpen" class="vote-tip" @click="voteIsOpen=!voteIsOpen">
|
||||
投票已收起,点击展开 {{result._voteIds.length}} 个投票项
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<!-- 版权声明 -->
|
||||
<view v-if="postDetailConfig && postDetailConfig.copyrightEnabled"
|
||||
class="copyright-wrap bg-white mt-24 pa-24 round-4">
|
||||
@@ -248,24 +255,18 @@
|
||||
|
||||
<!-- 验证码弹窗 -->
|
||||
<tm-dialog v-model="verificationCodeModal.show" :disabled="true" title="验证提示"
|
||||
:confirmText="verificationCodeModal.confirmText"
|
||||
:showCancel="true" model="verificationCodeModal.model" theme="split" confirmColor="blue shadow-blue-0"
|
||||
@cancel="verificationCodeModal.show = false"
|
||||
:confirmText="verificationCodeModal.confirmText" :showCancel="true" model="verificationCodeModal.model"
|
||||
theme="split" confirmColor="blue shadow-blue-0" @cancel="verificationCodeModal.show = false"
|
||||
@confirm="restrictReadCheckOrViewVideo">
|
||||
<template #default>
|
||||
<view class="pa-20">
|
||||
<!-- 扫码验证模式 -->
|
||||
<view v-if="verificationCodeModal.type === 'scan'" class="flex flex-col flex-center">
|
||||
<text class="mb-20">请扫描下方二维码获取验证码</text>
|
||||
<tm-images
|
||||
:width="180"
|
||||
:height="180"
|
||||
:src="verificationCodeModal.imgUrl"
|
||||
preview
|
||||
round="5"
|
||||
></tm-images>
|
||||
<tm-input bg-color="grey-lighten-5" required v-model="restrictReadInputCode" placeholder="请输入验证码"
|
||||
:border-bottom="false" :flat="true"></tm-input>
|
||||
<tm-images :width="180" :height="180" :src="verificationCodeModal.imgUrl" preview
|
||||
round="5"></tm-images>
|
||||
<tm-input bg-color="grey-lighten-5" required v-model="restrictReadInputCode"
|
||||
placeholder="请输入验证码" :border-bottom="false" :flat="true"></tm-input>
|
||||
</view>
|
||||
|
||||
<!-- 观看视频模式 -->
|
||||
@@ -278,8 +279,7 @@
|
||||
<!-- 密码弹窗 -->
|
||||
<tm-dialog v-model="passwordModal.show" :disabled="true" title="验证提示" confirmText="确定" content="请输入密码"
|
||||
:showCancel="true" model="confirm" theme="split" confirmColor="blue shadow-blue-0"
|
||||
:input-val.sync="restrictReadInputCode"
|
||||
@cancel="passwordModal.show = false"
|
||||
:input-val.sync="restrictReadInputCode" @cancel="passwordModal.show = false"
|
||||
@confirm="restrictReadCheck"></tm-dialog>
|
||||
|
||||
<!-- 评论弹窗 -->
|
||||
@@ -291,38 +291,45 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MarkdownConfig from '@/common/markdown/markdown.config.js';
|
||||
import MarkdownConfig from '@/common/markdown/markdown.config.js';
|
||||
|
||||
import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
|
||||
import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
|
||||
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
|
||||
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
|
||||
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
|
||||
import tmDialog from '@/tm-vuetify/components/tm-dialog/tm-dialog.vue';
|
||||
import tmMore from '@/tm-vuetify/components/tm-more/tm-more.vue';
|
||||
import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
|
||||
import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
|
||||
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
|
||||
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
|
||||
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
|
||||
import tmDialog from '@/tm-vuetify/components/tm-dialog/tm-dialog.vue';
|
||||
import tmMore from '@/tm-vuetify/components/tm-more/tm-more.vue';
|
||||
|
||||
import mpHtml from '@/components/mp-html/components/mp-html/mp-html.vue';
|
||||
import commentList from '@/components/comment-list/comment-list.vue';
|
||||
import commentItem from '@/components/comment-item/comment-item.vue';
|
||||
import commentModal from '@/components/comment-modal/comment-modal.vue';
|
||||
import mpHtml from '@/components/mp-html/components/mp-html/mp-html.vue';
|
||||
import commentList from '@/components/comment-list/comment-list.vue';
|
||||
import commentItem from '@/components/comment-item/comment-item.vue';
|
||||
import commentModal from '@/components/comment-modal/comment-modal.vue';
|
||||
import ArticleVote from '@/components/article-vote/article-vote.vue';
|
||||
|
||||
import rCanvas from '@/components/r-canvas/r-canvas.vue';
|
||||
import barrage from '@/components/barrage/barrage.vue';
|
||||
import {getAppConfigs} from '@/config/index.js'
|
||||
import {upvote} from '@/utils/upvote.js'
|
||||
import {
|
||||
import rCanvas from '@/components/r-canvas/r-canvas.vue';
|
||||
import barrage from '@/components/barrage/barrage.vue';
|
||||
import {
|
||||
getAppConfigs
|
||||
} from '@/config/index.js'
|
||||
import {
|
||||
upvote
|
||||
} from '@/utils/upvote.js'
|
||||
import {
|
||||
checkPostRestrictRead,
|
||||
copyToClipboard,
|
||||
getRestrictReadTypeName,
|
||||
getShowableContent
|
||||
} from "@/utils/restrictRead";
|
||||
import HaloTokenConfig from "@/config/uhalo.config";
|
||||
import RestrictReadSkeleton from "@/components/restrict-read-skeleton/restrict-read-skeleton.vue";
|
||||
import TmImages from "@/tm-vuetify/components/tm-images/tm-images.vue";
|
||||
import TmInput from "@/tm-vuetify/components/tm-input/tm-input.vue";
|
||||
} from "@/utils/restrictRead";
|
||||
import HaloTokenConfig from "@/config/uhalo.config";
|
||||
import RestrictReadSkeleton from "@/components/restrict-read-skeleton/restrict-read-skeleton.vue";
|
||||
import TmImages from "@/tm-vuetify/components/tm-images/tm-images.vue";
|
||||
import TmInput from "@/tm-vuetify/components/tm-input/tm-input.vue";
|
||||
import pluginAvailable from "@/common/mixins/pluginAvailable.js"
|
||||
|
||||
let videoAd = null;
|
||||
export default {
|
||||
let videoAd = null;
|
||||
export default {
|
||||
mixins:[pluginAvailable],
|
||||
components: {
|
||||
TmInput,
|
||||
TmImages,
|
||||
@@ -339,7 +346,8 @@ export default {
|
||||
commentItem,
|
||||
rCanvas,
|
||||
barrage,
|
||||
commentModal
|
||||
commentModal,
|
||||
ArticleVote
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -389,7 +397,9 @@ export default {
|
||||
title: ""
|
||||
},
|
||||
|
||||
commentListScrollTop: 0
|
||||
commentListScrollTop: 0,
|
||||
|
||||
voteIsOpen: true
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -430,13 +440,18 @@ export default {
|
||||
haloConfigs: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler: function (newVal) {
|
||||
handler: function(newVal) {
|
||||
if (!newVal) return;
|
||||
this.fnHandleSetFlotButtonItems(newVal);
|
||||
}
|
||||
}
|
||||
},
|
||||
onLoad(e) {
|
||||
async onLoad(e) {
|
||||
// 检查插件
|
||||
this.setPluginId(this.NeedPluginIds.PluginVote)
|
||||
this.setPluginError("阿偶,检测到当前插件没有安装或者启用,无法使用投票功能,请联系管理员")
|
||||
await this.checkPluginAvailable()
|
||||
|
||||
this.fnSetPageTitle('文章加载中...');
|
||||
this.queryParams.name = e.name;
|
||||
this.fnGetData();
|
||||
@@ -472,7 +487,8 @@ export default {
|
||||
.getPostByName(this.queryParams.name)
|
||||
.then(res => {
|
||||
console.log('详情', res);
|
||||
this.result = res;
|
||||
const tempResult = res;
|
||||
tempResult._voteIds = this.extractVoteBlockIds(res.content?.raw || res.content?.content || "")
|
||||
|
||||
const openid = uni.getStorageSync('openid');
|
||||
if (openid === '' || openid === null) {
|
||||
@@ -480,11 +496,11 @@ export default {
|
||||
}
|
||||
|
||||
const toolsPluginEnabled = getAppConfigs().pluginConfig.toolsPlugin?.enabled;
|
||||
const restrictRead = checkPostRestrictRead(this.result);
|
||||
const restrictRead = checkPostRestrictRead(tempResult);
|
||||
|
||||
if (restrictRead && toolsPluginEnabled) {
|
||||
let verifyCodeType = getAppConfigs().pluginConfig.toolsPlugin?.verifyCodeType;
|
||||
const postVerifyCodeType = this.result?.metadata?.annotations?.verifyCodeType;
|
||||
const postVerifyCodeType = tempResult?.metadata?.annotations?.verifyCodeType;
|
||||
if (postVerifyCodeType) {
|
||||
verifyCodeType = postVerifyCodeType;
|
||||
}
|
||||
@@ -505,10 +521,10 @@ export default {
|
||||
// #endif
|
||||
}
|
||||
|
||||
const showableContentArr = getShowableContent(this.result);
|
||||
const showableContentArr = getShowableContent(tempResult);
|
||||
this.showContentArr = showableContentArr;
|
||||
}
|
||||
|
||||
this.result = tempResult;
|
||||
this.fnSetPageTitle('文章详情');
|
||||
this.loading = 'success';
|
||||
this.fnHandleSetFlotButtonItems(this.haloConfigs);
|
||||
@@ -970,7 +986,8 @@ export default {
|
||||
filePath: this.poster.url,
|
||||
success: () => {
|
||||
uni.$tm.toast('保存成功');
|
||||
}, fail: (e) => {
|
||||
},
|
||||
fail: (e) => {
|
||||
uni.$tm.toast('保存失败,请重试');
|
||||
}
|
||||
})
|
||||
@@ -1112,7 +1129,7 @@ export default {
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.login({
|
||||
provider: 'weixin',
|
||||
success: function (loginRes) {
|
||||
success: function(loginRes) {
|
||||
try {
|
||||
// todo 因为没有获取openid,所以先使用code代替
|
||||
uni.setStorageSync('openid', loginRes.code)
|
||||
@@ -1140,7 +1157,8 @@ export default {
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.$httpApi.v2.requestRestrictReadCheck(this.result?.metadata?.annotations?.restrictReadEnable, this.restrictReadInputCode, this.result?.metadata?.name)
|
||||
this.$httpApi.v2.requestRestrictReadCheck(this.result?.metadata?.annotations?.restrictReadEnable, this
|
||||
.restrictReadInputCode, this.result?.metadata?.name)
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
this.passwordModal.show = false;
|
||||
@@ -1162,8 +1180,7 @@ export default {
|
||||
videoAd = wx.createRewardedVideoAd({
|
||||
adUnitId: this.verificationCodeModal.adId
|
||||
})
|
||||
videoAd.onError(err => {
|
||||
})
|
||||
videoAd.onError(err => {})
|
||||
videoAd.onClose((status) => {
|
||||
if (status && status.isEnded || status === undefined) {
|
||||
//这里写广告播放完成后的事件
|
||||
@@ -1174,7 +1191,7 @@ export default {
|
||||
})
|
||||
}
|
||||
},
|
||||
openVideoAd: function () {
|
||||
openVideoAd: function() {
|
||||
if (videoAd && this.verificationCodeModal.adId !== '') {
|
||||
videoAd.show().catch(err => {
|
||||
// 失败重试
|
||||
@@ -1223,14 +1240,35 @@ export default {
|
||||
}).exec();
|
||||
}, 2 * 1000)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 从HTML字符串中提取所有vote-block的id
|
||||
* @param {string} html - 包含vote-block的HTML字符串
|
||||
* @returns {string[]} 提取到的id数组
|
||||
*/
|
||||
extractVoteBlockIds(html) {
|
||||
// 正则表达式匹配<vote-block id="xxx">格式
|
||||
// 捕获组提取id值
|
||||
const regex = /<vote-block\s+id="(vote-\w+)"\s*\/?>/g;
|
||||
const ids = [];
|
||||
let match;
|
||||
|
||||
// 循环匹配所有符合条件的id
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
// 第一个捕获组是id值
|
||||
ids.push(match[1]);
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-page {
|
||||
.app-page {
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
@@ -1238,9 +1276,9 @@ export default {
|
||||
box-sizing: border-box;
|
||||
background-color: #fafafd;
|
||||
padding-bottom: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.bg-image {
|
||||
.bg-image {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
@@ -1249,15 +1287,15 @@ export default {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-wrap {
|
||||
.loading-wrap {
|
||||
padding: 0 24rpx;
|
||||
height: inherit;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.head {
|
||||
.head {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -1284,8 +1322,7 @@ export default {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
|
||||
&-name {
|
||||
}
|
||||
&-name {}
|
||||
|
||||
&-time {
|
||||
margin-left: 36rpx;
|
||||
@@ -1352,9 +1389,9 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.category {
|
||||
.category {
|
||||
margin: 0 24rpx;
|
||||
padding: 24rpx;
|
||||
background-color: #ffffff;
|
||||
@@ -1379,30 +1416,49 @@ export default {
|
||||
background-color: #607d8b;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.category-tag + .category-tag {
|
||||
.category-tag+.category-tag {
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
.content {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.markdown-wrap,
|
||||
.evan-markdown,
|
||||
.ad-wrap,
|
||||
.copyright-wrap,
|
||||
.comment-wrap {
|
||||
.markdown-wrap,
|
||||
.evan-markdown,
|
||||
.ad-wrap,
|
||||
.copyright-wrap,
|
||||
.comment-wrap {
|
||||
box-shadow: 0rpx 0rpx 24rpx rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
}
|
||||
|
||||
.markdown-wrap {
|
||||
.vote-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.vote-opra {
|
||||
font-size: 24rpx;
|
||||
font-weight: normal;
|
||||
color:#999;
|
||||
}
|
||||
.vote-tip{
|
||||
margin-top:12rpx;
|
||||
padding:50rpx 24rpx;
|
||||
background:#F1F5F9;
|
||||
color:#666;
|
||||
font-size:24rpx;
|
||||
border-radius:12rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.markdown-wrap {
|
||||
overflow: hidden;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.copyright-title {
|
||||
.copyright-title {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding-left: 24rpx;
|
||||
@@ -1418,41 +1474,41 @@ export default {
|
||||
background-color: rgb(3, 174, 252);
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.poup-head {
|
||||
.poup-head {
|
||||
border-bottom: 2rpx solid #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
.poup-body {
|
||||
.poup-body {
|
||||
height: 50vh;
|
||||
}
|
||||
}
|
||||
|
||||
.poup-empty {
|
||||
.poup-empty {
|
||||
width: 100%;
|
||||
height: 40vh;
|
||||
}
|
||||
}
|
||||
|
||||
.poup-loading-wrap {
|
||||
.poup-loading-wrap {
|
||||
width: 100%;
|
||||
height: 40vh;
|
||||
|
||||
.e-loading-icon {
|
||||
font-size: 80rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.poster-content {
|
||||
.poster-content {
|
||||
width: 100%;
|
||||
min-height: 60vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.copyright-text {
|
||||
.copyright-text {
|
||||
line-height: 1.7;
|
||||
}
|
||||
}
|
||||
|
||||
.poster-loading {
|
||||
.poster-loading {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
@@ -1461,14 +1517,14 @@ export default {
|
||||
bottom: 0;
|
||||
// background-color: rgba(0, 0, 0, 0.1);
|
||||
z-index: 666;
|
||||
}
|
||||
}
|
||||
|
||||
.poster-save {
|
||||
.poster-save {
|
||||
box-sizing: border-box;
|
||||
border-top: 2rpx dashed #eee;
|
||||
}
|
||||
}
|
||||
|
||||
.original-url {
|
||||
.original-url {
|
||||
display: flex;
|
||||
|
||||
&_left {
|
||||
@@ -1496,5 +1552,5 @@ export default {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -193,7 +193,6 @@
|
||||
class="w-full">您已参与投票</tm-button>
|
||||
<tm-button v-else theme="light-blue" class="w-full" :block="true"
|
||||
@click="handleSubmit()">提交投票</tm-button>
|
||||
|
||||
</view>
|
||||
</block>
|
||||
</block>
|
||||
|
||||
Reference in New Issue
Block a user