feat(home): 新增创建问卷功能并优化首页样式

- 新增 CreateQuestion 组件,用于创建新问卷
- 在首页添加创建问卷入口
- 优化首页样式,调整背景图显示方式
- 在 QuestionAction 组件中添加题目移动逻辑
This commit is contained in:
陈昱达
2025-03-19 11:19:34 +08:00
parent 3cc345c21f
commit b6cb171e26
5 changed files with 269 additions and 33 deletions

3
components.d.ts vendored
View File

@@ -11,7 +11,6 @@ declare module 'vue' {
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElOption: typeof import('element-plus/es')['ElOption'] ElOption: typeof import('element-plus/es')['ElOption']
ElSelect: typeof import('element-plus/es')['ElSelect'] ElSelect: typeof import('element-plus/es')['ElSelect']
ElText: typeof import('element-plus/es')['ElText']
RichText: typeof import('./src/components/RichText.vue')['default'] RichText: typeof import('./src/components/RichText.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
@@ -35,6 +34,8 @@ declare module 'vue' {
VanRow: typeof import('vant/es')['Row'] VanRow: typeof import('vant/es')['Row']
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']
VanTabs: typeof import('vant/es')['Tabs']
YLCascader: typeof import('./src/components/YLCascader.vue')['default'] YLCascader: typeof import('./src/components/YLCascader.vue')['default']
YLInput: typeof import('./src/components/YLInput.vue')['default'] YLInput: typeof import('./src/components/YLInput.vue')['default']
YLPicker: typeof import('./src/components/YLPicker.vue')['default'] YLPicker: typeof import('./src/components/YLPicker.vue')['default']

View File

@@ -144,7 +144,7 @@
<script setup> <script setup>
import checkboxQuestionAction from './components/QuestionItemAction/CheckboxQuestionAction.vue'; import checkboxQuestionAction from './components/QuestionItemAction/CheckboxQuestionAction.vue';
import { showConfirmDialog } from 'vant'; import { showConfirmDialog } from 'vant';
import { toRefs, ref } from 'vue'; import { toRefs, ref, watch } from 'vue';
import QuestionBefore from '@/views/Design/components/ActionCompoents/components/QuestionItemAction/QuestionBefore.vue'; import QuestionBefore from '@/views/Design/components/ActionCompoents/components/QuestionItemAction/QuestionBefore.vue';
import RateQuestionAction from './components/QuestionItemAction/RateQuestionAction.vue'; import RateQuestionAction from './components/QuestionItemAction/RateQuestionAction.vue';
import CompletionQuestionAction from '@/views/Design/components/ActionCompoents/components/QuestionItemAction/CompletionQuestionAction.vue'; import CompletionQuestionAction from '@/views/Design/components/ActionCompoents/components/QuestionItemAction/CompletionQuestionAction.vue';
@@ -171,15 +171,15 @@ const props = defineProps({
questions: { questions: {
type: Array, type: Array,
default: () => [] default: () => []
},
questionIndex: {
type: Number,
default: 0
} }
}); });
const { questionsInfo } = toRefs(props); const { questionsInfo } = toRefs(props);
const logics = questionsInfo.value.logics; const logics = questionsInfo.value.logics;
const questions = ref(props.questions); const questions = ref(props.questions);
const questionIndex = defineModel('questionIndex', {
type: Number,
default: 0
});
// emit // emit
const emit = defineEmits(['move', 'copy', 'delete', 'setting', 'logics']); const emit = defineEmits(['move', 'copy', 'delete', 'setting', 'logics']);
@@ -197,11 +197,11 @@ const activeQuestion = ref(props.data);
const show = ref(false); const show = ref(false);
const questionShow = ref(false); const questionShow = ref(false);
const questionBeforeShow = ref(false); const questionBeforeShow = ref(false);
const actions = [ const actions = ref([
{ name: '上移题目', action: 'up' }, { name: '上移题目', action: 'up' },
{ name: '下移题目', action: 'down' }, { name: '下移题目', action: 'down' },
{ name: '复制题目', action: 'copy' } { name: '复制题目', action: 'copy' }
]; ]);
const deleteQuestion = () => { const deleteQuestion = () => {
showConfirmDialog({ showConfirmDialog({
title: '提示', title: '提示',
@@ -209,14 +209,48 @@ const deleteQuestion = () => {
}) })
.then(() => { .then(() => {
// on confirm // on confirm
const index = props.questionIndex; const index = questionIndex.value;
questions.value.splice(props.questionIndex, 1); questions.value.splice(questionIndex.value, 1);
emit('delete', index); emit('delete', index);
}) })
.catch(() => { .catch(() => {
// on cancel // on cancel
}); });
}; };
/**
* @name 监听当前题目是否可以移动
* @created_date 2025/3/19
* @description
**/
watch(
() => questionIndex.value,
(newVal) => {
if (newVal === questions.value.length - 1) {
actions.value = [
{ name: '上移题目', action: 'up' },
{ name: '下移题目', action: 'down', disabled: true },
{ name: '复制题目', action: 'copy' }
];
}
if (newVal === 0) {
actions.value = [
{ name: '上移题目', action: 'up', disabled: true },
{ name: '下移题目', action: 'down' },
{ name: '复制题目', action: 'copy' }
];
}
if (newVal > 0 && newVal < questions.value.length - 1) {
actions.value = [
{ name: '上移题目', action: 'up' },
{ name: '下移题目', action: 'down' },
{ name: '复制题目', action: 'copy' }
];
}
},
{
immediate: true
}
);
// 打开题目弹窗 // 打开题目弹窗
const openQuestionActionModel = () => { const openQuestionActionModel = () => {
@@ -229,30 +263,38 @@ const openQuestionSettingModel = () => {
// 题目上下移动 // 题目上下移动
const questionMove = (action) => { const questionMove = (action) => {
if (action.action === 'down') { if (action.action === 'down') {
if (props.questionIndex === questions.value.length - 1) { if (questionIndex.value === questions.value.length - 1) {
return; return;
} }
const temp = questions.value[props.questionIndex]; const temp = questions.value[questionIndex.value];
questions.value.splice(props.questionIndex, 1); questions.value.splice(questionIndex.value, 1);
questions.value.splice(props.questionIndex + 1, 0, temp); questions.value.splice(questionIndex.value + 1, 0, temp);
emit('move', 'down'); emit('move', 'down');
questionIndex.value += 1;
} else if (action.action === 'up') { } else if (action.action === 'up') {
if (props.questionIndex === 0) { if (questionIndex.value === 0) {
actions.value = [
{ name: '上移题目', action: 'up', disabled: true },
{ name: '下移题目', action: 'down' },
{ name: '复制题目', action: 'copy' }
];
questionShow.value = false;
return; return;
} }
const temp = questions.value[props.questionIndex]; const temp = questions.value[questionIndex.value];
questions.value.splice(props.questionIndex, 1); questions.value.splice(questionIndex.value, 1);
questions.value.splice(props.questionIndex - 1, 0, temp); questions.value.splice(questionIndex.value - 1, 0, temp);
emit('move', 'up'); emit('move', 'up');
} else { } else {
// 复制 题目 生成新的id 更新最新的 last index // 复制 题目 生成新的id 更新最新的 last index
const temp = JSON.parse(JSON.stringify(questions.value[props.questionIndex])); const temp = JSON.parse(JSON.stringify(questions.value[questionIndex.value]));
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(questionIndex.value + 1, 0, newQuestion);
questionsInfo.value.survey.last_question_index += 1; questionsInfo.value.survey.last_question_index += 1;
emit('copy', newQuestion); emit('copy', newQuestion);
questionShow.value = false; questionShow.value = false;
@@ -265,8 +307,8 @@ const getSkipTypeText = (skipType) => {
const ls = []; const ls = [];
logics.map((item) => { logics.map((item) => {
if ( if (
item.skip_type === skipType item.skip_type === skipType &&
&& item.question_index === activeQuestion.value.question_index item.question_index === activeQuestion.value.question_index
) { ) {
ls.push(item); ls.push(item);
} }
@@ -282,13 +324,13 @@ const getSkipTypeText = (skipType) => {
const questionSetting = (type) => { const questionSetting = (type) => {
switch (type) { switch (type) {
case 'before': case 'before':
questionBeforeShow.value = true; questionBeforeShow.value = true;
break; break;
case 'after': case 'after':
questionBeforeShow.value = true; questionBeforeShow.value = true;
break; break;
} }
skipType.value = type === 'before' ? 1 : 0; skipType.value = type === 'before' ? 1 : 0;
}; };

View File

@@ -2,6 +2,7 @@
import LastSurvey from './components/LastSurvey/Index.vue'; import LastSurvey from './components/LastSurvey/Index.vue';
import Market from './components/Market/Index.vue'; import Market from './components/Market/Index.vue';
import CreateSurvey from './components/CreateSurvey/Index.vue'; import CreateSurvey from './components/CreateSurvey/Index.vue';
import CreateQuestion from './components/CreateSurvey/CreateQuestion.vue';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import utils from '@/assets/js/common'; import utils from '@/assets/js/common';
import { getUserInfo } from '@/api/common/index.js'; import { getUserInfo } from '@/api/common/index.js';
@@ -10,7 +11,7 @@ import appBridge from '@/assets/js/appBridge';
const contentShow = ref(false); const contentShow = ref(false);
const show = ref(false); const show = ref(false);
onMounted(async() => { onMounted(async () => {
if (appBridge.isInReactNative()) { if (appBridge.isInReactNative()) {
const appToken = utils.getSessionStorage('xToken'); const appToken = utils.getSessionStorage('xToken');
getUserInfo(appToken) getUserInfo(appToken)
@@ -60,7 +61,8 @@ function create() {
</div> </div>
</div> </div>
<van-popup v-model:show="show" round closeable position="bottom"> <van-popup v-model:show="show" round closeable position="bottom">
<CreateSurvey :createdNewPage="true"></CreateSurvey> <!-- <CreateSurvey :createdNewPage="true"></CreateSurvey>-->
<create-question :createdNewPage="true"></create-question>
</van-popup> </van-popup>
</template> </template>
@@ -96,7 +98,7 @@ function create() {
//background: linear-gradient(0deg, #f5f5f5 0%, #f5f5f5 84%, #a5d380 100%); //background: linear-gradient(0deg, #f5f5f5 0%, #f5f5f5 84%, #a5d380 100%);
&> :first-child { & > :first-child {
position: relative; position: relative;
z-index: 10; z-index: 10;
@@ -107,7 +109,7 @@ function create() {
//justify-content: space-around; //justify-content: space-around;
border-radius: 16px; border-radius: 16px;
&>div { & > div {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 50px; width: 50px;

View File

@@ -0,0 +1,191 @@
<script setup>
import { ref, onMounted } from 'vue';
import { consoleSurveys, getQuestionList, useTemplate } from '@/api/home/index.js';
import { snQuestions, saveQuestions } from '@/api/design/index.js';
import { useRouter } from 'vue-router';
import { useCounterStore } from '@/stores/counter';
import { storeToRefs } from 'pinia';
// 获取 Store 实例
const counterStore = useCounterStore();
const store = storeToRefs(counterStore);
const router = useRouter();
const surveys = ref([]);
const createdNewPage = defineModel('createdNewPage', {
type: Boolean,
default: false
});
const createdQuestion = (item) => {
const query = {
group_id: 0,
source: 1,
project_name: `${item.title}问卷 `,
remarks: '为优化活动服务品质,烦请完成问卷,感谢配合',
scene_code: item.parentCode,
scene_code_info: item.code,
// 很迷茫 模板新增 tag 空数组 非模板 就是k
tags: ''
};
if (createdNewPage.value) {
query.scene_code = item.parentCode;
query.tags = '';
} else {
if (item.sn) {
query.scene_code = null;
query.tags = [];
}
}
// 如果放在了底部 当作新增组件
if (createdNewPage.value) {
consoleSurveys(query).then((res) => {
if (res.data) {
createdApx(res);
}
});
} else {
if (item.sn) {
useTemplate(item.sn, query).then((temp) => {
if (temp.data) {
createdApx(temp);
}
});
} else {
consoleSurveys(query).then((res) => {
if (res.data) {
createdApx(res);
}
});
}
}
};
const createdApx = (res) => {
snQuestions({ sn: res.data.data.sn }).then((ques) => {
if (ques.data) {
ques.data.data.survey.introduction = `<p>为优化活动服务品质,烦请完成问卷,感谢配合!您的反馈至关重要!(此提示语为默认提示语,您可选择自行输入本问卷的提示语)</p>`;
store.questionsInfo.value = ques.data.data;
saveQuestions({
sn: res.data.data.sn,
introduction: ques.data.data.survey.introduction,
title: ques.data.data.survey.title
}).then(() => {
router.push({
path: '/create',
query: {
sn: res.data.data.sn
}
});
});
}
});
};
// 添加获取问卷列表的方法
const getList = () => {
getQuestionList().then((res) => {
if (res.data.code === 0) {
if (res.data.data) {
res.data.data.forEach((item) => {
// if (item.parentCode && item.parentCode === 1) {
surveys.value.push(item);
// }
});
// surveys.value.push({});
}
}
});
};
// 在组件挂载时调用
onMounted(() => {
getList();
});
</script>
<template>
<!-- <div class="home-pen">-->
<!-- <img :src="homePen" alt="" />-->
<!-- </div>-->
<div class="create_survey">
<div class="create_survey_title" style="color: #000; text-align: left">新建问卷</div>
<div class="flex align-center space-between warp">
<div
v-for="survey in surveys"
:key="survey.title"
span="6"
class="survey"
@click="createdQuestion(survey)"
>
<img :src="survey.h5Image" alt="" width="40" />
<span>{{ survey.h5Title }}</span>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.home-pen {
//height: 200px;
position: absolute;
top: -10px;
left: 0;
z-index: 8;
//width: 100%;
background: #fff;
img {
width: 100%;
//height: 200px;
}
}
.create_survey {
overflow: hidden;
//border-radius: 16px;
background: #fff;
//padding: 15px;
color: #000;
.create_survey_title {
margin: 16px;
color: #000;
font-weight: bold;
font-size: 15px;
font-family: PingFangSC, 'PingFang SC';
}
& .warp {
flex-wrap: wrap;
}
}
.survey {
display: flex;
//margin-right: 30px;
flex-direction: column;
align-items: center;
//flex: 1;
width: 25%;
//margin: 0 16px;
text-align: center;
//justify-content: space-around;
span {
margin-top: 8px;
margin-bottom: 18px;
color: #000;
font-weight: 400;
font-size: 0.34rem;
}
}
</style>

View File

@@ -148,7 +148,7 @@ onMounted(() => {
border-radius: 16px; border-radius: 16px;
background-image: url('@/assets/img/home/home-back.png'); background-image: url('@/assets/img/home/home-back.png');
background-position: center; /* 确保图片居中显示 */ background-position: center; /* 确保图片居中显示 */
background-size: contain; /* 或者使用具体的尺寸,如 100% 100% */ background-size: cover; /* 或者使用具体的尺寸,如 100% 100% */
background-repeat: no-repeat; background-repeat: no-repeat;
//padding: 15px; //padding: 15px;