Changes
This commit is contained in:
@@ -2,30 +2,71 @@
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="问卷列表" placeholder fixed></van-nav-bar>
|
||||
<div id="app" v-cloak class="qsv-h5-page">
|
||||
<div class="qsv-h5-header">
|
||||
<div class="qsv-h5-nav">
|
||||
<button class="qsv-h5-back-btn" @click="historyBack">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</button>
|
||||
<div class="qsv-h5-nav-title">问卷服务</div>
|
||||
<div class="qsv-h5-nav-placeholder"></div>
|
||||
</div>
|
||||
<div class="qsv-h5-search-row">
|
||||
<van-search
|
||||
v-model="pageForm.title"
|
||||
shape="round"
|
||||
background="transparent"
|
||||
placeholder="搜索标题名称"
|
||||
clearable
|
||||
@search="onRefresh"
|
||||
@clear="onRefresh">
|
||||
</van-search>
|
||||
<button class="qsv-h5-search-btn" @click="onRefresh">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="qsv-h5-filter-row">
|
||||
<button
|
||||
v-for="item in categoryOptions"
|
||||
:key="item.value"
|
||||
:class="['qsv-h5-filter-btn', pageForm.category === item.value ? 'active' : '']"
|
||||
@click="changeCategory(item.value)">
|
||||
<i :class="pageForm.category === item.value ? 'fa fa-check-circle' : 'fa fa-circle-o'"></i>
|
||||
<span>{{item.label}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<van-sticky offset-top="46px">
|
||||
<van-tabs v-model="pageForm.category" @change="doSearch">
|
||||
<van-tab v-for="item in dict.type.ACTIVITY_QSV_CATEGORY" :name="item.code" :title="item.label"
|
||||
:key="item.code"></van-tab>
|
||||
</van-tabs>
|
||||
</van-sticky>
|
||||
|
||||
<table-list api="/platform/h5/qsv/pageData" :page_form.sync="pageForm" ref="tableListRef" title="title"
|
||||
@ready="doSearch">
|
||||
<!-- <template #header="{index,row}"></template>-->
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="开始时间">{{row.startTime}}</table-column>
|
||||
<table-column label="结束时间">{{row.endTime}}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onEnter(row)">
|
||||
<i class="fa fa-eye"></i>
|
||||
<span>查看</span>
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh" class="qsv-h5-list-wrap">
|
||||
<van-list v-model="loading" :finished="finished" finished-text="暂无更多内容" @load="onLoad">
|
||||
<div
|
||||
v-for="row in list"
|
||||
:key="row.id"
|
||||
class="qsv-h5-card"
|
||||
@click="onEnter(row)">
|
||||
<div class="qsv-h5-cover">
|
||||
<img :src="row.cover || defaultCover" alt="">
|
||||
</div>
|
||||
<div class="qsv-h5-card-body">
|
||||
<div class="qsv-h5-title-row">
|
||||
<div class="qsv-h5-title">{{row.title}}</div>
|
||||
<div class="qsv-h5-answered" v-if="row.answeredText || row.isAnswered">{{row.answeredText || getAnsweredText(row.category)}}</div>
|
||||
</div>
|
||||
<div class="qsv-h5-meta-row">
|
||||
<div class="qsv-h5-type">
|
||||
<i :class="getCategoryIcon(row.category)"></i>
|
||||
<span>{{getCategoryText(row.category)}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="qsv-h5-time">
|
||||
<div>开始时间:{{formatDateTime(row.startTime)}}</div>
|
||||
<div>结束时间:{{formatDateTime(row.endTime)}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</table-list>
|
||||
<van-empty v-if="!loading && list.length === 0" description="暂无内容"></van-empty>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
@@ -35,12 +76,26 @@ layout("/layouts/platform_h5.html"){
|
||||
store,
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
requesting: false,
|
||||
finished: false,
|
||||
refreshing: false,
|
||||
cacheRestored: false,
|
||||
list: [],
|
||||
defaultCover: "/assets/mobile/svg/qsv/icon.svg",
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
category: "QUIZ"
|
||||
title: "",
|
||||
category: ""
|
||||
},
|
||||
categoryOptions: [
|
||||
{label: "全部", value: ""},
|
||||
{label: "答题", value: "QUIZ"},
|
||||
{label: "问卷", value: "SURVEY"},
|
||||
{label: "投票", value: "VOTE"}
|
||||
],
|
||||
categoryPaths: {
|
||||
QUIZ: "/platform/h5/qsv/quiz",
|
||||
SURVEY: "/platform/h5/qsv/survey",
|
||||
@@ -50,30 +105,114 @@ layout("/layouts/platform_h5.html"){
|
||||
},
|
||||
methods: {
|
||||
historyBack() {
|
||||
this.$pjaxReplace('/platform/h5/home')
|
||||
this.$pjaxReplace('/platform/h5/home')
|
||||
},
|
||||
|
||||
doSearch() {
|
||||
this.$nextTick(() => {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageForm.totalCount = 0
|
||||
this.$refs.tableListRef.doSearch()
|
||||
changeCategory(category) {
|
||||
if (this.pageForm.category === category) {
|
||||
return
|
||||
}
|
||||
this.pageForm.category = category
|
||||
this.onRefresh()
|
||||
},
|
||||
|
||||
onRefresh() {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageForm.totalCount = 0
|
||||
this.list = []
|
||||
this.finished = false
|
||||
this.clearListCache()
|
||||
this.onLoad()
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
if (this.requesting || this.finished) {
|
||||
return
|
||||
}
|
||||
this.requesting = true
|
||||
this.loading = true
|
||||
this.$axios.post("/platform/h5/qsv/pageData", this.pageForm).then((res) => {
|
||||
if (res.code === 0) {
|
||||
const pageData = res.data || {}
|
||||
const rows = pageData.list || []
|
||||
this.list = this.list.concat(rows)
|
||||
this.pageForm.totalCount = pageData.totalCount || 0
|
||||
if (this.list.length >= this.pageForm.totalCount || rows.length === 0) {
|
||||
this.finished = true
|
||||
}
|
||||
this.pageForm.pageNumber++
|
||||
this.saveListCache()
|
||||
}
|
||||
}).finally(() => {
|
||||
this.requesting = false
|
||||
this.loading = false
|
||||
this.refreshing = false
|
||||
})
|
||||
},
|
||||
|
||||
async onEnter(item) {
|
||||
if (item.groupId) {
|
||||
const {
|
||||
code,
|
||||
data
|
||||
} = await $.get('/platform/activity/basic/scope/getScopeUser', {activityGroupId: item.groupId})
|
||||
if (code === 0 && data === 0) {
|
||||
this.$toast.fail("您没有权限参与")
|
||||
return
|
||||
}
|
||||
refreshListSilently() {
|
||||
if (this.requesting) {
|
||||
return
|
||||
}
|
||||
const requestForm = Object.assign({}, this.pageForm, {
|
||||
pageNumber: 1
|
||||
})
|
||||
this.requesting = true
|
||||
this.$axios.post("/platform/h5/qsv/pageData", requestForm).then((res) => {
|
||||
if (res.code === 0) {
|
||||
const pageData = res.data || {}
|
||||
const rows = pageData.list || []
|
||||
this.list = rows
|
||||
this.pageForm = Object.assign({}, this.pageForm, {
|
||||
pageNumber: 2,
|
||||
totalCount: pageData.totalCount || 0
|
||||
})
|
||||
this.finished = rows.length >= this.pageForm.totalCount || rows.length === 0
|
||||
this.saveListCache()
|
||||
}
|
||||
}).finally(() => {
|
||||
this.requesting = false
|
||||
this.loading = false
|
||||
this.refreshing = false
|
||||
})
|
||||
},
|
||||
|
||||
const {startTime, endTime} = item
|
||||
getCategoryText(category) {
|
||||
const map = {
|
||||
QUIZ: "答题",
|
||||
SURVEY: "问卷",
|
||||
VOTE: "投票"
|
||||
}
|
||||
return map[category] || "活动"
|
||||
},
|
||||
|
||||
getCategoryIcon(category) {
|
||||
const map = {
|
||||
QUIZ: "fa fa-check-square-o",
|
||||
SURVEY: "fa fa-list-alt",
|
||||
VOTE: "fa fa-bar-chart"
|
||||
}
|
||||
return map[category] || "fa fa-tags"
|
||||
},
|
||||
|
||||
getAnsweredText(category) {
|
||||
const map = {
|
||||
QUIZ: "已答题",
|
||||
SURVEY: "已填写",
|
||||
VOTE: "已投票"
|
||||
}
|
||||
return map[category] || "已完成"
|
||||
},
|
||||
|
||||
formatDateTime(value) {
|
||||
if (!value) {
|
||||
return "-"
|
||||
}
|
||||
return this.$moment(value).format("YYYY-MM-DD HH:mm")
|
||||
},
|
||||
|
||||
onEnter(item) {
|
||||
const {startTime} = item
|
||||
if (this.$moment(startTime).unix() > this.$moment().unix()) {
|
||||
this.$toast("未开始")
|
||||
return
|
||||
@@ -81,13 +220,315 @@ layout("/layouts/platform_h5.html"){
|
||||
|
||||
const path = this.categoryPaths[item.category]
|
||||
if (path) {
|
||||
this.saveListCache()
|
||||
pjaxReplace(path + "?id=" + item.id)
|
||||
}
|
||||
},
|
||||
|
||||
getCacheKey() {
|
||||
return "qsv:h5:list"
|
||||
},
|
||||
|
||||
saveListCache() {
|
||||
try {
|
||||
sessionStorage.setItem(this.getCacheKey(), JSON.stringify({
|
||||
time: Date.now(),
|
||||
list: this.list,
|
||||
pageForm: this.pageForm,
|
||||
finished: this.finished
|
||||
}))
|
||||
} catch (e) {
|
||||
}
|
||||
},
|
||||
|
||||
restoreListCache() {
|
||||
try {
|
||||
const cacheText = sessionStorage.getItem(this.getCacheKey())
|
||||
if (!cacheText) {
|
||||
return
|
||||
}
|
||||
const cache = JSON.parse(cacheText)
|
||||
if (!cache || Date.now() - cache.time > 5 * 60 * 1000) {
|
||||
this.clearListCache()
|
||||
return
|
||||
}
|
||||
this.list = cache.list || []
|
||||
this.pageForm = Object.assign({}, this.pageForm, cache.pageForm || {})
|
||||
this.finished = !!cache.finished
|
||||
this.cacheRestored = this.list.length > 0
|
||||
} catch (e) {
|
||||
this.clearListCache()
|
||||
}
|
||||
},
|
||||
|
||||
clearListCache() {
|
||||
try {
|
||||
sessionStorage.removeItem(this.getCacheKey())
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.restoreListCache()
|
||||
},
|
||||
mounted() {
|
||||
if (this.cacheRestored) {
|
||||
setTimeout(() => {
|
||||
this.refreshListSilently()
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.qsv-h5-page {
|
||||
min-height: 100vh;
|
||||
background: #f4f8ff;
|
||||
padding-bottom: 18px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.qsv-h5-header {
|
||||
padding: 10px 14px 16px;
|
||||
background: linear-gradient(180deg, #1688f8 0%, #0f83ee 100%);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.qsv-h5-nav {
|
||||
height: 38px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.qsv-h5-back-btn,
|
||||
.qsv-h5-nav-placeholder {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.qsv-h5-back-btn {
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, .16);
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.qsv-h5-nav-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.qsv-h5-search-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.qsv-h5-header .van-search {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.qsv-h5-header .van-search__content {
|
||||
background: rgba(255, 255, 255, .92);
|
||||
}
|
||||
|
||||
.qsv-h5-search-btn {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border: 1px solid rgba(255, 255, 255, .45);
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, .18);
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
font-size: 18px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.qsv-h5-filter-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 12px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
.qsv-h5-filter-row::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.qsv-h5-filter-btn {
|
||||
height: 32px;
|
||||
width: 100%;
|
||||
padding: 0 6px;
|
||||
border: 0;
|
||||
border-radius: 17px;
|
||||
background: rgba(255, 255, 255, .92);
|
||||
color: #1688f8;
|
||||
font-size: 14px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.qsv-h5-filter-btn.active {
|
||||
background: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.qsv-h5-filter-btn i {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.qsv-h5-list-wrap {
|
||||
min-height: calc(100vh - 170px);
|
||||
padding: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.qsv-h5-card {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
padding: 14px;
|
||||
margin-bottom: 14px;
|
||||
background: #ffffff;
|
||||
border-radius: 13px;
|
||||
box-shadow: 0 8px 24px rgba(35, 102, 168, .1);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.qsv-h5-cover {
|
||||
width: 116px;
|
||||
height: 87px;
|
||||
aspect-ratio: 4 / 3;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: #eaf2ff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.qsv-h5-cover img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.qsv-h5-card-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.qsv-h5-title-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.qsv-h5-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
color: #1f2f46;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.qsv-h5-answered {
|
||||
height: 22px;
|
||||
min-width: 50px;
|
||||
padding: 0 8px;
|
||||
border: 1px solid #19a05f;
|
||||
border-radius: 11px;
|
||||
background: #f0fff8;
|
||||
color: #149356;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.qsv-h5-meta-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
.qsv-h5-type {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
height: 20px;
|
||||
padding: 0 6px;
|
||||
border: 1px solid #ff9f43;
|
||||
border-radius: 4px;
|
||||
background: #fff7ec;
|
||||
color: #f28b18;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 18px;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.qsv-h5-type i {
|
||||
color: #f28b18;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.qsv-h5-time {
|
||||
color: #71829b;
|
||||
font-size: 12px;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
.qsv-h5-card {
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.qsv-h5-cover {
|
||||
width: 96px;
|
||||
height: 72px;
|
||||
}
|
||||
|
||||
.qsv-h5-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!--#
|
||||
}
|
||||
|
||||
@@ -3,17 +3,19 @@ layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="问卷列表" placeholder fixed></van-nav-bar>
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" :title="pageTitle" placeholder fixed></van-nav-bar>
|
||||
|
||||
<div class="container" :style="{'padding-bottom': answerRecord.isFinish ? 0 : '84px'}">
|
||||
<van-sticky offset-top="46px">
|
||||
<!--开启计时 未完成 活动未结束-->
|
||||
<div class="timer" v-if="activity.timeLimit > 0 && !answerRecord.isFinish && !isEnd">⏰{{ remainingTime }}s
|
||||
<div class="quiz-hero" :style="heroStyle">
|
||||
<div class="quiz-hero-mask">
|
||||
<div class="timer" v-if="activity.timeLimit > 0 && !answerRecord.isFinish && !isEnd">
|
||||
⏰ {{ remainingTime }}s
|
||||
</div>
|
||||
<h2 class="quiz-title">{{ activity.title }}</h2>
|
||||
</div>
|
||||
<h2 class="title">{{ activity.title }}</h2>
|
||||
</van-sticky>
|
||||
</div>
|
||||
|
||||
<div v-if="!answerRecord.isFinish">
|
||||
<div class="quiz-card-scroll" v-if="!answerRecord.isFinish">
|
||||
<div v-for="(subject, index) in subjects" :key="index" class="subject">
|
||||
<p>{{index+1}}、{{ subject.title }}</p>
|
||||
<div class="tag">
|
||||
@@ -84,9 +86,28 @@ layout("/layouts/platform_h5.html"){
|
||||
return this.activity.timeLimit * 60 - this.answerTime
|
||||
}
|
||||
return 0
|
||||
},
|
||||
pageTitle() {
|
||||
return this.getCategoryPageTitle(this.activity && this.activity.category, "在线答题")
|
||||
},
|
||||
heroStyle() {
|
||||
if (this.activity && this.activity.cover) {
|
||||
return {
|
||||
backgroundImage: "url(" + this.activity.cover + ")"
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCategoryPageTitle(category, defaultTitle) {
|
||||
const titleMap = {
|
||||
QUIZ: "在线答题",
|
||||
SURVEY: "问卷调查",
|
||||
VOTE: "投票"
|
||||
}
|
||||
return titleMap[category] || defaultTitle
|
||||
},
|
||||
//获取活动、题目
|
||||
listSubjects() {
|
||||
this.$axios.post("/platform/h5/qsv/quiz/subjects", {activityId: this.id}).then((res) => {
|
||||
@@ -245,14 +266,6 @@ layout("/layouts/platform_h5.html"){
|
||||
return
|
||||
}
|
||||
|
||||
const selectedOptionIds = subject.userSelectOptionIds || []
|
||||
const hasSelected = selectedOptionIds.includes(optionId)
|
||||
// 多选题达到最大可选数量后禁止继续勾选,但允许取消已选项。
|
||||
if (subject.type === "checkbox" && !hasSelected && subject.maxMulti && subject.maxMulti > 0 && selectedOptionIds.length >= subject.maxMulti) {
|
||||
this.$toast("本题最多选择" + subject.maxMulti + "项")
|
||||
return
|
||||
}
|
||||
|
||||
if (subject.type === "radio") {
|
||||
this.$refs["subject" + subject.id][0].toggleAll(false)
|
||||
}
|
||||
@@ -341,20 +354,53 @@ layout("/layouts/platform_h5.html"){
|
||||
style: /*language=CSS*/ `
|
||||
.container {
|
||||
padding: 0;
|
||||
background: #f4f8ff;
|
||||
height: calc(100vh - 46px);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/deep/ .container .title {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
background: #ffffff;
|
||||
margin: 0 0 10px 0 !important;
|
||||
color: var(--color-primary);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
/deep/ .quiz-hero {
|
||||
flex: 0 0 auto;
|
||||
min-height: 170px;
|
||||
background: linear-gradient(135deg, #1688f8, #0a55c5);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin: 12px 12px 14px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 24px rgba(35, 102, 168, .14);
|
||||
}
|
||||
|
||||
.container .card {
|
||||
padding: 10px;
|
||||
background: #ffffff;
|
||||
/deep/ .quiz-hero-mask {
|
||||
min-height: 170px;
|
||||
padding: 24px 20px 22px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, .16), rgba(0, 0, 0, .56));
|
||||
}
|
||||
|
||||
/deep/ .quiz-title {
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
text-shadow: 0 2px 8px rgba(0, 0, 0, .28);
|
||||
}
|
||||
|
||||
.quiz-card-scroll {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding: 0 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.subject {
|
||||
@@ -377,10 +423,15 @@ layout("/layouts/platform_h5.html"){
|
||||
}
|
||||
|
||||
/deep/ .timer {
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
background: #fff;
|
||||
padding: 10px 0;
|
||||
align-self: flex-start;
|
||||
margin-bottom: 12px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, .22);
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
|
||||
@@ -6,15 +6,15 @@ layout("/layouts/platform_h5.html"){
|
||||
<van-nav-bar @click-left="historyBack" left-arrow left-text="返回" title="问卷列表" placeholder fixed></van-nav-bar>
|
||||
|
||||
<div class="container" :style="{'padding-bottom': answerRecord.isFinish ? 0 : '84px'}">
|
||||
<van-sticky offset-top="46px">
|
||||
<h2 class="title">{{ activity.title }}</h2>
|
||||
<div class="score-form">
|
||||
<div class="score-form-total">
|
||||
<div class="score-font-style">{{totalScore}}</div>
|
||||
<i class="score-underline"></i>
|
||||
<div class="quiz-hero" :style="heroStyle">
|
||||
<div class="quiz-hero-mask">
|
||||
<h2 class="quiz-title">{{ activity.title }}</h2>
|
||||
<div class="quiz-score">
|
||||
<span>{{totalScore}}</span>
|
||||
<em>分</em>
|
||||
</div>
|
||||
</div>
|
||||
</van-sticky>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div v-for="(subject, index) in subjects" :key="index" class="subject">
|
||||
@@ -111,6 +111,14 @@ layout("/layouts/platform_h5.html"){
|
||||
}
|
||||
}
|
||||
return totalScore
|
||||
},
|
||||
heroStyle() {
|
||||
if (this.activity && this.activity.cover) {
|
||||
return {
|
||||
backgroundImage: "url(" + this.activity.cover + ")"
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
@@ -199,23 +207,60 @@ layout("/layouts/platform_h5.html"){
|
||||
style: /*language=CSS*/ `
|
||||
.container {
|
||||
padding: 0;
|
||||
background: #f4f8ff;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/deep/ .container .title {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
margin: 0 !important;
|
||||
background: #ffffff;
|
||||
color: var(--color-primary);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
/deep/ .quiz-hero {
|
||||
min-height: 190px;
|
||||
background: linear-gradient(135deg, #1688f8, #0a55c5);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin: 12px 12px 14px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 24px rgba(35, 102, 168, .14);
|
||||
}
|
||||
|
||||
.container .score {
|
||||
/deep/ .quiz-hero-mask {
|
||||
min-height: 190px;
|
||||
padding: 28px 20px 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, .18), rgba(0, 0, 0, .58));
|
||||
}
|
||||
|
||||
.container .card {
|
||||
padding: 10px;
|
||||
background: #ffffff;
|
||||
/deep/ .quiz-title {
|
||||
margin: 0 0 14px;
|
||||
color: #fff;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
text-shadow: 0 2px 8px rgba(0, 0, 0, .28);
|
||||
}
|
||||
|
||||
/deep/ .quiz-score {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
color: #fff;
|
||||
text-shadow: 0 2px 10px rgba(0, 0, 0, .3);
|
||||
}
|
||||
|
||||
/deep/ .quiz-score span {
|
||||
font-size: 48px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/deep/ .quiz-score em {
|
||||
margin-left: 6px;
|
||||
margin-bottom: 5px;
|
||||
font-style: normal;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.subject {
|
||||
@@ -237,46 +282,6 @@ layout("/layouts/platform_h5.html"){
|
||||
top: 0;
|
||||
}
|
||||
|
||||
/deep/ .score-form {
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
border-top: 1px solid #f1f1f1;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/deep/ .score-form-total {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
padding: 10px 20px 10px 0;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
/deep/ .score-text-news {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/deep/ .score-font-style {
|
||||
font-size: 32px;
|
||||
color: #ff6a00;
|
||||
word-break: keep-all;
|
||||
line-height: 38px;
|
||||
min-width: 47px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/deep/ .score-underline {
|
||||
background: url(//image.wjx.cn/images/newimg/score-form/score-underline@2x.png) no-repeat center;
|
||||
background-size: 47px 16px;
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
width: 47px;
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.van-checkbox__icon--checked .van-icon {
|
||||
color: #fff !important;
|
||||
background-color: var(--color-primary) !important;
|
||||
|
||||
@@ -5,18 +5,53 @@ layout("/layouts/platform_h5.html"){
|
||||
<style>
|
||||
.container {
|
||||
padding: 0;
|
||||
background: #f4f8ff;
|
||||
height: calc(100vh - 46px);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.container .title {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
background: #ffffff;
|
||||
margin: 0 0 10px 0;
|
||||
.quiz-hero {
|
||||
flex: 0 0 auto;
|
||||
min-height: 170px;
|
||||
background: linear-gradient(135deg, #1688f8, #0a55c5);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin: 12px 12px 14px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 24px rgba(35, 102, 168, .14);
|
||||
}
|
||||
|
||||
.container .card {
|
||||
padding: 10px;
|
||||
background: #ffffff;
|
||||
.quiz-hero-mask {
|
||||
min-height: 170px;
|
||||
padding: 24px 20px 22px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, .16), rgba(0, 0, 0, .56));
|
||||
}
|
||||
|
||||
.quiz-title {
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
text-shadow: 0 2px 8px rgba(0, 0, 0, .28);
|
||||
}
|
||||
|
||||
.quiz-card-scroll {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding: 0 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.subject {
|
||||
@@ -49,16 +84,30 @@ layout("/layouts/platform_h5.html"){
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.van-checkbox__icon--checked .van-icon {
|
||||
.van-checkbox__icon--checked .van-icon,
|
||||
.van-radio__icon--checked .van-icon {
|
||||
color: #fff !important;
|
||||
background-color: var(--color-primary) !important;
|
||||
border-color: var(--color-primary) !important;
|
||||
}
|
||||
|
||||
.van-checkbox__icon--disabled .van-icon {
|
||||
.van-checkbox__icon--disabled .van-icon,
|
||||
.van-radio__icon--disabled .van-icon {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.survey-option-finished-selected .survey-option-text {
|
||||
color: #00a000;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.survey-option-finished-selected .van-radio__icon--disabled .van-icon,
|
||||
.survey-option-finished-selected .van-checkbox__icon--disabled .van-icon {
|
||||
color: #fff !important;
|
||||
background-color: #d8d8d8 !important;
|
||||
border-color: #d8d8d8 !important;
|
||||
}
|
||||
|
||||
.ui-input-box {
|
||||
border: 1px solid #e3e3e3;
|
||||
margin: 5px 0;
|
||||
@@ -80,57 +129,145 @@ layout("/layouts/platform_h5.html"){
|
||||
resize: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.option-fill-box {
|
||||
border: 1px solid #e3e3e3;
|
||||
margin: 0 16px 8px 46px;
|
||||
background-color: #fff;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.option-fill-box input {
|
||||
background-color: #fff;
|
||||
border: none !important;
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
-webkit-appearance: none;
|
||||
resize: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.required-star {
|
||||
color: #ee0a24;
|
||||
margin-left: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<van-nav-bar title="问卷调查" left-arrow @click-left="historyBack" placeholder fixed></van-nav-bar>
|
||||
<div class="container" :style="{'padding-bottom': answerRecord.isFinish ? 0 : '84px'}">
|
||||
<h2 class="title">{{ activity.title }}</h2>
|
||||
<div v-if="!isFinished">
|
||||
<div v-for="(subject, index) in subjects" :key="index" class="subject">
|
||||
<p>{{index+1}}、{{ subject.title }}</p>
|
||||
<van-nav-bar :title="pageTitle" left-arrow left-text="返回" @click-left="historyBack" placeholder fixed></van-nav-bar>
|
||||
<div class="container" :style="{'padding-bottom': '84px'}">
|
||||
<div class="quiz-hero" :style="heroStyle">
|
||||
<div class="quiz-hero-mask">
|
||||
<h2 class="quiz-title">{{ activity.title }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="quiz-card-scroll" v-if="!isFinished">
|
||||
<div v-for="(subject, index) in visibleSubjects" :key="subject.id" class="subject">
|
||||
<p>{{index+1}}、{{ subject.title }}<span class="required-star">*</span></p>
|
||||
<div class="tag">
|
||||
<van-tag v-if="subject.type === 'checkbox'" type="primary" size="large">多选</van-tag>
|
||||
<van-tag v-if="subject.type === 'radio'" type="primary" size="large">单选</van-tag>
|
||||
<van-tag v-if="subject.score" type="primary" size="large">{{subject.score}}分</van-tag>
|
||||
</div>
|
||||
|
||||
<!--单选、多选-->
|
||||
<van-checkbox-group
|
||||
v-model="subject.userSelectOptionIds"
|
||||
:ref="'subject'+subject.id"
|
||||
v-if="['radio','checkbox'].includes(subject.type)"
|
||||
<!--单选-->
|
||||
<van-radio-group
|
||||
:value="getRadioValue(subject)"
|
||||
v-if="subject.type === 'radio'"
|
||||
>
|
||||
<van-cell-group>
|
||||
<van-cell
|
||||
v-for="(option,index) in subject.options"
|
||||
clickable
|
||||
:key="option.id"
|
||||
:title="option.text"
|
||||
@click="cellToggle(subject,option.id)"
|
||||
>
|
||||
<template #icon>
|
||||
<van-checkbox :name="option.id" :ref="'option'+option.id" style="margin-right: 10px"></van-checkbox>
|
||||
</template>
|
||||
<img
|
||||
slot="right-icon"
|
||||
v-if="option.imgUrl"
|
||||
:src="option.imgUrl"
|
||||
alt=""
|
||||
style="width: 40px; height: 40px"
|
||||
@click.stop="previewOptionImg(option.imgUrl)"
|
||||
/>
|
||||
</van-cell>
|
||||
<template v-for="(option,index) in subject.options">
|
||||
<van-cell
|
||||
clickable
|
||||
:key="option.id"
|
||||
:title="option.text"
|
||||
:class="getFinishedOptionClass(subject, option)"
|
||||
@click="cellToggle(subject,option.id)"
|
||||
>
|
||||
<template #title>
|
||||
<span class="survey-option-text">{{option.text}}</span>
|
||||
</template>
|
||||
<template #icon>
|
||||
<van-radio :name="option.id" :disabled="answerRecord.isFinish" style="margin-right: 10px"></van-radio>
|
||||
</template>
|
||||
<img
|
||||
slot="right-icon"
|
||||
v-if="option.imgUrl"
|
||||
:src="option.imgUrl"
|
||||
alt=""
|
||||
style="width: 40px; height: 40px"
|
||||
@click.stop="previewOptionImg(option.imgUrl)"
|
||||
/>
|
||||
</van-cell>
|
||||
<div
|
||||
class="option-fill-box"
|
||||
v-if="isOptionFillVisible(subject, option)"
|
||||
:key="option.id + '-fill'">
|
||||
<input
|
||||
v-model="subject.optionFillContents[option.id]"
|
||||
:placeholder="option.fillPlaceholder || '请输入补充内容'">
|
||||
</div>
|
||||
</template>
|
||||
</van-cell-group>
|
||||
</van-radio-group>
|
||||
|
||||
<!--多选-->
|
||||
<van-checkbox-group
|
||||
v-model="subject.userSelectOptionIds"
|
||||
v-if="subject.type === 'checkbox'"
|
||||
>
|
||||
<van-cell-group>
|
||||
<template v-for="(option,index) in subject.options">
|
||||
<van-cell
|
||||
clickable
|
||||
:key="option.id"
|
||||
:title="option.text"
|
||||
:class="getFinishedOptionClass(subject, option)"
|
||||
@click="cellToggle(subject,option.id)"
|
||||
>
|
||||
<template #title>
|
||||
<span class="survey-option-text">{{option.text}}</span>
|
||||
</template>
|
||||
<template #icon>
|
||||
<van-checkbox :name="option.id" :ref="'option'+option.id" :disabled="answerRecord.isFinish" style="margin-right: 10px"></van-checkbox>
|
||||
</template>
|
||||
<img
|
||||
slot="right-icon"
|
||||
v-if="option.imgUrl"
|
||||
:src="option.imgUrl"
|
||||
alt=""
|
||||
style="width: 40px; height: 40px"
|
||||
@click.stop="previewOptionImg(option.imgUrl)"
|
||||
/>
|
||||
</van-cell>
|
||||
<div
|
||||
class="option-fill-box"
|
||||
:key="option.id + '-fill'"
|
||||
v-if="isOptionFillVisible(subject, option)">
|
||||
<input
|
||||
type="text"
|
||||
v-model="subject.optionFillContents[option.id]"
|
||||
:placeholder="option.fillPlaceholder || '请填写补充内容'"
|
||||
:readonly="answerRecord.isFinish"
|
||||
@click.stop />
|
||||
</div>
|
||||
</template>
|
||||
</van-cell-group>
|
||||
</van-checkbox-group>
|
||||
|
||||
<!--填空题-->
|
||||
<div class="ui-input-box" v-if="subject.type==='text'">
|
||||
<input type="text" v-model="subject.userFillContent" :readonly="answerRecord.isFinish" />
|
||||
<input type="text" v-model="subject.userFillContent" :placeholder="'请输入' + subject.title" :readonly="answerRecord.isFinish" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-control" v-if="!answerRecord.isFinish">
|
||||
<van-button type="primary" @click="onSubmit" block :disabled="isEnd">{{isEnd ? '已结束' : '提交'}}</van-button>
|
||||
<div class="button-control">
|
||||
<van-button v-if="!answerRecord.isFinish" type="primary" @click="onSubmit" block :disabled="isEnd">{{isEnd ? '已结束' : '提交'}}</van-button>
|
||||
<van-button v-else type="primary" @click="historyBack" block>返回</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -158,10 +295,34 @@ layout("/layouts/platform_h5.html"){
|
||||
return this.$moment().unix() > this.$moment(this.activity.endTime).unix()
|
||||
}
|
||||
return true
|
||||
},
|
||||
visibleSubjects() {
|
||||
return this.subjects.filter((subject) => {
|
||||
return this.isSubjectVisible(subject)
|
||||
})
|
||||
},
|
||||
pageTitle() {
|
||||
return this.getCategoryPageTitle(this.activity && this.activity.category, "问卷调查")
|
||||
},
|
||||
heroStyle() {
|
||||
if (this.activity && this.activity.cover) {
|
||||
return {
|
||||
backgroundImage: "url(" + this.activity.cover + ")"
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getCategoryPageTitle(category, defaultTitle) {
|
||||
const titleMap = {
|
||||
QUIZ: "在线答题",
|
||||
SURVEY: "问卷调查",
|
||||
VOTE: "投票"
|
||||
}
|
||||
return titleMap[category] || defaultTitle
|
||||
},
|
||||
//获取活动、题目
|
||||
listSubjects() {
|
||||
this.$axios.post("/platform/h5/qsv/survey/subjects", { activityId: this.id }).then((res) => {
|
||||
@@ -193,14 +354,214 @@ layout("/layouts/platform_h5.html"){
|
||||
const answer = this.answerRecord.extJson[subject.id]
|
||||
|
||||
if (["radio", "checkbox"].includes(subject.type)) {
|
||||
this.$refs["subject" + subject.id][0].toggleAll(false)
|
||||
answer?.optionIds.forEach((optionId) => {
|
||||
this.$nextTick(() => {
|
||||
this.$refs["option" + optionId][0].toggle()
|
||||
})
|
||||
})
|
||||
const optionIds = answer?.optionIds || []
|
||||
this.$set(subject, "userSelectOptionIds", subject.type === "radio" ? optionIds.slice(0, 1) : optionIds)
|
||||
this.$set(subject, "optionFillContents", answer?.optionFillContents || {})
|
||||
} else if (subject.type === "text") {
|
||||
subject.userFillContent = answer?.text
|
||||
this.$set(subject, "userFillContent", answer?.text)
|
||||
}
|
||||
})
|
||||
if (!this.answerRecord.isFinish) {
|
||||
this.clearHiddenSubjectAnswers()
|
||||
}
|
||||
},
|
||||
|
||||
//判断题目是否显示,未配置隐显逻辑的题目默认显示;已配置的题目按条件树递归计算后再执行显示或隐藏动作
|
||||
isSubjectVisible(subject) {
|
||||
if (!subject.visibleRuleEnabled) {
|
||||
return true
|
||||
}
|
||||
|
||||
const rootGroup = this.getVisibleRuleRootGroup(subject)
|
||||
if (!rootGroup) {
|
||||
return true
|
||||
}
|
||||
|
||||
const matched = this.evaluateVisibleRuleGroup(rootGroup)
|
||||
|
||||
if (subject.visibleRuleAction === "hide") {
|
||||
return !matched
|
||||
}
|
||||
return matched
|
||||
},
|
||||
|
||||
//读取隐显逻辑根分组,兼容旧版 subjectId/optionId 平铺条件
|
||||
getVisibleRuleRootGroup(subject) {
|
||||
let conditions = subject.visibleRuleConditions || []
|
||||
if (typeof conditions === "string") {
|
||||
try {
|
||||
conditions = JSON.parse(conditions)
|
||||
} catch (e) {
|
||||
conditions = []
|
||||
}
|
||||
}
|
||||
if (!Array.isArray(conditions) || conditions.length === 0) {
|
||||
return null
|
||||
}
|
||||
if (conditions.length === 1 && conditions[0].type === "group") {
|
||||
return this.normalizeVisibleRuleGroup(conditions[0])
|
||||
}
|
||||
const children = conditions.filter((condition) => {
|
||||
return condition.subjectId && condition.optionId
|
||||
}).map((condition) => {
|
||||
return {
|
||||
type: "item",
|
||||
field: condition.subjectId,
|
||||
operator: "contains",
|
||||
value: condition.optionId
|
||||
}
|
||||
})
|
||||
if (children.length === 0) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
type: "group",
|
||||
logic: subject.visibleRuleLogic === "OR" ? "or" : "and",
|
||||
children
|
||||
}
|
||||
},
|
||||
|
||||
//规整条件分组,避免历史数据或空字段影响递归判断
|
||||
normalizeVisibleRuleGroup(group) {
|
||||
return {
|
||||
type: "group",
|
||||
logic: (group.logic || "and").toLowerCase() === "or" ? "or" : "and",
|
||||
children: Array.isArray(group.children) ? group.children.map((child) => {
|
||||
if (child.type === "group") {
|
||||
return this.normalizeVisibleRuleGroup(child)
|
||||
}
|
||||
return {
|
||||
type: "item",
|
||||
field: child.field || child.subjectId || "",
|
||||
operator: child.operator || (child.optionId ? "contains" : "=="),
|
||||
value: child.value !== undefined && child.value !== null ? child.value : (child.optionId || "")
|
||||
}
|
||||
}) : []
|
||||
}
|
||||
},
|
||||
|
||||
//递归计算条件分组,支持并且、或者和嵌套分组
|
||||
evaluateVisibleRuleGroup(group) {
|
||||
const children = (group.children || []).filter((child) => {
|
||||
return child.type === "group" || (child.field && child.operator)
|
||||
})
|
||||
if (children.length === 0) {
|
||||
return true
|
||||
}
|
||||
const results = children.map((child) => {
|
||||
if (child.type === "group") {
|
||||
return this.evaluateVisibleRuleGroup(child)
|
||||
}
|
||||
return this.evaluateVisibleRuleItem(child)
|
||||
})
|
||||
return group.logic === "or"
|
||||
? results.some((item) => {
|
||||
return item
|
||||
})
|
||||
: results.every((item) => {
|
||||
return item
|
||||
})
|
||||
},
|
||||
|
||||
//计算单条条件,选择题按选项ID判断,填空题支持文本包含和数字大小比较
|
||||
evaluateVisibleRuleItem(condition) {
|
||||
const triggerSubject = this.subjects.find((item) => {
|
||||
return item.id === condition.field
|
||||
})
|
||||
if (!triggerSubject) {
|
||||
return false
|
||||
}
|
||||
const answer = ["radio", "checkbox"].includes(triggerSubject.type)
|
||||
? (triggerSubject.userSelectOptionIds || [])
|
||||
: triggerSubject.userFillContent
|
||||
return this.compareVisibleRuleValue(answer, condition.operator, condition.value)
|
||||
},
|
||||
|
||||
//比较条件值,和后台逻辑抽屉实时预览保持同一套语义
|
||||
compareVisibleRuleValue(answer, operator, value) {
|
||||
if (Array.isArray(answer)) {
|
||||
if (operator === "!=") {
|
||||
return !answer.includes(value)
|
||||
}
|
||||
return answer.includes(value)
|
||||
}
|
||||
if ([">", "<", ">=", "<="].includes(operator)) {
|
||||
const answerNumber = Number(answer)
|
||||
const valueNumber = Number(value)
|
||||
if (isNaN(answerNumber) || isNaN(valueNumber)) {
|
||||
return false
|
||||
}
|
||||
if (operator === ">") {
|
||||
return answerNumber > valueNumber
|
||||
}
|
||||
if (operator === "<") {
|
||||
return answerNumber < valueNumber
|
||||
}
|
||||
if (operator === ">=") {
|
||||
return answerNumber >= valueNumber
|
||||
}
|
||||
return answerNumber <= valueNumber
|
||||
}
|
||||
const answerText = answer === undefined || answer === null ? "" : String(answer)
|
||||
const valueText = value === undefined || value === null ? "" : String(value)
|
||||
if (operator === "!=") {
|
||||
return answerText !== valueText
|
||||
}
|
||||
if (operator === "contains") {
|
||||
return answerText.indexOf(valueText) !== -1
|
||||
}
|
||||
return answerText === valueText
|
||||
},
|
||||
|
||||
//清空当前隐藏题目的答案,避免隐藏题残留旧选项或填空内容参与后续提交
|
||||
clearHiddenSubjectAnswers() {
|
||||
this.subjects.forEach((subject) => {
|
||||
if (this.isSubjectVisible(subject)) {
|
||||
return
|
||||
}
|
||||
if (["radio", "checkbox"].includes(subject.type)) {
|
||||
this.$set(subject, "userSelectOptionIds", [])
|
||||
this.$set(subject, "optionFillContents", {})
|
||||
} else if (subject.type === "text") {
|
||||
this.$set(subject, "userFillContent", null)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
//判断选项是否已被选择,用于控制选项补充填写框显示和提交校验
|
||||
isOptionSelected(subject, optionId) {
|
||||
return subject.userSelectOptionIds && subject.userSelectOptionIds.includes(optionId)
|
||||
},
|
||||
|
||||
getFinishedOptionClass(subject, option) {
|
||||
return this.answerRecord.isFinish && this.isOptionSelected(subject, option.id)
|
||||
? "survey-option-finished-selected"
|
||||
: ""
|
||||
},
|
||||
|
||||
//单选题仍按数组保存,这里只取第一个值用于 radio 组件展示
|
||||
getRadioValue(subject) {
|
||||
return subject.userSelectOptionIds && subject.userSelectOptionIds.length > 0
|
||||
? subject.userSelectOptionIds[0]
|
||||
: ""
|
||||
},
|
||||
|
||||
//选中需要补充填写的选项后显示输入框,例如选择“否”后填写原因
|
||||
isOptionFillVisible(subject, option) {
|
||||
return option.fillRequired && this.isOptionSelected(subject, option.id)
|
||||
},
|
||||
|
||||
//清空未选中选项的补充填写内容,避免取消选择后残留旧内容
|
||||
clearUnselectedOptionFillContents(subject) {
|
||||
if (!subject.optionFillContents) {
|
||||
this.$set(subject, "optionFillContents", {})
|
||||
}
|
||||
if (!subject.options) {
|
||||
return
|
||||
}
|
||||
subject.options.forEach((option) => {
|
||||
if (!this.isOptionSelected(subject, option.id)) {
|
||||
this.$delete(subject.optionFillContents, option.id)
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -212,9 +573,14 @@ layout("/layouts/platform_h5.html"){
|
||||
}
|
||||
|
||||
if (subject.type === "radio") {
|
||||
this.$refs["subject" + subject.id][0].toggleAll(false)
|
||||
this.$set(subject, "userSelectOptionIds", [optionId])
|
||||
} else {
|
||||
this.$refs["option" + optionId][0].toggle()
|
||||
}
|
||||
this.$refs["option" + optionId][0].toggle()
|
||||
this.$nextTick(() => {
|
||||
this.clearUnselectedOptionFillContents(subject)
|
||||
this.clearHiddenSubjectAnswers()
|
||||
})
|
||||
},
|
||||
|
||||
//预览图片
|
||||
@@ -231,13 +597,20 @@ layout("/layouts/platform_h5.html"){
|
||||
}
|
||||
|
||||
//提示那些题没有作答
|
||||
for (let i = 0; i < this.subjects.length; i++) {
|
||||
const subject = this.subjects[i]
|
||||
for (let i = 0; i < this.visibleSubjects.length; i++) {
|
||||
const subject = this.visibleSubjects[i]
|
||||
if (["radio", "checkbox"].includes(subject.type)) {
|
||||
if (!subject.userSelectOptionIds || subject.userSelectOptionIds.length === 0) {
|
||||
this.$toast.fail("第" + (i + 1) + "题未做答")
|
||||
return
|
||||
}
|
||||
for (let j = 0; j < subject.options.length; j++) {
|
||||
const option = subject.options[j]
|
||||
if (this.isOptionFillVisible(subject, option) && !subject.optionFillContents[option.id]) {
|
||||
this.$toast.fail("请填写第" + (i + 1) + "题的补充内容")
|
||||
return
|
||||
}
|
||||
}
|
||||
} else if ("text" === subject.type) {
|
||||
if (!subject.userFillContent) {
|
||||
this.$toast.fail("第" + (i + 1) + "题未作答")
|
||||
@@ -255,11 +628,12 @@ layout("/layouts/platform_h5.html"){
|
||||
answer: JSON.stringify({
|
||||
activityId: this.id,
|
||||
answerRecordId: this.answerRecordId,
|
||||
subjects: this.subjects.map((subject) => {
|
||||
subjects: this.visibleSubjects.map((subject) => {
|
||||
return {
|
||||
id: subject.id,
|
||||
userSelectOptionIds: ["checkbox", "radio"].includes(subject.type) ? subject.userSelectOptionIds : [],
|
||||
userFillContent: subject.type === "text" ? subject.userFillContent : null
|
||||
userFillContent: subject.type === "text" ? subject.userFillContent : null,
|
||||
optionFillContents: ["checkbox", "radio"].includes(subject.type) ? subject.optionFillContents : {}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user