refactor(Design): 重构设计页面组件和逻辑

- 修改 QuestionAction 组件,优化题前隐藏和题后跳转的逻辑和界面
- 更新 ChooseQuestion 组件,改进选项索引的处理方式
- 调整 Design/Index 组件,替换 BaseSelect 为 Choice 组件
- 更新 components.d.ts,添加 VanButton 和 VanDialog 的类型声明
This commit is contained in:
陈昱达
2025-03-05 16:14:09 +08:00
parent 5f32c9eeee
commit af00e94f87
12 changed files with 1072 additions and 273 deletions

5
components.d.ts vendored
View File

@@ -2,7 +2,7 @@
// @ts-nocheck // @ts-nocheck
// Generated by unplugin-vue-components // Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399 // Read more: https://github.com/vuejs/core/pull/3399
export {} export {};
/* prettier-ignore */ /* prettier-ignore */
declare module 'vue' { declare module 'vue' {
@@ -10,15 +10,16 @@ declare module 'vue' {
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
VanActionSheet: typeof import('vant/es')['ActionSheet'] VanActionSheet: typeof import('vant/es')['ActionSheet']
VanButton: typeof import('vant/es')['Button']
VanCell: typeof import('vant/es')['Cell'] VanCell: typeof import('vant/es')['Cell']
VanCellGroup: typeof import('vant/es')['CellGroup'] VanCellGroup: typeof import('vant/es')['CellGroup']
VanCheckbox: typeof import('vant/es')['Checkbox'] VanCheckbox: typeof import('vant/es')['Checkbox']
VanCheckboxGroup: typeof import('vant/es')['CheckboxGroup'] VanCheckboxGroup: typeof import('vant/es')['CheckboxGroup']
VanDialog: typeof import('vant/es')['Dialog']
VanDivider: typeof import('vant/es')['Divider'] VanDivider: typeof import('vant/es')['Divider']
VanField: typeof import('vant/es')['Field'] VanField: typeof import('vant/es')['Field']
VanIcon: typeof import('vant/es')['Icon'] VanIcon: typeof import('vant/es')['Icon']
VanPopup: typeof import('vant/es')['Popup'] VanPopup: typeof import('vant/es')['Popup']
VanRate: typeof import('vant/es')['Rate']
VanSearch: typeof import('vant/es')['Search'] VanSearch: typeof import('vant/es')['Search']
VanSwitch: typeof import('vant/es')['Switch'] VanSwitch: typeof import('vant/es')['Switch']
VanTabbar: typeof import('vant/es')['Tabbar'] VanTabbar: typeof import('vant/es')['Tabbar']

View File

@@ -32,6 +32,10 @@ a,
justify-content: flex-start; justify-content: flex-start;
} }
.flex-warp {
flex-wrap: wrap;
}
.space-between { .space-between {
justify-content: space-between; justify-content: space-between;
} }

View File

@@ -6,7 +6,13 @@
:label="item.label" :label="item.label"
:value="item.value" :value="item.value"
:disabled="item.disabled" :disabled="item.disabled"
/> >
<span style="float: left" v-html="item.label"></span>
</el-option>
<template #label="{ label }">
<!-- {{ option }}-->
<div v-html="label"></div>
</template>
</el-select> </el-select>
</template> </template>
@@ -70,7 +76,7 @@ export default defineComponent({
} }
.yl-select { .yl-select {
height: 44px; /* 增加高度以适应触摸 */ height: 35px; /* 增加高度以适应触摸 */
padding: 10px; /* 增加内边距 */ padding: 10px; /* 增加内边距 */
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 5px; border-radius: 5px;

File diff suppressed because it is too large Load Diff

View File

@@ -17,11 +17,11 @@
@get-choose-question-id="getChooseQuestionId" @get-choose-question-id="getChooseQuestionId"
> >
<!-- 选择题 --> <!-- 选择题 -->
<base-select <Choice
v-if="element.question_type === 1" v-if="element.question_type === 1"
:element="element" :element="element"
:active="chooseQuestionId === element.id" :active="chooseQuestionId === element.id"
></base-select> ></Choice>
<!-- 填空题 --> <!-- 填空题 -->
<Completion <Completion
v-if="element.question_type === 4" v-if="element.question_type === 4"
@@ -57,7 +57,7 @@ import { ref, onMounted } from 'vue';
import { useCounterStore } from '@/stores/counter'; import { useCounterStore } from '@/stores/counter';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import Draggable from './components/Draggable.vue'; import Draggable from './components/Draggable.vue';
import BaseSelect from './components/Questions/BaseSelect.vue'; import Choice from './components/Questions/Choice.vue';
import ChooseQuestion from './components/ChooseQuestion.vue'; import ChooseQuestion from './components/ChooseQuestion.vue';
import Paging from './components/Questions/paging/Paging.vue'; import Paging from './components/Questions/paging/Paging.vue';
import Completion from './components/Questions/Completion.vue'; import Completion from './components/Questions/Completion.vue';

View File

@@ -35,12 +35,12 @@
<van-divider></van-divider> <van-divider></van-divider>
<van-cell title="题前隐藏" :border="false" @click="questionSetting('before')"> <van-cell title="题前隐藏" :border="false" @click="questionSetting('before')">
<template #right-icon> <template #right-icon>
<span> {{ getSkipTypeText(0) }} <van-icon name="arrow"></van-icon></span> <span> {{ getSkipTypeText(1) }} <van-icon name="arrow"></van-icon></span>
</template> </template>
</van-cell> </van-cell>
<van-cell title="题后跳转" :border="false" @click="questionSetting('after')"> <van-cell title="题后跳转" :border="false" @click="questionSetting('after')">
<template #right-icon> <template #right-icon>
<span> {{ getSkipTypeText(1) }} <van-icon name="arrow"></van-icon></span> <span> {{ getSkipTypeText(0) }} <van-icon name="arrow"></van-icon></span>
</template> </template>
</van-cell> </van-cell>
<van-divider></van-divider> <van-divider></van-divider>
@@ -54,25 +54,52 @@
<van-popup <van-popup
v-model:show="questionBeforeShow" v-model:show="questionBeforeShow"
title="题前隐藏逻辑" title="题前隐藏逻辑"
label="题前隐藏逻辑"
position="bottom" position="bottom"
:overlay="false"
closeable
close-icon="close"
round round
:style="{ minHeight: '60%' }" :style="{ maxHeight: '75%' }"
> >
<template v-for="(item, logicIndex) in logics" :key="logicIndex"> <div class="mv10">
<div <header><strong>题前隐藏逻辑</strong></header>
v-if="item.skip_type === skipType && item.question_index === activeQuestion.question_index" <question-before
class="mv10 question-setting" v-model="questionsInfo.logics"
> :skipType="skipType"
<template v-for="(log, logIndex) in item.logic" :key="logIndex"> :activeQuestion="activeQuestion"
<yl-select ></question-before>
v-model="log.logic" <van-button>添加逻辑</van-button>
:options="settingIfOption"
class="question-setting-if"
></yl-select>
<yl-select v-model="log.logic" :options="settingIfOption"></yl-select>
</template>
</div> </div>
</template>
<!-- <template v-for="(item, index) in logics" :key="index" class="flex">-->
<!-- <div class="flex space-between mv10">-->
<!-- <div-->
<!-- v-if="-->
<!-- item.skip_type === skipType && item.question_index === activeQuestion.question_index-->
<!-- "-->
<!-- class="question-setting"-->
<!-- >-->
<!-- <template v-for="(log, logIndex) in item.logic" :key="logIndex">-->
<!-- <div>-->
<!-- <yl-select-->
<!-- v-model="log.logic"-->
<!-- :options="settingIfOption"-->
<!-- class="question-setting-if"-->
<!-- ></yl-select>-->
<!-- <yl-select v-model="log.logic" :options="settingIfOption"></yl-select>-->
<!-- </div>-->
<!-- </template>-->
<!-- </div>-->
<!-- <div-->
<!-- v-if="-->
<!-- item.skip_type === skipType && item.question_index === activeQuestion.question_index-->
<!-- "-->
<!-- >-->
<!-- del-->
<!-- </div>-->
<!-- </div>-->
<!-- </template>-->
</van-popup> </van-popup>
</template> </template>
<script setup> <script setup>
@@ -80,18 +107,11 @@ import { showConfirmDialog } from 'vant';
import { ref } from 'vue'; import { ref } from 'vue';
import { useCounterStore } from '@/stores/counter'; import { useCounterStore } from '@/stores/counter';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import QuestionBefore from '@/views/Design/components/ActionCompoents/components/QuestionBefore.vue';
import YlSelect from '@/components/YLSelect.vue';
const store = useCounterStore(); const store = useCounterStore();
const { questionsInfo } = storeToRefs(store); const { questionsInfo } = storeToRefs(store);
const logics = questionsInfo.value.logics; const logics = questionsInfo.value.logics;
const settingIfOption = [
{ label: 'if', value: 'if' },
{ label: 'always', value: 'always' }
];
const props = defineProps({ const props = defineProps({
index: { index: {
type: Number, type: Number,
@@ -187,7 +207,7 @@ const questionSetting = (type) => {
questionBeforeShow.value = true; questionBeforeShow.value = true;
break; break;
} }
skipType.value = type === 'before' ? 0 : 1; skipType.value = type === 'before' ? 1 : 0;
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -205,10 +225,6 @@ const questionSetting = (type) => {
//max-width: 90vw; //max-width: 90vw;
} }
.question-setting-if {
width: 140px;
}
.question-action-container { .question-action-container {
font-size: 20px; font-size: 20px;

View File

@@ -0,0 +1,309 @@
<template>
<template v-for="(item, index) in logics" :key="index">
<div
v-if="item.skip_type === skipType && item.question_index === activeQuestion.question_index"
>
<div class="mt10">
<template v-for="(log, logIndex) in item.logic" :key="logIndex">
<div class="flex align-center space-between">
<div class="question-before mb10">
<!-- if always-->
<yl-select
v-model="log.logic"
:options="logIndex === 0 ? settingIfOptions : settingAndOptions"
class="if mr10"
@change="logicIf($event, index)"
></yl-select>
<!-- 问题-->
<yl-select
v-if="log.logic !== 'always'"
v-model="log.question_index"
class="question"
:options="beforeQuesOptions"
placeholder="请选择问题"
></yl-select>
<!-- 为空 不为空-->
<yl-select
v-if="log.logic !== 'always'"
v-model="log.is_answer"
class="answer mr10 mt10"
:options="answerOptions"
></yl-select>
<!-- 选项 or 分组 -->
<yl-select
v-if="log.logic !== 'always' && log.is_answer !== 0"
v-model="log.is_option_group"
:options="groupOptions"
class="mr10 group mt10"
></yl-select>
<!-- 符号-->
<yl-select
v-if="log.logic !== 'always' && log.is_answer !== 0"
v-model="log.operator"
:options="symbolOptions"
class="mr10 symbol mt10"
></yl-select>
<!-- 分组 option 选项option-->
<div
v-if="log.logic !== 'always' && log.is_answer !== 0"
class="flex align-center space-between option mt10"
>
<yl-select
v-if="log.is_option_group === 0"
v-model="log.option_index"
class="mr10"
:options="changeQuestionIndex(log.question_index, log)"
></yl-select>
<yl-select
v-else
v-model="log.group_index"
class="mr10"
:options="changeQuestionIndex(log.question_index, log)"
></yl-select>
</div>
</div>
<div class="action">
<van-icon
v-if="logIndex !== 0 || log.logic === 'always'"
name="clear"
class="mr10"
@click="deleteLogic(logIndex, item.logic, index)"
></van-icon>
<van-icon
v-if="log.logic !== 'always'"
name="add"
@click="addLogic(logIndex, item.logic)"
></van-icon>
</div>
</div>
</template>
<!-- 如果是题前隐藏-->
<div v-if="skipType === 1">隐藏本题</div>
</div>
<van-divider
:dashed="true"
:hairline="false"
content-position="right"
:style="{ borderColor: '#bfbfbf' }"
>
<van-icon name="delete">删除</van-icon>
</van-divider>
<!-- <div-->
<!-- v-if="item.skip_type === skipType && item.question_index === activeQuestion.question_index"-->
<!-- >-->
<!-- del-->
<!-- </div>-->
</div>
</template>
</template>
<script setup>
import YlSelect from '@/components/YLSelect.vue';
import { ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useCounterStore } from '@/stores/counter';
const store = useCounterStore();
const { questionsInfo } = storeToRefs(store);
const logics = ref(questionsInfo.value.logics);
const questions = questionsInfo.value.questions;
const props = defineProps({
activeQuestion: {
type: Object,
default: () => {
return {};
}
},
skipType: {
type: [Number, String],
default: ''
}
});
// 变量为beforeList questions里面 获取activeQuestion 之前所有的题目
const questionIndex = questions.findIndex((item) => {
return item.id === props.activeQuestion.id;
});
// 当前题目之前的列表
let beforeQuesOptions = [];
if (questionIndex > 0) {
beforeQuesOptions = questions.slice(0, questionIndex).map((item) => {
return {
...item,
label: item.stem,
value: item.question_index
};
});
}
// 题目选项
let optionOptions = [];
const changeQuestionIndex = (value, logicItem) => {
if (!value) {
return [];
}
beforeQuesOptions.map((item) => {
if (item.question_index === value) {
if (logicItem.is_option_group === 0) {
optionOptions = item.options[0].map((optItem) => {
return {
...optItem,
label: optItem.option,
value: optItem.option_index
};
});
} else if (logicItem.is_option_group === 1) {
optionOptions = item.config.option_groups.option_group.map((groupItem) => {
return {
...groupItem,
label: groupItem.title,
value: groupItem.group_index
};
});
} else {
optionOptions = [];
}
}
});
return optionOptions;
};
// 删除 题目设置
const deleteLogic = (logIndex, item, index) => {
item.splice(logIndex, 1);
// 如果小删除 删除后一个都没有了 就删除掉整个数组
if (item.length === 0) {
logics.value.splice(index, 1);
}
};
// 添加题目
const addLogic = (logIndex, item) => {
item.splice(logIndex + 1, 0, {
logic: 'and',
question_index: '',
question_type: '',
is_answer: 1,
operator: '=',
option_index: '',
relation_question_index: 0,
type: 0,
is_option_group: '',
group_index: null
});
};
const groupOptions = [
{
label: '选项',
value: 0
},
{
label: '分组',
value: 1
// disabled: true
}
];
const settingIfOptions = [
{ label: 'if', value: 'if' },
{ label: 'always', value: 'always' }
];
const settingAndOptions = [{ label: 'and', value: 'and' }];
const answerOptions = [
{ label: '为空', value: 0 },
{ label: '不为空', value: 1 }
];
const symbolOptions = [
{
label: '=',
value: '='
},
{
label: '≠',
value: '≠'
},
{
label: '>',
value: '>'
},
{
label: '≥',
value: '>'
},
{
label: '<',
value: '<'
},
{
label: '≤',
value: '≤'
}
];
const logicIf = (value, index) => {
if (value === 'always') {
logics.value[index].logic = [logics.value[index].logic[0]];
}
};
</script>
<style scoped lang="scss">
.flex1 {
flex: 1;
}
.mr10 {
margin-right: 10px;
}
.mt10 {
margin-top: 10px;
}
.mb10 {
margin-bottom: 10px;
}
.mv10 {
margin: 0 5px;
}
.action {
flex: none;
width: 40px;
}
.question-before {
& .if {
flex: none;
width: 90px;
}
& .question {
flex: none;
width: 210px;
}
& .group {
flex: none;
width: 75px;
}
& .answer {
flex: none;
width: 80px;
}
& .symbol {
flex: none;
width: 50px;
}
& .option {
//width: 120px;
width: 100%;
//flex: 1;
}
}
</style>

View File

@@ -79,13 +79,14 @@ const radioAddOption = () => {
option_type: 0, option_type: 0,
limit_right_content: '' limit_right_content: ''
}, },
option_index: 1, option_index: element.value.last_option_index + 1,
parent_id: 0, parent_id: 0,
type: 0, type: 0,
cascade: [], cascade: [],
config: [] config: []
}); });
}); });
element.value.last_option_index += 1;
}; };
// emit 事件 // emit 事件

View File

@@ -11,7 +11,8 @@
class="iconfont active-icon" class="iconfont active-icon"
:style="{ marginRight: isLastPage ? '0' : '16px' }" :style="{ marginRight: isLastPage ? '0' : '16px' }"
@click="activePage" @click="activePage"
>&#xe86c;</i> >&#xe86c;</i
>
<template v-if="!isLastPage"> <template v-if="!isLastPage">
<i class="iconfont moverQues" style="margin-right: 16px">&#xe71b;</i> <i class="iconfont moverQues" style="margin-right: 16px">&#xe71b;</i>
<i class="iconfont" @click="deleteHandle">&#xe6c5;</i> <i class="iconfont" @click="deleteHandle">&#xe6c5;</i>