perl: 选项配额优化

This commit is contained in:
dayou 2024-07-17 15:14:12 +08:00
parent 05414b1386
commit a0237c8c5f
17 changed files with 211 additions and 63 deletions

View File

@ -61,7 +61,7 @@ export interface DataItem {
starStyle?: string; starStyle?: string;
innerType?: string; innerType?: string;
deleteRecover?: boolean; deleteRecover?: boolean;
noDisplay?: boolean; quotaNoDisplay?: boolean;
} }
export interface Option { export interface Option {

View File

@ -63,7 +63,7 @@
} }
], ],
"deleteRecover": false, "deleteRecover": false,
"noDisplay": false "quotaNoDisplay": false
} }
] ]
} }

View File

@ -77,5 +77,5 @@ export const defaultQuestionConfig = {
} }
}, },
deleteRecover: false, deleteRecover: false,
noDisplay: false quotaNoDisplay: false
} }

View File

@ -25,7 +25,7 @@ import 'element-plus/theme-chalk/src/message.scss'
import { saveSurvey } from '@/management/api/survey' import { saveSurvey } from '@/management/api/survey'
import { showLogicEngine } from '@/management/hooks/useShowLogicEngine' import { showLogicEngine } from '@/management/hooks/useShowLogicEngine'
import buildData from './buildData' import buildData from './buildData'
import { getSurveyHistory, getConflictHistory } from '@/management/api/survey' import { getConflictHistory } from '@/management/api/survey'
const isSaving = ref<boolean>(false) const isSaving = ref<boolean>(false)
const isShowAutoSave = ref<boolean>(false) const isShowAutoSave = ref<boolean>(false)

View File

@ -48,7 +48,7 @@ export default defineComponent({
type: Number, type: Number,
default: 10 default: 10
}, },
noDisplay:{ quotaNoDisplay:{
type: Boolean, type: Boolean,
default: true default: true
} }
@ -103,7 +103,6 @@ export default defineComponent({
<div class="choice-wrapper"> <div class="choice-wrapper">
<div class={[isMatrix ? 'nest-box' : '', 'choice-box']}> <div class={[isMatrix ? 'nest-box' : '', 'choice-box']}>
{getOptions.map((item, index) => { {getOptions.map((item, index) => {
item.disabled = !this.readonly && item.quota !== "0" && (item.quota - item.voteCount) === 0
return ( return (
!item.hide && ( !item.hide && (
<div <div
@ -152,16 +151,16 @@ export default defineComponent({
)} )}
{ {
// //
!this.readonly && item.quota !== "0" && !this.noDisplay && ( !this.readonly && (item.quota && item.quota !== "0") && !this.quotaNoDisplay && (
<span <span
class="remaining-text" class="remaining-text"
style={{ style={{
display: 'block', display: 'block',
fontSize: 'smaller', fontSize: 'smaller',
color: item.quota - item.voteCount === 0 ? '#EB505C' : '#92949D' color: item.release === 0 ? '#EB505C' : '#92949D'
}} }}
> >
剩余{item.quota - item.voteCount} 剩余{item.release}
</span> </span>
)} )}
{slots.vote?.({ {slots.vote?.({

View File

@ -42,7 +42,7 @@ export default defineComponent({
type: [Number, String], type: [Number, String],
default: 1 default: 1
}, },
noDisplay:{ quotaNoDisplay:{
type: Boolean, type: Boolean,
default: false default: false
} }
@ -64,7 +64,7 @@ export default defineComponent({
return options.map((item) => { return options.map((item) => {
return { return {
...item, ...item,
disabled: isDisabled(item) disabled: (item.release === 0) || isDisabled(item)
} }
}) })
}) })
@ -101,7 +101,7 @@ export default defineComponent({
} }
}, },
render() { render() {
const { readonly, field, myOptions, onChange, maxNum, value, noDisplay, selectMoreView } = this const { readonly, field, myOptions, onChange, maxNum, value, quotaNoDisplay, selectMoreView } = this
return ( return (
<BaseChoice <BaseChoice
uiTarget="checkbox" uiTarget="checkbox"
@ -111,7 +111,7 @@ export default defineComponent({
options={myOptions} options={myOptions}
onChange={onChange} onChange={onChange}
value={value} value={value}
noDisplay={noDisplay} quotaNoDisplay={quotaNoDisplay}
> >
{{ {{
selectMore: (scoped) => { selectMore: (scoped) => {

View File

@ -112,9 +112,27 @@ const meta = {
] ]
}, },
{ {
key: "quotaConfig", name: 'optionQuota',
name: "quotaConfig", label: '选项配额',
type: "QuotaConfig", labelStyle: {
'font-weight': 'bold'
},
type: 'QuotaConfig',
// 输出转换
valueSetter({ options, deleteRecover, quotaNoDisplay}) {
return [{
key: 'options',
value: options
},
{
key: 'deleteRecover',
value: deleteRecover
},
{
key: 'quotaNoDisplay',
value: quotaNoDisplay
}]
}
} }
], ],
editConfigure: { editConfigure: {

View File

@ -32,7 +32,7 @@ export default defineComponent({
type: Boolean, type: Boolean,
default: false default: false
}, },
noDisplay:{ quotaNoDisplay:{
type: Boolean, type: Boolean,
default: false default: false
} }
@ -85,7 +85,7 @@ export default defineComponent({
field={this.field} field={this.field}
layout={this.layout} layout={this.layout}
onChange={this.onChange} onChange={this.onChange}
noDisplay={this.noDisplay} quotaNoDisplay={this.quotaNoDisplay}
> >
{{ {{
selectMore: (scoped) => { selectMore: (scoped) => {

View File

@ -72,6 +72,19 @@ const meta = {
} }
] ]
}, },
// deleteRecover
{
name: 'deleteRecover',
propType: Boolean,
description: '删除后恢复选项配额',
defaultValue: false
},
{
name: 'quotaNoDisplay',
propType: Boolean,
description: '不展示配额剩余数量',
defaultValue: false
}
], ],
formConfig: [ formConfig: [
basicConfig, basicConfig,
@ -87,9 +100,27 @@ const meta = {
hidden: true hidden: true
}, },
{ {
key: "quotaConfig", name: 'optionQuota',
name: "quotaConfig", label: '选项配额',
type: "QuotaConfig", labelStyle: {
'font-weight': 'bold'
},
type: 'QuotaConfig',
// 输出转换
valueSetter({ options, deleteRecover, quotaNoDisplay}) {
return [{
key: 'options',
value: options
},
{
key: 'deleteRecover',
value: deleteRecover
},
{
key: 'quotaNoDisplay',
value: quotaNoDisplay
}]
}
} }
], ],
editConfigure: { editConfigure: {

View File

@ -1,9 +1,7 @@
<template> <template>
<div class="quota-wrapper"> <div class="quota-wrapper">
<span class="quota-title">选项配额</span>
<span class="quota-config" @click="openQuotaConfig"> 设置> </span> <span class="quota-config" @click="openQuotaConfig"> 设置> </span>
<el-dialog v-model="dialogVisible" @closed="cleanTempQuota" class="dialog">
<el-dialog v-model="dialogTableVisible" @closed="cleanTempQuota" class="dialog">
<template #header> <template #header>
<div class="dialog-title">选项配额</div> <div class="dialog-title">选项配额</div>
</template> </template>
@ -58,7 +56,7 @@
</el-tooltip> </el-tooltip>
</div> </div>
<div> <div>
<el-checkbox v-model="noDisplayValue" label="不展示配额剩余数量"> </el-checkbox> <el-checkbox v-model="quotaNoDisplayValue" label="不展示配额剩余数量"> </el-checkbox>
<el-tooltip <el-tooltip
class="tooltip" class="tooltip"
effect="dark" effect="dark"
@ -88,26 +86,30 @@ import { ElMessageBox } from 'element-plus'
const props = defineProps(['formConfig', 'moduleConfig']) const props = defineProps(['formConfig', 'moduleConfig'])
const emit = defineEmits(['form-change']) const emit = defineEmits(['form-change'])
const dialogTableVisible = ref(false) const dialogVisible = ref(false)
const moduleConfig = ref(props.moduleConfig) const moduleConfig = ref(props.moduleConfig)
const optionData = ref(props.moduleConfig.options) const optionData = ref(props.moduleConfig.options)
const deleteRecoverValue = ref(moduleConfig.value.deleteRecover) const deleteRecoverValue = ref(moduleConfig.value.deleteRecover)
const noDisplayValue = ref(moduleConfig.value.noDisplay) const quotaNoDisplayValue = ref(moduleConfig.value.quotaNoDisplay)
const openQuotaConfig = () => { const openQuotaConfig = () => {
optionData.value.forEach((item) => { optionData.value.forEach((item) => {
item.tempQuota = item.quota item.tempQuota = item.quota
}) })
dialogTableVisible.value = true dialogVisible.value = true
} }
const cancel = () => { const cancel = () => {
dialogTableVisible.value = false dialogVisible.value = false
} }
const confirm = () => { const confirm = () => {
handleDeleteRecoverChange() dialogVisible.value = false
handleNoDisplayChange() //
handleQuotaChange() handleQuotaChange()
dialogTableVisible.value = false emit(FORM_CHANGE_EVENT_KEY, {
options: optionData.value,
deleteRecover: deleteRecoverValue.value,
quotaNoDisplay: quotaNoDisplayValue.value
})
} }
const handleCellClick = (row, column) => { const handleCellClick = (row, column) => {
if (column.property === 'quota') { if (column.property === 'quota') {
@ -127,18 +129,6 @@ const handleInput = (row) => {
} }
row.isEditing = false row.isEditing = false
} }
const handleDeleteRecoverChange = () => {
const key = 'deleteRecover'
const value = deleteRecoverValue.value
emit(FORM_CHANGE_EVENT_KEY, { key, value })
}
const handleNoDisplayChange = () => {
const key = 'noDisplay'
const value = noDisplayValue.value
emit(FORM_CHANGE_EVENT_KEY, { key, value })
}
const handleQuotaChange = () => { const handleQuotaChange = () => {
optionData.value.forEach((item) => { optionData.value.forEach((item) => {
item.quota = item.tempQuota item.quota = item.tempQuota
@ -156,7 +146,7 @@ watch(
moduleConfig.value = val moduleConfig.value = val
optionData.value = val.options optionData.value = val.options
deleteRecoverValue.value = val.deleteRecover deleteRecoverValue.value = val.deleteRecover
noDisplayValue.value = val.noDisplay quotaNoDisplayValue.value = val.quotaNoDisplay
}, },
{ immediate: true, deep: true } { immediate: true, deep: true }
) )
@ -164,9 +154,9 @@ watch(
<style lang="scss" scoped> <style lang="scss" scoped>
.quota-wrapper { .quota-wrapper {
width: 100%; width: 90%;
display: flex; display: flex;
justify-content: space-between; justify-content: flex-end;
} }
.quota-title { .quota-title {
font-size: 14px; font-size: 14px;

View File

@ -13,6 +13,7 @@ import QuestionRuleContainer from '../../materials/questions/QuestionRuleContain
import { useVoteMap } from '@/render/hooks/useVoteMap' import { useVoteMap } from '@/render/hooks/useVoteMap'
import { useShowOthers } from '@/render/hooks/useShowOthers' import { useShowOthers } from '@/render/hooks/useShowOthers'
import { useShowInput } from '@/render/hooks/useShowInput' import { useShowInput } from '@/render/hooks/useShowInput'
import { useOptionsQuota } from '@/render/hooks/useOptionsQuota'
import store from '@/render/store' import store from '@/render/store'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { ruleEngine } from '@/render/hooks/useRuleEngine.js' import { ruleEngine } from '@/render/hooks/useRuleEngine.js'
@ -41,16 +42,26 @@ const questionConfig = computed(() => {
const { type, field, options = [], ...rest } = cloneDeep(moduleConfig) const { type, field, options = [], ...rest } = cloneDeep(moduleConfig)
// console.log(field,'formValuechange') // console.log(field,'formValuechange')
let alloptions = options let alloptions = options
if (type === QUESTION_TYPE.VOTE || NORMAL_CHOICES.includes(type)) { if (type === QUESTION_TYPE.VOTE) {
//
const { options, voteTotal } = useVoteMap(field) const { options, voteTotal } = useVoteMap(field)
const voteOptions = unref(options) const voteOptions = unref(options)
alloptions = alloptions.map((obj, index) => Object.assign(obj, voteOptions[index])) alloptions = alloptions.map((obj, index) => Object.assign(obj, voteOptions[index]))
moduleConfig.voteTotal = unref(voteTotal) moduleConfig.voteTotal = unref(voteTotal)
} }
if(NORMAL_CHOICES.includes(type) &&
options.some(option => option.quota > 0)) {
//
let { options: optionWithQuota } = useOptionsQuota(field)
alloptions = alloptions.map((obj, index) => Object.assign(obj, optionWithQuota[index]))
console.log({alloptions})
}
if ( if (
NORMAL_CHOICES.includes(type) && NORMAL_CHOICES.includes(type) &&
options.filter((optionItem) => optionItem.others).length > 0 options.some(option => option.others)
) { ) {
//
let { options, othersValue } = useShowOthers(field) let { options, othersValue } = useShowOthers(field)
const othersOptions = unref(options) const othersOptions = unref(options)
alloptions = alloptions.map((obj, index) => Object.assign(obj, othersOptions[index])) alloptions = alloptions.map((obj, index) => Object.assign(obj, othersOptions[index]))
@ -60,6 +71,7 @@ const questionConfig = computed(() => {
RATES.includes(type) && rest?.rangeConfig && RATES.includes(type) && rest?.rangeConfig &&
Object.keys(rest?.rangeConfig).filter((index) => rest?.rangeConfig[index].isShowInput).length > 0 Object.keys(rest?.rangeConfig).filter((index) => rest?.rangeConfig[index].isShowInput).length > 0
) { ) {
//
let { rangeConfig, othersValue } = useShowInput(field) let { rangeConfig, othersValue } = useShowInput(field)
moduleConfig.rangeConfig = unref(rangeConfig) moduleConfig.rangeConfig = unref(rangeConfig)
moduleConfig.othersValue = unref(othersValue) moduleConfig.othersValue = unref(othersValue)
@ -105,5 +117,10 @@ const handleChange = (data) => {
if (props.moduleConfig.type === QUESTION_TYPE.VOTE) { if (props.moduleConfig.type === QUESTION_TYPE.VOTE) {
store.dispatch('updateVoteData', data) store.dispatch('updateVoteData', data)
} }
//
if (props.moduleConfig.type === NORMAL_CHOICES) {
store.dispatch('changeQuota', data)
}
} }
</script> </script>

View File

@ -0,0 +1,22 @@
import store from '../store/index'
export const useOptionsQuota = (questionKey) => {
const options = store.state.questionData[questionKey].options.map((option) => {
if(option.quota){
const optionHash = option.hash
const selectCount = store.state.quotaMap?.[questionKey]?.[optionHash] || 0
const release = Number(option.quota) - Number(selectCount)
return {
...option,
disabled: release === 0,
selectCount,
release
}
} else {
return {
...option,
}
}
})
return { options }
}

View File

@ -1,7 +1,6 @@
import store from '../store/index' import store from '../store/index'
export const useVoteMap = (questionKey) => { export const useVoteMap = (questionKey) => {
let voteTotal = store.state.voteMap?.[questionKey]?.total || 0 let voteTotal = store.state.voteMap?.[questionKey]?.total || 0
const options = store.state.questionData[questionKey].options.map((option) => { const options = store.state.questionData[questionKey].options.map((option) => {
const optionHash = option.hash const optionHash = option.hash
const voteCount = store.state.voteMap?.[questionKey]?.[optionHash] || 0 const voteCount = store.state.voteMap?.[questionKey]?.[optionHash] || 0

View File

@ -82,14 +82,14 @@ const normalizationRequestBody = () => {
} }
// //
localStorage.removeItem(surveyPath + "_questionData") localStorage.removeItem(surveyPath.value + "_questionData")
localStorage.removeItem("isSubmit") localStorage.removeItem("isSubmit")
// //
var formData = Object.assign({}, store.state.formValues) var formData = Object.assign({}, store.state.formValues)
for(const key in formData){ for(const key in formData){
formData[key] = encodeURIComponent(formData[key]) formData[key] = encodeURIComponent(formData[key])
} }
localStorage.setItem(surveyPath + "_questionData", JSON.stringify(formData)) localStorage.setItem(surveyPath.value + "_questionData", JSON.stringify(formData))
localStorage.setItem('isSubmit', JSON.stringify(true)) localStorage.setItem('isSubmit', JSON.stringify(true))
if (encryptInfo?.encryptType) { if (encryptInfo?.encryptType) {

View File

@ -5,10 +5,11 @@ import 'moment/locale/zh-cn'
moment.locale('zh-cn') moment.locale('zh-cn')
import adapter from '../adapter' import adapter from '../adapter'
import { queryVote, getEncryptInfo } from '@/render/api/survey' import { queryVote, getEncryptInfo } from '@/render/api/survey'
import { RuleMatch } from '@/common/logicEngine/RulesMatch'
import state from './state' import state from './state'
import useCommandComponent from '../hooks/useCommandComponent' import useCommandComponent from '../hooks/useCommandComponent'
import BackAnswerDialog from '../components/BackAnswerDialog.vue' import BackAnswerDialog from '../components/BackAnswerDialog.vue'
import { NORMAL_CHOICES } from '@/common/typeEnum.ts'
/** /**
* CODE_MAP不从management引入在dev阶段会导致B端 router被加载进而导致C端路由被添加 baseUrl: /management * CODE_MAP不从management引入在dev阶段会导致B端 router被加载进而导致C端路由被添加 baseUrl: /management
*/ */
@ -18,6 +19,7 @@ const CODE_MAP = {
NO_AUTH: 403 NO_AUTH: 403
} }
const VOTE_INFO_KEY = 'voteinfo' const VOTE_INFO_KEY = 'voteinfo'
const QUOTA_INFO_KEY = 'limitinfo'
import router from '../router' import router from '../router'
const confirm = useCommandComponent(BackAnswerDialog) const confirm = useCommandComponent(BackAnswerDialog)
@ -145,7 +147,7 @@ export default {
for (const field in questionData) { for (const field in questionData) {
const { type } = questionData[field] const { type } = questionData[field]
if (/vote/.test(type) || /radio/.test(type) || /checkbox/.test(type)) { if (/vote/.test(type)) {
fieldList.push(field) fieldList.push(field)
} }
} }
@ -221,16 +223,80 @@ export default {
console.log(error) console.log(error)
} }
}, },
async initRuleEngine({ commit }, ruleConf) { async initQuotaMap({ state, commit }) {
const ruleEngine = new RuleMatch(ruleConf) const questionData = state.questionData
commit('setRuleEgine', ruleEngine) const surveyPath = state.surveyPath
const fieldList = Object.keys(questionData).filter(field => {
if (NORMAL_CHOICES.includes(questionData[field].type)) {
return questionData[field].options.some(option => option.quota > 0)
}
})
// 如果不存在则不请求选项上限接口
if (fieldList.length <= 0) {
return
}
try {
localStorage.removeItem(QUOTA_INFO_KEY)
const quotaRes = await queryVote({
surveyPath,
fieldList: fieldList.join(',')
})
if (quotaRes.code === 200) {
localStorage.setItem(
QUOTA_INFO_KEY,
JSON.stringify({
...quotaRes.data
})
)
Object.keys(quotaRes.data).forEach(field => {
Object.keys(quotaRes.data[field]).forEach((optionHash) => {
commit('updateQuotaMapByKey', { questionKey: field, optionKey: optionHash, data: quotaRes.data[field][optionHash] })
})
})
}
} catch (error) {
console.log(error)
}
},
// 题目选中时更新选项配额
changeQuota({ state, commit }, data) {
const { key: questionKey, value: questionVal } = data
// 更新前获取接口缓存在localStorage中的数据
const localData = localStorage.getItem(QUOTA_INFO_KEY)
const quotaMap = JSON.parse(localData)
// const quotaMap = state.quotaMap
const currentQuestion = state.questionData[questionKey]
const options = currentQuestion.options
options.forEach((option) => {
const optionhash = option.hash
const selectCount = quotaMap?.[questionKey]?.[optionhash].selectCount || 0
// 如果选中值包含该选项,对应 voteCount 和 voteTotal + 1
if (
Array.isArray(questionVal) ? questionVal.includes(optionhash) : questionVal === optionhash
) {
const countPayload = {
questionKey,
optionKey: optionhash,
selectCount: selectCount + 1
}
commit('updateQuotaMapByKey', countPayload)
} else {
const countPayload = {
questionKey,
optionKey: optionhash,
selectCount: selectCount
}
commit('updateQuotaMapByKey', countPayload)
}
})
} }
} }
// 加载上次填写过的数据到问卷页 // 加载上次填写过的数据到问卷页
function loadFormData({ commit, dispatch }, {bannerConf, baseConf, bottomConf, dataConf, skinConf, submitConf }, formData) { function loadFormData({ commit, dispatch }, {bannerConf, baseConf, bottomConf, dataConf, skinConf, submitConf }, formData) {
commit('setRouter', 'indexPage')
// 根据初始的schema生成questionData, questionSeq, rules, formValues, 这四个字段 // 根据初始的schema生成questionData, questionSeq, rules, formValues, 这四个字段
const { questionData, questionSeq, rules, formValues } = adapter.generateData({ const { questionData, questionSeq, rules, formValues } = adapter.generateData({
bannerConf, bannerConf,
@ -262,11 +328,12 @@ export default {
}) })
// 获取已投票数据 // 获取已投票数据
dispatch('initVoteData') dispatch('initVoteData')
// 获取选项上线选中数据
dispatch('initQuotaMap')
} }
// 加载空白页面 // 加载空白页面
function clearFormData({ commit, dispatch }, { bannerConf, baseConf, bottomConf, dataConf, skinConf, submitConf }) { function clearFormData({ commit, dispatch }, { bannerConf, baseConf, bottomConf, dataConf, skinConf, submitConf }) {
commit('setRouter', 'indexPage')
// 根据初始的schema生成questionData, questionSeq, rules, formValues, 这四个字段 // 根据初始的schema生成questionData, questionSeq, rules, formValues, 这四个字段
const { questionData, questionSeq, rules, formValues } = adapter.generateData({ const { questionData, questionSeq, rules, formValues } = adapter.generateData({
@ -293,4 +360,5 @@ export default {
}) })
// 获取已投票数据 // 获取已投票数据
dispatch('initVoteData') dispatch('initVoteData')
dispatch('initQuotaMap')
} }

View File

@ -58,7 +58,11 @@ export default {
setEncryptInfo(state, data) { setEncryptInfo(state, data) {
state.encryptInfo = data state.encryptInfo = data
}, },
setRuleEgine(state, ruleEngine) { updateQuotaMapByKey(state, { questionKey, optionKey, data }) {
state.ruleEngine = ruleEngine // 兼容为空的情况
if (!state.quotaMap[questionKey]) {
state.quotaMap[questionKey] = {}
}
state.quotaMap[questionKey][optionKey] = data
} }
} }

View File

@ -12,5 +12,5 @@ export default {
questionSeq: [], // 题目的顺序,因为可能会有分页的情况,所以是一个二维数组[[qid1, qid2], [qid3,qid4]] questionSeq: [], // 题目的顺序,因为可能会有分页的情况,所以是一个二维数组[[qid1, qid2], [qid3,qid4]]
voteMap: {}, voteMap: {},
encryptInfo: null, encryptInfo: null,
ruleEngine: null quotaMap: {}
} }