【北大开源实践】- 问卷断点续答 - 前端 (#282)
* feat:增加断点续答功能 * feat:增加断点续答功能 * fix: 同步代码并且解决冲突 --------- Co-authored-by: dayou <853094838@qq.com>
This commit is contained in:
parent
ef5d775276
commit
9047e6a344
52
web/components.d.ts
vendored
Normal file
52
web/components.d.ts
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
export {}
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSelectV2: typeof import('element-plus/es')['ElSelectV2']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
IEpBottom: typeof import('~icons/ep/bottom')['default']
|
||||
IEpCheck: typeof import('~icons/ep/check')['default']
|
||||
IEpCirclePlus: typeof import('~icons/ep/circle-plus')['default']
|
||||
IEpClose: typeof import('~icons/ep/close')['default']
|
||||
IEpCopyDocument: typeof import('~icons/ep/copy-document')['default']
|
||||
IEpDelete: typeof import('~icons/ep/delete')['default']
|
||||
IEpIphone: typeof import('~icons/ep/iphone')['default']
|
||||
IEpLoading: typeof import('~icons/ep/loading')['default']
|
||||
IEpMinus: typeof import('~icons/ep/minus')['default']
|
||||
IEpMonitor: typeof import('~icons/ep/monitor')['default']
|
||||
IEpMore: typeof import('~icons/ep/more')['default']
|
||||
IEpSearch: typeof import('~icons/ep/search')['default']
|
||||
IEpSort: typeof import('~icons/ep/sort')['default']
|
||||
IEpSortDown: typeof import('~icons/ep/sort-down')['default']
|
||||
IEpSortUp: typeof import('~icons/ep/sort-up')['default']
|
||||
IEpTop: typeof import('~icons/ep/top')['default']
|
||||
IEpView: typeof import('~icons/ep/view')['default']
|
||||
IEpWarningFilled: typeof import('~icons/ep/warning-filled')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
}
|
||||
export interface ComponentCustomProperties {
|
||||
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||
}
|
||||
}
|
@ -67,13 +67,13 @@ const setterList = computed(() => {
|
||||
}
|
||||
} else {
|
||||
formValue = _get(store.state.edit.schema, formKey, formItem.value)
|
||||
console.log("formVaue:", formValue)
|
||||
dataConfig[formKey] = formValue
|
||||
}
|
||||
formItem.value = formValue
|
||||
}
|
||||
|
||||
form.dataConfig = dataConfig
|
||||
|
||||
return form
|
||||
})
|
||||
})
|
||||
|
@ -7,6 +7,6 @@ export default [
|
||||
{
|
||||
title: '提交限制',
|
||||
key: 'limitConfig',
|
||||
formList: ['limit_tLimit']
|
||||
formList: ['limit_tLimit', 'limit_breakAnswer', 'limit_backAnswer']
|
||||
}
|
||||
]
|
||||
|
@ -21,5 +21,19 @@ export default {
|
||||
tip: '问卷仅在指定时间段内可填写',
|
||||
type: 'QuestionTimeHour',
|
||||
placement: 'top'
|
||||
},
|
||||
limit_breakAnswer: {
|
||||
key: 'baseConf.breakAnswer',
|
||||
label: '允许断点续答',
|
||||
tip: '回填前一次作答中的内容(注:更换设备/浏览器/清除缓存/更改内容重新发布则此功能失效)',
|
||||
type: 'ELSwitch',
|
||||
value: false
|
||||
},
|
||||
limit_backAnswer: {
|
||||
key: 'baseConf.backAnswer',
|
||||
label: '自动填充上次填写内容',
|
||||
tip: '回填前一次提交的内容(注:更换设备/浏览器/清除缓存/更改内容重新发布则此功能失效)',
|
||||
type: 'ELSwitch',
|
||||
value: false
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,9 @@ export default {
|
||||
tLimit: 0,
|
||||
answerBegTime: '',
|
||||
answerEndTime: '',
|
||||
answerLimitTime: 0
|
||||
answerLimitTime: 0,
|
||||
breakAnswer: false,
|
||||
backAnswer: false
|
||||
},
|
||||
submitConf: {
|
||||
submitTitle: '',
|
||||
|
38
web/src/materials/setters/widgets/ELSwitch.vue
Normal file
38
web/src/materials/setters/widgets/ELSwitch.vue
Normal file
@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<el-switch
|
||||
v-model="modelValue"
|
||||
class="ml-2"
|
||||
style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
@change="handleInputChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { FORM_CHANGE_EVENT_KEY } from '@/materials/setters/constant'
|
||||
|
||||
interface Props {
|
||||
formConfig: any
|
||||
moduleConfig: any
|
||||
}
|
||||
|
||||
interface Emit {
|
||||
(ev: typeof FORM_CHANGE_EVENT_KEY, arg: { key: string; value: boolean }): void
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
const props = defineProps<Props>()
|
||||
const modelValue = ref(props.formConfig.value)
|
||||
|
||||
const handleInputChange = (value: boolean) => {
|
||||
const key = props.formConfig.key
|
||||
|
||||
modelValue.value = value
|
||||
|
||||
emit(FORM_CHANGE_EVENT_KEY, { key, value })
|
||||
}
|
||||
</script>
|
||||
|
||||
|
79
web/src/render/components/BackAnswerDialog.vue
Normal file
79
web/src/render/components/BackAnswerDialog.vue
Normal file
@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div class="mask" v-show="visible">
|
||||
<div class="box">
|
||||
<div class="title">{{ title }}</div>
|
||||
<div class="btn-box">
|
||||
<div class="btn cancel" @click="handleCancel">{{ cancelBtnText }}</div>
|
||||
<div class="btn confirm" @click="handleConfirm">{{ confirmBtnText }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
visible?: boolean
|
||||
cancelBtnText?: string
|
||||
confirmBtnText?: string
|
||||
title?: string
|
||||
}
|
||||
|
||||
interface Emit {
|
||||
(ev: 'confirm', callback: () => void): void
|
||||
(ev: 'cancel', callback: () => void): void
|
||||
(ev: 'close'): void
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
cancelBtnText: '取消',
|
||||
confirmBtnText: '确定',
|
||||
title: ''
|
||||
})
|
||||
|
||||
const handleConfirm = () => {
|
||||
emit('confirm', () => {
|
||||
emit('close')
|
||||
})
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('cancel', () => {
|
||||
emit('close')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import url('../styles/dialog.scss');
|
||||
|
||||
.btn-box {
|
||||
padding: 20px 0 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.btn {
|
||||
width: 48%;
|
||||
font-size: 0.28rem;
|
||||
border-radius: 0.04rem;
|
||||
text-align: center;
|
||||
padding: 0.16rem 0;
|
||||
line-height: 0.4rem;
|
||||
cursor: pointer;
|
||||
|
||||
&.cancel {
|
||||
background: #fff;
|
||||
color: #92949d;
|
||||
border: 1px solid #e3e4e8;
|
||||
}
|
||||
|
||||
&.confirm {
|
||||
background-color: #4a4c5b;
|
||||
border: 1px solid #4a4c5b;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -81,6 +81,17 @@ const normalizationRequestBody = () => {
|
||||
clientTime: Date.now()
|
||||
}
|
||||
|
||||
//浏览器缓存数据
|
||||
localStorage.removeItem(surveyPath + "_questionData")
|
||||
localStorage.removeItem("isSubmit")
|
||||
//数据加密
|
||||
var formData = Object.assign({}, store.state.formValues)
|
||||
for(const key in formData){
|
||||
formData[key] = encodeURIComponent(formData[key])
|
||||
}
|
||||
localStorage.setItem(surveyPath + "_questionData", JSON.stringify(formData))
|
||||
localStorage.setItem('isSubmit', JSON.stringify(true))
|
||||
|
||||
if (encryptInfo?.encryptType) {
|
||||
result.encryptType = encryptInfo?.encryptType
|
||||
result.data = encrypt[result.encryptType as 'rsa']({
|
||||
|
@ -6,6 +6,9 @@ moment.locale('zh-cn')
|
||||
import adapter from '../adapter'
|
||||
import { queryVote, getEncryptInfo } from '@/render/api/survey'
|
||||
import { RuleMatch } from '@/common/logicEngine/RulesMatch'
|
||||
import state from './state'
|
||||
import useCommandComponent from '../hooks/useCommandComponent'
|
||||
import BackAnswerDialog from '../components/BackAnswerDialog.vue'
|
||||
/**
|
||||
* CODE_MAP不从management引入,在dev阶段,会导致B端 router被加载,进而导致C端路由被添加 baseUrl: /management
|
||||
*/
|
||||
@ -16,6 +19,9 @@ const CODE_MAP = {
|
||||
}
|
||||
const VOTE_INFO_KEY = 'voteinfo'
|
||||
import router from '../router'
|
||||
|
||||
const confirm = useCommandComponent(BackAnswerDialog)
|
||||
|
||||
export default {
|
||||
// 初始化
|
||||
init(
|
||||
@ -23,7 +29,7 @@ export default {
|
||||
{ bannerConf, baseConf, bottomConf, dataConf, skinConf, submitConf }
|
||||
) {
|
||||
commit('setEnterTime')
|
||||
const { begTime, endTime, answerBegTime, answerEndTime } = baseConf
|
||||
const { begTime, endTime, answerBegTime, answerEndTime, breakAnswer, backAnswer} = baseConf
|
||||
const { msgContent } = submitConf
|
||||
const now = Date.now()
|
||||
if (now < new Date(begTime).getTime()) {
|
||||
@ -57,31 +63,72 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
// 根据初始的schema生成questionData, questionSeq, rules, formValues, 这四个字段
|
||||
const { questionData, questionSeq, rules, formValues } = adapter.generateData({
|
||||
bannerConf,
|
||||
baseConf,
|
||||
bottomConf,
|
||||
dataConf,
|
||||
skinConf,
|
||||
submitConf
|
||||
})
|
||||
//回填,断点续填
|
||||
const localData = JSON.parse(localStorage.getItem(state.surveyPath + "_questionData"))
|
||||
|
||||
// 将数据设置到state上
|
||||
commit('assignState', {
|
||||
questionData,
|
||||
questionSeq,
|
||||
rules,
|
||||
bannerConf,
|
||||
baseConf,
|
||||
bottomConf,
|
||||
dataConf,
|
||||
skinConf,
|
||||
submitConf,
|
||||
formValues
|
||||
})
|
||||
// 获取已投票数据
|
||||
dispatch('initVoteData')
|
||||
//数据解密
|
||||
for(const key in localData){
|
||||
localData[key] = decodeURIComponent(localData[key])
|
||||
}
|
||||
|
||||
const isSubmit = JSON.parse(localStorage.getItem('isSubmit'))
|
||||
if(localData) {
|
||||
if(isSubmit){
|
||||
if(!backAnswer) {
|
||||
clearFormData({ commit, dispatch }, { bannerConf, baseConf, bottomConf, dataConf, skinConf, submitConf })
|
||||
} else {
|
||||
confirm({
|
||||
title: "您之前已提交过问卷,是否要回填?",
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
loadFormData({ commit, dispatch }, {bannerConf, baseConf, bottomConf, dataConf, skinConf, submitConf }, localData)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
} finally {
|
||||
confirm.close()
|
||||
}
|
||||
},
|
||||
onCancel: async() => {
|
||||
try {
|
||||
clearFormData({ commit, dispatch }, { bannerConf, baseConf, bottomConf, dataConf, skinConf, submitConf })
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
} finally {
|
||||
confirm.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
} else{
|
||||
if(!breakAnswer) {
|
||||
clearFormData({ commit, dispatch }, { bannerConf, baseConf, bottomConf, dataConf, skinConf, submitConf })
|
||||
} else {
|
||||
confirm({
|
||||
title: "您之前已填写部分内容, 是否要继续填写?",
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
loadFormData({ commit, dispatch }, {bannerConf, baseConf, bottomConf, dataConf, skinConf, submitConf }, localData)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
} finally {
|
||||
confirm.close()
|
||||
}
|
||||
},
|
||||
onCancel: async() => {
|
||||
try {
|
||||
clearFormData({ commit, dispatch }, { bannerConf, baseConf, bottomConf, dataConf, skinConf, submitConf })
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
} finally {
|
||||
confirm.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
clearFormData({ commit, dispatch }, { bannerConf, baseConf, bottomConf, dataConf, skinConf, submitConf })
|
||||
}
|
||||
},
|
||||
// 用户输入或者选择后,更新表单数据
|
||||
changeData({ commit }, data) {
|
||||
@ -177,3 +224,71 @@ export default {
|
||||
commit('setRuleEgine', ruleEngine)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载上次填写过的数据到问卷页
|
||||
function loadFormData({ commit, dispatch }, {bannerConf, baseConf, bottomConf, dataConf, skinConf, submitConf }, formData) {
|
||||
commit('setRouter', 'indexPage')
|
||||
|
||||
// 根据初始的schema生成questionData, questionSeq, rules, formValues, 这四个字段
|
||||
const { questionData, questionSeq, rules, formValues } = adapter.generateData({
|
||||
bannerConf,
|
||||
baseConf,
|
||||
bottomConf,
|
||||
dataConf,
|
||||
skinConf,
|
||||
submitConf
|
||||
})
|
||||
console.log("formdata", formData)
|
||||
|
||||
for(const key in formData){
|
||||
formValues[key] = formData[key]
|
||||
console.log("formValues",formValues)
|
||||
}
|
||||
|
||||
// 将数据设置到state上
|
||||
commit('assignState', {
|
||||
questionData,
|
||||
questionSeq,
|
||||
rules,
|
||||
bannerConf,
|
||||
baseConf,
|
||||
bottomConf,
|
||||
dataConf,
|
||||
skinConf,
|
||||
submitConf,
|
||||
formValues
|
||||
})
|
||||
// 获取已投票数据
|
||||
dispatch('initVoteData')
|
||||
}
|
||||
|
||||
// 加载空白页面
|
||||
function clearFormData({ commit, dispatch }, { bannerConf, baseConf, bottomConf, dataConf, skinConf, submitConf }) {
|
||||
commit('setRouter', 'indexPage')
|
||||
|
||||
// 根据初始的schema生成questionData, questionSeq, rules, formValues, 这四个字段
|
||||
const { questionData, questionSeq, rules, formValues } = adapter.generateData({
|
||||
bannerConf,
|
||||
baseConf,
|
||||
bottomConf,
|
||||
dataConf,
|
||||
skinConf,
|
||||
submitConf
|
||||
})
|
||||
|
||||
// 将数据设置到state上
|
||||
commit('assignState', {
|
||||
questionData,
|
||||
questionSeq,
|
||||
rules,
|
||||
bannerConf,
|
||||
baseConf,
|
||||
bottomConf,
|
||||
dataConf,
|
||||
skinConf,
|
||||
submitConf,
|
||||
formValues
|
||||
})
|
||||
// 获取已投票数据
|
||||
dispatch('initVoteData')
|
||||
}
|
||||
|
@ -17,8 +17,19 @@ export default {
|
||||
},
|
||||
changeFormData(state, data) {
|
||||
let { key, value } = data
|
||||
// console.log('formValues', key, value)
|
||||
set(state, `formValues.${key}`, value)
|
||||
|
||||
//数据加密
|
||||
var formData = Object.assign({}, state.formValues);
|
||||
for(const key in formData){
|
||||
formData[key] = encodeURIComponent(formData[key])
|
||||
}
|
||||
|
||||
//浏览器存储
|
||||
localStorage.removeItem(state.surveyPath + "_questionData")
|
||||
localStorage.setItem(state.surveyPath + "_questionData", JSON.stringify(formData))
|
||||
localStorage.setItem('isSubmit', JSON.stringify(false))
|
||||
|
||||
},
|
||||
changeSelectMoreData(state, data) {
|
||||
const { key, value, field } = data
|
||||
|
Loading…
Reference in New Issue
Block a user