feat(survey): 优化问卷创建功能

- 新增保存设置功能
- 优化题目列表渲染逻辑
- 添加断点续答、IP限制等设置项
-修复部分组件样式问题
This commit is contained in:
陈昱达
2025-03-11 20:11:15 +08:00
parent 2c6f86bfc6
commit 4d34c293b8
8 changed files with 178 additions and 69 deletions

6
package-lock.json generated
View File

@@ -12,6 +12,7 @@
"axios": "^1.8.2",
"dotenv": "^16.4.7",
"element-plus": "^2.7.8",
"js-base64": "^3.7.7",
"lodash": "^4.17.21",
"pinia": "^2.1.7",
"sortablejs": "^1.15.6",
@@ -7676,6 +7677,11 @@
"node": ">= 0.4"
}
},
"node_modules/js-base64": {
"version": "3.7.7",
"resolved": "https://registry.npmmirror.com/js-base64/-/js-base64-3.7.7.tgz",
"integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw=="
},
"node_modules/js-tokens": {
"version": "4.0.0",
"dev": true,

View File

@@ -17,6 +17,7 @@
"axios": "^1.8.2",
"dotenv": "^16.4.7",
"element-plus": "^2.7.8",
"js-base64": "^3.7.7",
"lodash": "^4.17.21",
"pinia": "^2.1.7",
"sortablejs": "^1.15.6",

View File

@@ -16,8 +16,7 @@ export function saveQuestion(params) {
export function sync(params) {
return request({
url: `console/surveys/${params.sn}/sync`,
method: 'post',
data: params
method: 'get'
});
}
export function questionDetails(params) {
@@ -29,3 +28,18 @@ export function questionDetails(params) {
data: params
});
}
// 获取setting
export function getSetting(params) {
return request({
url: `/console/surveys/${params.sn}/answer_setting`,
method: 'get'
});
}
export function saveSettings(params) {
return request({
url: `/console/surveys/${params.sn}/answer_setting`,
method: 'put',
data: params
});
}

View File

@@ -167,8 +167,8 @@ const getMaxDateLimit = computed(() => {
props.format
);
const tempStr = '0000-12-31 23:59:59';
const result
= props.maxDate.length !== 0 && thisMax.length > props.maxDate.length
const result =
props.maxDate.length !== 0 && thisMax.length > props.maxDate.length
? thisMax.slice(0, props.maxDate.length) + tempStr.slice(props.maxDate.length)
: thisMax;
return result.slice(0, props.format.length);
@@ -191,8 +191,8 @@ function onChange({ selectedValues, columnIndex }) {
renderMinuteColumns,
renderSecondColumns
];
updateColumns[columnIndex]
&& updateColumns[columnIndex](changeValue, getMinDateLimit.value, getMaxDateLimit.value, false);
updateColumns[columnIndex] &&
updateColumns[columnIndex](changeValue, getMinDateLimit.value, getMaxDateLimit.value, false);
}
// 渲染全部列

View File

@@ -70,9 +70,13 @@ export const useCommonStore = defineStore('common', {
}
},
setQuestionInfo(questionInfo) {
console.log(questionInfo, 9998);
console.log(this);
this.questionsInfo = questionInfo;
// 保持原有响应式引用,仅更新变化的部分
this.questionsInfo = Object.assign({}, this.questionsInfo, questionInfo);
// 对嵌套结构特殊处理(如数组需要保持引用)
if (questionInfo.questions) {
this.questionsInfo.questions = [...questionInfo.questions];
}
}
},
getters: {

View File

@@ -121,7 +121,7 @@
</template>
<script setup>
import { v4 as uuidv4 } from 'uuid';
import { ref, onMounted, watch } from 'vue';
import { ref, onMounted, watch, computed } from 'vue';
import { useCounterStore } from '@/stores/counter';
import { storeToRefs } from 'pinia';
import Draggable from './components/Draggable.vue';
@@ -210,7 +210,7 @@ const counterStore = useCounterStore();
const store = storeToRefs(counterStore);
const chooseQuestionId = ref('');
const questionInfo = ref(store.questionsInfo.value);
const questionInfo = computed(() => store.questionsInfo.value);
const emit = defineEmits(['getActiveQuestion']);
// 获取选中的题目的ID

View File

@@ -46,7 +46,11 @@
</option-action>
</van-radio-group>
<van-checkbox-group v-if="element.question_type === 2" shape="square">
<option-action v-model:data="element.options[index]" :active="active" :question="element">
<option-action
v-model:data="element.options[optionIndex]"
:active="active"
:question="element"
>
<template #item="{ element: it, index: itIndex }">
<van-checkbox
:key="itIndex"

View File

@@ -25,13 +25,17 @@
></contenteditable>
</div>
<button @click="show = true">添加题目</button>
<button v-if="questionInfo.questions.length === 0" @click="show = true">添加题目</button>
</div>
</van-cell-group>
<div class="ques">
<!-- 题目-->
<Design :active-id="activeId" @get-active-question="getActiveQuestion"></Design>
<Design
:active-id="activeId"
class="desgin"
@get-active-question="getActiveQuestion"
></Design>
<!-- <van-button @click="show = true">添加题目</van-button>-->
<!-- 弹出的新增题目弹窗-->
<van-popup
@@ -87,7 +91,7 @@
<van-cell title="每页一题" :border="false" label-align="left">
<template #right-icon>
<van-switch
v-model="questionInfo.survey.is_page_one_question"
v-model="questionInfo.survey.is_one_page_one_question"
class="option-action-sheet-switch"
size="0.5rem"
:active-value="1"
@@ -104,6 +108,7 @@
size="0.5rem"
:active-value="1"
:inactive-value="0"
@change="saveSetting('is_publish_number', ['publish_number'])"
></van-switch>
</template>
</van-cell>
@@ -112,7 +117,13 @@
class="child-group"
:border="false"
>
<van-field label="投放数量最大为" input-align="right">
<van-field
v-model="questionInfo.survey.publish_number"
label="投放数量最大为"
input-align="right"
type="number"
@blur="saveSetting($event, 'publish_number')"
>
<template #right-icon> </template>
</van-field>
</van-cell-group>
@@ -125,13 +136,14 @@
size="0.5rem"
:active-value="1"
:inactive-value="0"
@change="saveSetting('is_time', ['start_time', 'end_time'])"
></van-switch>
</template>
</van-cell>
<!--有效期-->
<van-cell-group v-if="questionInfo.survey.is_time === 1" class="child-group" :border="false">
<van-field
v-model="questionInfo.survey.startTime"
v-model="questionInfo.survey.start_time"
is-link
label="起始时间"
input-align="right"
@@ -140,7 +152,7 @@
>
</van-field>
<van-field
v-model="questionInfo.survey.endTime"
v-model="questionInfo.survey.end_time"
is-link
label="截至时间"
input-align="right"
@@ -158,6 +170,7 @@
size="0.5rem"
:active-value="1"
:inactive-value="0"
@change="saveSetting"
></van-switch>
</template>
</van-cell>
@@ -174,6 +187,7 @@
size="0.5rem"
:active-value="1"
:inactive-value="0"
@change="saveSetting"
></van-switch>
</template>
</van-cell>
@@ -186,6 +200,7 @@
size="0.5rem"
:active-value="1"
:inactive-value="0"
@change="saveSetting('is_ip_number', ['is_number'])"
></van-switch>
</template>
</van-cell>
@@ -195,6 +210,7 @@
label="同一个IP地址只能作答"
:border="false"
input-align="right"
@blur="saveSetting($event, 'is_number')"
>
<template #right-icon> </template>
</van-field>
@@ -207,6 +223,7 @@
size="0.5rem"
:active-value="1"
:inactive-value="0"
@change="saveSetting('is_browser_number', 'browser_number')"
></van-switch>
</template>
</van-cell>
@@ -220,22 +237,23 @@
label="同一个浏览器只能作答"
:border="false"
input-align="right"
@blur="saveSetting($event, 'browser_number')"
>
<template #right-icon> </template>
</van-field>
</van-cell-group>
<van-divider></van-divider>
<van-field
v-model="endText"
label="结束语"
:border="false"
readonly
label-align="left"
input-align="right"
is-link
@click="openEndTextModel"
>
</van-field>
<!-- <van-field-->
<!-- v-model="endText"-->
<!-- label="结束语"-->
<!-- :border="false"-->
<!-- readonly-->
<!-- label-align="left"-->
<!-- input-align="right"-->
<!-- is-link-->
<!-- @click="openEndTextModel"-->
<!-- >-->
<!-- </van-field>-->
</van-cell-group>
</van-action-sheet>
@@ -253,15 +271,22 @@
</van-popup>
<!-- 结束语选择-->
<van-popup v-model:show="textModel" position="bottom" round>
<van-picker :columns="columns" @confirm="onConfirm"></van-picker>
</van-popup>
<!-- <van-popup v-model:show="textModel" position="bottom" round>-->
<!-- <van-picker :columns="columns" @confirm="onConfirm"></van-picker>-->
<!-- </van-popup>-->
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { questionDetails, snQuestions } from '@/api/design/index';
import { ref, computed, onMounted, watch } from 'vue';
import * as Base64 from 'js-base64';
import {
getSetting,
questionDetails,
saveQuestion,
snQuestions,
sync,
saveSettings
} from '@/api/design/index';
import Design from '@/views/Design/Index.vue';
import { useCounterStore } from '@/stores/counter';
import { storeToRefs } from 'pinia';
@@ -291,35 +316,12 @@ const currentType = ref();
const route = useRoute();
const surveyTitle = route.meta.title as string;
const show = ref(false);
const textModel = ref(false);
// const textModel = ref(false);
const activeId = ref(0);
const showSetting = ref(false);
const timePickerModel = ref(false);
// picker 结束语选择器
const columns = ref([
{
text: '成功完成',
value: '1'
},
{
text: '题前终止',
value: '2'
},
{
text: '配额超限',
value: '3'
}
]);
const endText = ref('');
const onConfirm = (ev) => {
endText.value = columns.value[ev.selectedValues[0] - 1].text;
textModel.value = false;
};
const openSettingAction = () => {
showSetting.value = true;
};
@@ -330,15 +332,11 @@ const showTimePicker = (type, value) => {
currentDate.value = value;
};
const openEndTextModel = () => {
textModel.value = true;
};
const onConfirmDate = (e) => {
if (currentType.value === 'start') {
questionInfo.value.survey.startTime = e;
questionInfo.value.survey.start_time = e;
} else {
questionInfo.value.survey.endTime = e;
questionInfo.value.survey.end_time = e;
}
timePickerModel.value = false;
};
@@ -460,9 +458,10 @@ const questionEvent = (item) => {
: []
})
);
// 给store 装数据 判断是 push 还是 splice
if (activeQuestionIndex.value === -1) {
questionInfo.value.questions.push(questionJson);
questionInfo.value.questions.splice(questionInfo.value.questions.length + 1, 0, questionJson);
} else {
questionInfo.value.questions.splice(activeQuestionIndex.value + 1, 0, questionJson);
}
@@ -470,6 +469,55 @@ const questionEvent = (item) => {
questionInfo.value.survey.last_question_index += 1;
activeId.value = id;
show.value = false;
saveQuestionItem(questionJson);
};
// 新增保存增加的一个
const saveQuestionItem = (questionJson) => {
const query = {
sn: route.query.sn,
data: {
logics: questionInfo.value.logics,
questions: [questionJson],
survey: {
local_pages: [],
pages: questionInfo.value.questions.map((item) => {
return [item.question_index];
}),
version: Base64.encode(`${new Date().getTime()}`)
}
}
};
saveQuestion(query).then(() => {
sync({ sn: query.sn });
});
};
// 保存设置
const saveSetting = (parentKey, childKeys) => {
const query = {};
settingList.map((item) => {
if (item === 'access_password') {
query[item] = JSON.stringify(questionInfo.value.survey[item]);
} else {
query[item] = questionInfo.value.survey[item];
}
});
// 增删 修改 数据结构
if (parentKey) {
childKeys.map((key) => {
query[key] = query[parentKey] === 1 ? query[key] : 0;
if (parentKey === 'is_time' && query[parentKey] === 0) {
delete query[key];
}
});
}
saveSettings({
sn: route.query.sn,
...query
});
};
const init = () => {
@@ -478,17 +526,45 @@ const init = () => {
};
defineExpose({ init });
// 获取题目象棋
// 定义setting 接口返回的数据集和 调用put接口时对比以下
let settingList = [];
// 获取题目相亲
const getQuestionDetail = () => {
return snQuestions({ sn: route.query.sn }).then((res) => {
if (res.data) {
counterStore.setQuestionInfo(res.data.data);
return res.data.data; // 返回数据以便在onMounted中使用
questionInfo.value.survey = Object.assign({}, res.data.data.survey);
questionInfo.value.questions = res.data.data.questions.map((item) => {
return {
...item
};
});
questionInfo.value.logics = res.data.data.logics.map((item) => {
return {
...item
};
});
getSetting({ sn: route.query.sn }).then((setting) => {
if (setting.data) {
// 获取所有的key
settingList = Object.keys(setting.data.data);
questionInfo.value.survey = Object.assign(questionInfo.value.survey, setting.data.data);
}
});
}
});
};
const questionInfo = computed(() => store.questionsInfo.value);
watch(
() => questionInfo.value.questions,
() => {
// questionInfo.value.questions = newVal;
},
{ deep: true }
);
onMounted(async () => {
await getQuestionDetail(); // 等待接口返回数据
});
@@ -548,6 +624,10 @@ onMounted(async () => {
font-size: 16px;
}
& .desgin {
padding-bottom: 50px;
}
.ques_list {
margin-bottom: 10px;
}