feat/design: 优化矩阵题样式和功能

- 新增矩阵题类型的支持和样式
- 修复矩阵题选项保存逻辑
-优化矩阵题输入框样式
- 新增项目名称保存功能
- 调整问卷创建页面逻辑
This commit is contained in:
陈昱达
2025-03-18 18:25:09 +08:00
parent 36ea47bf9c
commit 5371f41a2f
14 changed files with 166 additions and 73 deletions

1
components.d.ts vendored
View File

@@ -40,6 +40,7 @@ declare module 'vue' {
VanRadioGroup: typeof import('vant/es')['RadioGroup'] VanRadioGroup: typeof import('vant/es')['RadioGroup']
VanRow: typeof import('vant/es')['Row'] VanRow: typeof import('vant/es')['Row']
VanSearch: typeof import('vant/es')['Search'] VanSearch: typeof import('vant/es')['Search']
VanStep: typeof import('vant/es')['Step']
VanStepper: typeof import('vant/es')['Stepper'] VanStepper: typeof import('vant/es')['Stepper']
VanSwitch: typeof import('vant/es')['Switch'] VanSwitch: typeof import('vant/es')['Switch']
VanTab: typeof import('vant/es')['Tab'] VanTab: typeof import('vant/es')['Tab']

View File

@@ -43,3 +43,10 @@ export function saveSettings(params) {
data: params data: params
}); });
} }
export function saveProjectName(sn, params) {
return request({
url: `/console/surveys/${sn}/project_name`,
method: 'PATCH',
data: params
});
}

View File

@@ -224,7 +224,7 @@
} }
input { input {
outline-color: transparent; outline: none;
} }
.el-input__wrapper, .el-input__wrapper,

View File

@@ -235,7 +235,6 @@ const questionMove = (action) => {
const temp = questions.value[props.questionIndex]; const temp = questions.value[props.questionIndex];
questions.value.splice(props.questionIndex, 1); questions.value.splice(props.questionIndex, 1);
questions.value.splice(props.questionIndex + 1, 0, temp); questions.value.splice(props.questionIndex + 1, 0, temp);
emit('move', 'down'); emit('move', 'down');
} else if (action.action === 'up') { } else if (action.action === 'up') {
if (props.questionIndex === 0) { if (props.questionIndex === 0) {
@@ -247,14 +246,12 @@ const questionMove = (action) => {
emit('move', 'up'); emit('move', 'up');
} else { } else {
// 复制 题目 生成新的id 更新最新的 last index // 复制 题目 生成新的id 更新最新的 last index
const temp = questions.value[props.questionIndex]; const temp = JSON.parse(JSON.stringify(questions.value[props.questionIndex]));
const newQuestion = { const newQuestion = {
...temp, ...temp,
id: uuidv4(), id: uuidv4(),
question_index: questionsInfo.value.survey.last_question_index + 1 question_index: questionsInfo.value.survey.last_question_index + 1
}; };
questions.value.splice(props.questionIndex + 1, 0, newQuestion); questions.value.splice(props.questionIndex + 1, 0, newQuestion);
questionsInfo.value.survey.last_question_index += 1; questionsInfo.value.survey.last_question_index += 1;
emit('copy', newQuestion); emit('copy', newQuestion);

View File

@@ -12,10 +12,13 @@
@blur="emit('saveOption')" @blur="emit('saveOption')"
@update:model-value=" @update:model-value="
(value) => { (value) => {
actionQuestion.config.min_select = Number(value); actionQuestion.config.min_select = value;
} }
" "
> >
<!-- <template #input>-->
<!-- <van-step></van-step>-->
<!-- </template>-->
<template #right-icon> <template #right-icon>
<span>个</span> <span>个</span>
</template> </template>
@@ -31,7 +34,7 @@
@blur="emit('saveOption')" @blur="emit('saveOption')"
@update:model-value=" @update:model-value="
(value) => { (value) => {
actionQuestion.config.max_select = Number(value); actionQuestion.config.max_select = value;
} }
" "
> >

View File

@@ -29,7 +29,7 @@
@blur="emit('saveOption')" @blur="emit('saveOption')"
@update:model-value=" @update:model-value="
(value) => { (value) => {
actionQuestion.config.line_height = Number(value); actionQuestion.config.line_height = value;
} }
" "
> >
@@ -52,7 +52,7 @@
@blur="emit('saveOption')" @blur="emit('saveOption')"
@update:model-value=" @update:model-value="
(value) => { (value) => {
actionQuestion.config.line_height = Number(value); actionQuestion.config.line_height = value;
} }
" "
> >
@@ -90,7 +90,7 @@
@blur="emit('saveOption')" @blur="emit('saveOption')"
@update:model-value=" @update:model-value="
(value) => { (value) => {
actionQuestion.config.min = Number(value); actionQuestion.config.min = value;
} }
" "
> >
@@ -110,7 +110,7 @@
@blur="emit('saveOption')" @blur="emit('saveOption')"
@update:model-value=" @update:model-value="
(value) => { (value) => {
actionQuestion.config.max = Number(value); actionQuestion.config.max = value;
} }
" "
> >
@@ -151,7 +151,7 @@
@blur="emit('saveOption')" @blur="emit('saveOption')"
@update:model-value=" @update:model-value="
(value) => { (value) => {
actionQuestion.config.line_height = Number(value); actionQuestion.config.line_height = value;
} }
" "
> >
@@ -170,7 +170,7 @@
@blur="emit('saveOption')" @blur="emit('saveOption')"
@update:model-value=" @update:model-value="
(value) => { (value) => {
actionQuestion.config.line_height = Number(value); actionQuestion.config.line_height = value;
} }
" "
> >

View File

@@ -12,7 +12,7 @@
@blur="emit('saveOption')" @blur="emit('saveOption')"
@update:model-value=" @update:model-value="
(value) => { (value) => {
actionQuestion.config.min_number = Number(value); actionQuestion.config.min_number = value;
} }
" "
> >
@@ -31,7 +31,7 @@
@blur="emit('saveOption')" @blur="emit('saveOption')"
@update:model-value=" @update:model-value="
(value) => { (value) => {
actionQuestion.config.max_number = Number(value); actionQuestion.config.max_number = value;
} }
" "
> >
@@ -53,7 +53,7 @@
@blur="emit('saveOption')" @blur="emit('saveOption')"
@update:model-value=" @update:model-value="
(value) => { (value) => {
actionQuestion.config.min_size = Number(value); actionQuestion.config.min_size = value;
} }
" "
> >
@@ -72,7 +72,7 @@
@blur="emit('saveOption')" @blur="emit('saveOption')"
@update:model-value=" @update:model-value="
(value) => { (value) => {
actionQuestion.config.max_size = Number(value); actionQuestion.config.max_size = value;
} }
" "
> >

View File

@@ -25,7 +25,7 @@
@blur="emit('saveOption')" @blur="emit('saveOption')"
@update:model-value=" @update:model-value="
(value) => { (value) => {
actionQuestion.config.min_select = Number(value); actionQuestion.config.min_select = value;
} }
" "
> >
@@ -95,7 +95,7 @@
@blur="emit('saveOption')" @blur="emit('saveOption')"
@update:model-value=" @update:model-value="
(value) => { (value) => {
actionQuestion.config.min_select = Number(value); actionQuestion.config.min_select = value;
} }
" "
> >
@@ -114,7 +114,7 @@
@blur="emit('saveOption')" @blur="emit('saveOption')"
@update:model-value=" @update:model-value="
(value) => { (value) => {
actionQuestion.config.max_select = Number(value); actionQuestion.config.max_select = value;
} }
" "
> >

View File

@@ -120,14 +120,14 @@ function emitInfo() {
} }
function intervalChange(value) { function intervalChange(value) {
localConfig.value.score_interval = Number(value) === 0 ? 1 : Number(value); localConfig.value.score_interval = value === 0 ? 1 : value;
setDefaultMax(); setDefaultMax();
emitInfo(); emitInfo();
} }
const minChange = (value) => { const minChange = (value) => {
const oldMax = Number(props.config.min); const oldMax = Number(props.config.min);
localConfig.value.min = Number(value); localConfig.value.min = value;
if (localConfig.value.min > localConfig.value.max) { if (localConfig.value.min > localConfig.value.max) {
localConfig.value.min = oldMax; localConfig.value.min = oldMax;
} }
@@ -137,7 +137,7 @@ const minChange = (value) => {
function maxChange(value) { function maxChange(value) {
const oldMax = Number(props.config.max); const oldMax = Number(props.config.max);
localConfig.value.max = Number(value); localConfig.value.max = value;
if (!isSurplus()) { if (!isSurplus()) {
localConfig.value.max = oldMax; localConfig.value.max = oldMax;
} }

View File

@@ -4,7 +4,12 @@ import MatrixCheckbox from '@/views/Design/components/Questions/MatrixCheckbox.v
import MatrixText from '@/views/Design/components/Questions/MatrixText.vue'; import MatrixText from '@/views/Design/components/Questions/MatrixText.vue';
import MatrixRadio from '@/views/Design/components/Questions/MatrixRadio.vue'; import MatrixRadio from '@/views/Design/components/Questions/MatrixRadio.vue';
const question = defineModel<question>('element', { default: () => ({}), required: false }); const question = defineModel<question>('element', {
type: Object,
default: () => {
return {};
}
});
// eslint-disable-next-line // eslint-disable-next-line
const activeComponent = computed<Component>(() => { const activeComponent = computed<Component>(() => {
switch (question.value.question_type) { switch (question.value.question_type) {
@@ -33,30 +38,33 @@ console.log(rows.value, cols.value);
active: boolean; active: boolean;
}>(); }>();
const emit = defineEmits(['update:element']);
const emitValue = () => { const emitValue = () => {
emit('update:element', element.value); console.log(question.value);
emit('update:element', question.value);
}; };
</script> </script>
<template> <template>
<van-field <van-field
v-model="element.stem" v-model="question.stem"
:label="element.stem" :label="question.stem"
:required="element.config.is_required === 1" :required="question.config.is_required === 1"
label-align="top" label-align="top"
class="contenteditable-question-title"
> >
<template #left-icon> <template #left-icon>
{{ index + 1 }} {{ index + 1 }}
</template> </template>
<!-- 使用 title 插槽来自定义标题 --> <!-- 使用 title 插槽来自定义标题 -->
<template #label> <template #label>
<h1> <contenteditable v-model="question.stem" :active="active" @blur="emitValue" />
<contenteditable v-model="element.stem" :active="active" @blur="emitValue" />
</h1>
</template> </template>
<template #input> <template #input>
<!-- <div style="width: 1000px; overflow: scroll">-->
<Component :is="activeComponent" v-model:rows="rows" v-model:cols="cols" /> <Component :is="activeComponent" v-model:rows="rows" v-model:cols="cols" />
<!-- </div>-->
</template> </template>
<!-- 使用 label 插槽来自定义标题 --> <!-- 使用 label 插槽来自定义标题 -->
<!-- <template #input>--> <!-- <template #input>-->
@@ -104,7 +112,7 @@ const emitValue = () => {
<!-- </template>--> <!-- </template>-->
</van-field> </van-field>
</template> </template>
<style lang="scss"> <style lang="scss" scoped>
.matrix-table { .matrix-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
@@ -116,8 +124,6 @@ const emitValue = () => {
th, th,
td { td {
//min-width: 80px;
//padding: 8px;
border-width: 0 0 1px; border-width: 0 0 1px;
text-align: center; text-align: center;
} }
@@ -126,24 +132,4 @@ const emitValue = () => {
.td-input { .td-input {
text-align: center; text-align: center;
} }
input[type='text'] {
width: 85%;
border: none;
border-radius: 5px;
outline: 1px solid #ddd;
}
input[type='checkbox'] {
border: 1px solid #ddd;
border-radius: 5px;
background-color: red;
outline: none;
}
input[type='radio'] {
border: 1px solid #ddd;
border-radius: 5px;
outline: none;
}
</style> </style>

View File

@@ -26,6 +26,7 @@
<th v-for="(col, colIndex) in cols" :key="colIndex"> <th v-for="(col, colIndex) in cols" :key="colIndex">
<input <input
class="van-icon matrix-checkbox"
type="checkbox" type="checkbox"
:name="`R${rowIndex + 1}`" :name="`R${rowIndex + 1}`"
:value="`${rowIndex + 1}_${colIndex + 1}`" :value="`${rowIndex + 1}_${colIndex + 1}`"
@@ -114,4 +115,38 @@ function handleMatrixRadioChange(row: number, col: number) {
// }; // };
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss">
@import '@/assets/css/theme';
.matrix-table {
text-align: left;
}
.matrix-checkbox {
position: relative;
overflow: hidden;
width: 15px;
height: 15px;
border: 1px solid #f4f4f4;
border-radius: 4px;
outline: none;
cursor: pointer;
appearance: none; /* 去除默认样式 */
//transition: border-color 0.3s;
}
.matrix-checkbox:checked {
border-color: transparent; /* 选中时边框颜色 */
}
.matrix-checkbox:checked::before {
content: '\e728';
position: absolute;
width: 15px;
height: 15px;
background-color: $theme-color; /* 选中颜色 */
color: #fff;
line-height: 15px;
text-align: center;
}
</style>

View File

@@ -24,6 +24,7 @@
<td v-for="(col, colIndex) in cols" :key="colIndex"> <td v-for="(col, colIndex) in cols" :key="colIndex">
<input <input
type="radio" type="radio"
class="van-icon matrix-radio"
:name="`R${rowIndex + 1}`" :name="`R${rowIndex + 1}`"
:value="`${rowIndex + 1}_${colIndex + 1}`" :value="`${rowIndex + 1}_${colIndex + 1}`"
:checked="isOptionChecked(rowIndex, colIndex)" :checked="isOptionChecked(rowIndex, colIndex)"
@@ -97,4 +98,38 @@ const emitValue = () => {
}; };
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss">
@import '@/assets/css/theme';
.matrix-table {
text-align: left;
}
.matrix-radio {
position: relative;
overflow: hidden;
width: 15px;
height: 15px;
border: 1px solid #f4f4f4;
border-radius: 50%;
outline: none;
cursor: pointer;
appearance: none; /* 去除默认样式 */
//transition: border-color 0.3s;
}
.matrix-radio:checked {
border-color: transparent; /* 选中时边框颜色 */
}
.matrix-radio:checked::before {
content: '\e728';
position: absolute;
width: 15px;
height: 15px;
background-color: $theme-color; /* 选中颜色 */
color: #fff;
line-height: 15px;
text-align: center;
}
</style>

View File

@@ -26,6 +26,7 @@
<td v-for="(col, colIndex) in cols" :key="colIndex"> <td v-for="(col, colIndex) in cols" :key="colIndex">
<input <input
type="text" type="text"
placeholder="请输入"
:name="`R${rowIndex + 1}`" :name="`R${rowIndex + 1}`"
:value="getInputValue(rowIndex, colIndex)" :value="getInputValue(rowIndex, colIndex)"
@change="handleMatrixTextChange(rowIndex, colIndex, $event)" @change="handleMatrixTextChange(rowIndex, colIndex, $event)"
@@ -95,4 +96,27 @@ function handleMatrixTextChange(row: number, col: number, e: Event) {
// }; // };
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss">
@import '@/assets/css/theme';
.matrix-table {
text-align: left;
tr,
td {
//width: 200px;
}
}
input {
width: 70%;
padding: 0 5px;
border: none;
border-radius: 4px;
outline: 1px solid #f4f4f4;
&:focus {
outline: 1px solid $theme-color;
}
}
</style>

View File

@@ -350,7 +350,8 @@ import {
saveQuestion, saveQuestion,
snQuestions, snQuestions,
sync, sync,
saveSettings saveSettings,
saveProjectName
} from '@/api/design/index'; } from '@/api/design/index';
import Design from '@/views/Design/Index.vue'; import Design from '@/views/Design/Index.vue';
import { useCounterStore } from '@/stores/counter'; import { useCounterStore } from '@/stores/counter';
@@ -419,6 +420,10 @@ const saveTitle = () => {
sn: route.query.sn, sn: route.query.sn,
title: questionInfo.value.survey.title, title: questionInfo.value.survey.title,
introduction: questionInfo.value.survey.introduction introduction: questionInfo.value.survey.introduction
}).then((res) => {
if (res.data) {
saveProjectName(route.query.sn, { project_name: questionInfo.value.survey.title });
}
}); });
}; };
@@ -580,7 +585,7 @@ const previewQuestion = () => {
router.push({ name: 'preview', query: { ...route.query } }); router.push({ name: 'preview', query: { ...route.query } });
}; };
onMounted(async() => { onMounted(async () => {
await getQuestionDetail(); await getQuestionDetail();
}); });
</script> </script>