Merge branch 'feature/feature-20250331-h5' into uat

This commit is contained in:
陈昱达
2025-03-18 19:44:48 +08:00
13 changed files with 160 additions and 90 deletions

4
auto-imports.d.ts vendored
View File

@@ -5,4 +5,6 @@
// Generated by unplugin-auto-import // Generated by unplugin-auto-import
// biome-ignore lint: disable // biome-ignore lint: disable
export {} export {}
declare global {} declare global {
}

15
babel.config.json Normal file
View File

@@ -0,0 +1,15 @@
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3,
"targets": {
"chrome": "58",
"ie": "11"
}
}
]
]
}

5
components.d.ts vendored
View File

@@ -26,11 +26,8 @@ declare module 'vue' {
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']
VanCol: typeof import('vant/es')['Col']
VanDivider: typeof import('vant/es')['Divider'] VanDivider: typeof import('vant/es')['Divider']
VanField: typeof import('vant/es')['Field'] VanField: typeof import('vant/es')['Field']
VanGrid: typeof import('vant/es')['Grid']
VanGridItem: typeof import('vant/es')['GridItem']
VanIcon: typeof import('vant/es')['Icon'] VanIcon: typeof import('vant/es')['Icon']
VanList: typeof import('vant/es')['List'] VanList: typeof import('vant/es')['List']
VanNavBar: typeof import('vant/es')['NavBar'] VanNavBar: typeof import('vant/es')['NavBar']
@@ -38,9 +35,7 @@ declare module 'vue' {
VanPopup: typeof import('vant/es')['Popup'] VanPopup: typeof import('vant/es')['Popup']
VanRadio: typeof import('vant/es')['Radio'] VanRadio: typeof import('vant/es')['Radio']
VanRadioGroup: typeof import('vant/es')['RadioGroup'] VanRadioGroup: typeof import('vant/es')['RadioGroup']
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

@@ -19,7 +19,7 @@
</contenteditable> </contenteditable>
</template> </template>
<template #input> <template #input>
<template v-for="(item, optionIndex) in element.options" :key="item.id"> <template v-for="item /*optionIndex*/ in element.options" :key="item.id">
<van-radio-group v-if="element.question_type === 1" v-model="choiceValue"> <van-radio-group v-if="element.question_type === 1" v-model="choiceValue">
<option-action <option-action
:data="isPreview ? item.options : item" :data="isPreview ? item.options : item"
@@ -61,7 +61,7 @@
<van-checkbox-group v-if="element.question_type === 2" v-model="value" shape="square"> <van-checkbox-group v-if="element.question_type === 2" v-model="value" shape="square">
<option-action <option-action
v-model:data="element.options[optionIndex]" :data="isPreview ? item.options : item"
handle=".moverQues" handle=".moverQues"
:active="active" :active="active"
:question="element" :question="element"
@@ -102,13 +102,12 @@
</template> </template>
<script setup> <script setup>
import OptionAction from '@/views/Design/components/ActionCompoents/OptionAction.vue'; import OptionAction from '@/views/Design/components/ActionCompoents/OptionAction.vue';
import { defineAsyncComponent, ref } from 'vue'; import { defineAsyncComponent } from 'vue';
// 是否是预览 // 是否是预览
const isPreview = defineModel('isPreview', { default: false, type: Boolean }); const isPreview = defineModel('isPreview', { default: false, type: Boolean });
const choiceValue = defineModel('answer', { default: '1', type: String }); const choiceValue = defineModel('answer', { default: '1', type: String });
const value = defineModel('checkboxAnswer', { default: [1, 2], type: Array });
// console.log(`choiceValue.value`, choiceValue.value);
const Contenteditable = defineAsyncComponent(() => import('@/components/contenteditable.vue')); const Contenteditable = defineAsyncComponent(() => import('@/components/contenteditable.vue'));
defineProps({ defineProps({
@@ -121,6 +120,11 @@ defineProps({
default: 0 default: 0
} }
}); });
/**
* 题目
* @type {ModelRef<Object, string, Object, Object>}
*/
const element = defineModel('element', { const element = defineModel('element', {
type: Object, type: Object,
default: () => { default: () => {
@@ -129,7 +133,6 @@ const element = defineModel('element', {
}; };
} }
}); });
const value = ref([]);
const emit = defineEmits(['update:element']); const emit = defineEmits(['update:element']);
const emitValue = () => { const emitValue = () => {
emit('update:element', element.value); emit('update:element', element.value);

View File

@@ -13,12 +13,12 @@ const question = defineModel<question>('element', {
// 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) {
case 8: case 8:
return MatrixText; return MatrixText;
case 9: case 9:
return MatrixRadio; return MatrixRadio;
case 10: case 10:
return MatrixCheckbox; return MatrixCheckbox;
} }
}); });

View File

@@ -30,9 +30,9 @@
<p>{{ element.config.prompt_right }}</p> <p>{{ element.config.prompt_right }}</p>
</div> </div>
<RateCharacter <RateCharacter
v-model:model="value"
:config="element.config" :config="element.config"
:index="optionIndex" :index="optionIndex"
v-model:model="value"
@change="handleRateChange" @change="handleRateChange"
/> />
</div> </div>

View File

@@ -9,7 +9,7 @@ import { showFailToast } from 'vant';
const contentShow = ref(false); const contentShow = ref(false);
const show = ref(false); const show = ref(false);
onMounted(async () => { onMounted(async() => {
if (utils.getSessionStorage('xToken')) { if (utils.getSessionStorage('xToken')) {
const appToken = utils.getSessionStorage('xToken'); const appToken = utils.getSessionStorage('xToken');
getUserInfo(appToken) getUserInfo(appToken)

View File

@@ -87,12 +87,12 @@ const getList = () => {
if (res.data.code === 0) { if (res.data.code === 0) {
if (res.data.data) { if (res.data.data) {
res.data.data.forEach((item) => { res.data.data.forEach((item) => {
if (item.parentCode && item.parentCode === 1) { // if (item.parentCode && item.parentCode === 1) {
surveys.value.push(item); surveys.value.push(item);
} // }
}); });
surveys.value.push({}); // surveys.value.push({});
} }
} }
}); });
@@ -118,8 +118,8 @@ onMounted(() => {
class="survey" class="survey"
@click="createdQuestion(survey)" @click="createdQuestion(survey)"
> >
<img :src="survey.image" alt="" width="40" /> <img :src="survey.h5Image" alt="" width="40" />
<span>{{ survey.title }}</span> <span>{{ survey.h5Title }}</span>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -61,7 +61,7 @@ import endPng from '@/assets/img/publish/end.png';
const survey = ref({ const survey = ref({
project_name: '' project_name: ''
}); });
const fetchSurveys = async () => { const fetchSurveys = async() => {
const params = { const params = {
page: 1, page: 1,
per_page: 10, per_page: 10,

View File

@@ -134,7 +134,7 @@ const onLoad = () => {
fetchSurveys(); fetchSurveys();
}, 500); }, 500);
}; };
const fetchSurveys = async () => { const fetchSurveys = async() => {
const params = { const params = {
page: form.value.page, page: form.value.page,
per_page: form.value.pageSize, per_page: form.value.pageSize,
@@ -172,7 +172,7 @@ const deleteItem = (item) => {
showCancelButton: true, showCancelButton: true,
confirmButtonColor: '#03B03C' confirmButtonColor: '#03B03C'
}) })
.then(async () => { .then(async() => {
const res = await deleteSurveys(item.sn); const res = await deleteSurveys(item.sn);
if (res.data.message) { if (res.data.message) {
showToast(res.data.message); showToast(res.data.message);
@@ -196,7 +196,7 @@ const copyItem = (item) => {
showCancelButton: true, showCancelButton: true,
confirmButtonColor: '#03B03C' confirmButtonColor: '#03B03C'
}) })
.then(async () => { .then(async() => {
const res = await copySurveys(item.sn); const res = await copySurveys(item.sn);
if (res.data.code === 200 || res.data.code === 201) { if (res.data.code === 200 || res.data.code === 201) {
showSuccessToast('复制成功'); showSuccessToast('复制成功');
@@ -237,7 +237,7 @@ const editItem = (item) => {
}); });
}; };
// 保存为模板 // 保存为模板
const saveTemplate = async (item) => { const saveTemplate = async(item) => {
const data = JSON.parse(JSON.stringify(item)); const data = JSON.parse(JSON.stringify(item));
const res = await saveTemplates(item.sn, data); const res = await saveTemplates(item.sn, data);
if (res.data.code === 200 || res.data.code === 201) { if (res.data.code === 200 || res.data.code === 201) {

View File

@@ -146,11 +146,9 @@
<!-- 投放设置--> <!-- 投放设置-->
<van-action-sheet v-model:show="showSetting" title="" style="background-color: #f2f2f2"> <van-action-sheet v-model:show="showSetting" title="" style="background-color: #f2f2f2">
<template #description> <div class="van-action-sheet-title flex flex-start">投放设置</div>
<div class="setting_title flex flex-start">投放设置</div> <van-cell-group :border="false" class="round-group br12">
</template> <van-cell title="每页一题" :border="false" label-align="left">
<van-cell-group :border="false" class="setting_group">
<van-cell title="每页一题" class="setting_block" :border="false" label-align="left">
<template #right-icon> <template #right-icon>
<van-switch <van-switch
v-model="questionInfo.survey.is_one_page_one_question" v-model="questionInfo.survey.is_one_page_one_question"
@@ -162,7 +160,8 @@
></van-switch> ></van-switch>
</template> </template>
</van-cell> </van-cell>
<van-divider></van-divider> </van-cell-group>
<van-cell-group :border="false" class="round-group br12">
<van-cell title="投放数量" class="setting_block" :border="false" label-align="left"> <van-cell title="投放数量" class="setting_block" :border="false" label-align="left">
<template #right-icon> <template #right-icon>
<van-switch <van-switch
@@ -190,8 +189,10 @@
<template #right-icon> </template> <template #right-icon> </template>
</van-field> </van-field>
</van-cell-group> </van-cell-group>
<van-divider></van-divider> </van-cell-group>
<van-cell title="有效期" class="border-top" :border="false" label-align="left">
<van-cell-group :border="false" class="round-group br12">
<van-cell title="有效期" class="border-top" label-align="left">
<template #right-icon> <template #right-icon>
<van-switch <van-switch
v-model="questionInfo.survey.is_time" v-model="questionInfo.survey.is_time"
@@ -225,7 +226,7 @@
</van-field> </van-field>
</van-cell-group> </van-cell-group>
<van-cell title="断点续答" :border="false" label-align="left"> <van-cell title="断点续答" label-align="left">
<template #right-icon> <template #right-icon>
<van-switch <van-switch
v-model="questionInfo.survey.is_breakpoint" v-model="questionInfo.survey.is_breakpoint"
@@ -241,7 +242,6 @@
<van-cell <van-cell
title="企微身份获取(开启后仅支持私有化企业环境作答)" title="企微身份获取(开启后仅支持私有化企业环境作答)"
class="border-bottom" class="border-bottom"
:border="false"
label-align="left" label-align="left"
> >
<template #right-icon> <template #right-icon>
@@ -255,8 +255,10 @@
></van-switch> ></van-switch>
</template> </template>
</van-cell> </van-cell>
<van-divider></van-divider> </van-cell-group>
<van-cell title="IP答题次数限制" class="border-top" :border="false" label-align="left">
<van-cell-group class="round-group br12" :border="false">
<van-cell title="IP答题次数限制" class="border-top" label-align="left">
<template #right-icon> <template #right-icon>
<van-switch <van-switch
v-model="questionInfo.survey.is_ip_number" v-model="questionInfo.survey.is_ip_number"
@@ -268,7 +270,11 @@
></van-switch> ></van-switch>
</template> </template>
</van-cell> </van-cell>
<van-cell-group v-if="questionInfo.survey.is_ip_number === 1" class="child-group"> <van-cell-group
v-if="questionInfo.survey.is_ip_number === 1"
:border="false"
class="child-group"
>
<van-field <van-field
v-model="questionInfo.survey.is_number" v-model="questionInfo.survey.is_number"
label="同一个IP地址只能作答" label="同一个IP地址只能作答"
@@ -306,18 +312,6 @@
<template #right-icon> </template> <template #right-icon> </template>
</van-field> </van-field>
</van-cell-group> </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-cell-group> </van-cell-group>
</van-action-sheet> </van-action-sheet>
@@ -441,14 +435,14 @@ const questionEvent = (item) => {
options: options:
item.json.options.length > 0 item.json.options.length > 0
? item.json.options.map((item) => { ? item.json.options.map((item) => {
return item.map((it) => { return item.map((it) => {
return { return {
...it, ...it,
// 主键生成 // 主键生成
id: uuidv4() id: uuidv4()
}; };
}); });
}) })
: [] : []
}) })
); );
@@ -585,7 +579,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>
@@ -798,6 +792,10 @@ onMounted(async () => {
font-weight: bold; font-weight: bold;
} }
:deep(.van-cell) {
padding: 8px;
}
.survey-action { .survey-action {
position: fixed; position: fixed;
bottom: 0; bottom: 0;

View File

@@ -114,23 +114,22 @@
@next="next" @next="next"
@change-answer="onRelation($event, question)" @change-answer="onRelation($event, question)"
/> />
<!-- &lt;!&ndash; 多选题 &ndash;&gt;--> <!-- 多选题 -->
<!-- <q-checkbox--> <preview-checkbox
<!-- v-else-if="question.question_type === 2"--> v-else-if="question.question_type === 2"
<!-- :list="question.list"--> v-model:answer="question.answer"
<!-- :config="question.config"--> v-model:answerIndex="question.answerIndex"
<!-- v-model:answer="question.answer"--> :list="question.list"
<!-- v-model:answerIndex="question.answerIndex"--> :config="question.config"
<!-- :hideOptions="question.hideOptions"--> :hideOptions="question.hideOptions"
<!-- @changeAnswer="onRelation($event, question)"--> :stem="question.stem"
<!-- :stem="question.stem"--> :answerSn="questionsData.answer.sn"
<!-- @previous="previous"--> :answerSurveySn="questionsData.answer.survey_sn"
<!-- @next="next"--> :question="question"
<!-- isMobile--> @change-answer="onRelation($event, question)"
<!-- :answerSn="questionsData.answer.sn"--> @previous="previous"
<!-- :answerSurveySn="questionsData.answer.survey_sn"--> @next="next"
<!-- :question="question"--> />
<!-- />-->
<!-- &lt;!&ndash; 级联题 &ndash;&gt;--> <!-- &lt;!&ndash; 级联题 &ndash;&gt;-->
<!-- <q-cascader--> <!-- <q-cascader-->
<!-- v-else-if="question.question_type === 3"--> <!-- v-else-if="question.question_type === 3"-->
@@ -540,6 +539,7 @@ import layout from '@/layouts/index.vue';
// hooks file // hooks file
import icon from '@/assets/img/create-right-back.png'; import icon from '@/assets/img/create-right-back.png';
import PreviewCheckbox from '@/views/Survey/views/Preview/components/questions/PreviewCheckbox.vue';
// scrollbar // scrollbar
const scrollbar = useTemplateRef('scrollbar'); const scrollbar = useTemplateRef('scrollbar');
const questionStore = useQuestionStore(); const questionStore = useQuestionStore();
@@ -559,7 +559,7 @@ const {
translatedText translatedText
} = storeToRefs(questionStore); } = storeToRefs(questionStore);
// console.log(`questionsData`, questionsData.value); // console.log(`questionsData`, questions.value);
const props = defineProps({ const props = defineProps({
isTemplate: { isTemplate: {
type: Boolean, type: Boolean,
@@ -597,13 +597,6 @@ async function getQuestions() {
questionsData.value = data; questionsData.value = data;
} }
// 获取 DOM 的内容
// function getDomString (htmlString) {
// if (typeof htmlString !== 'string') return;
// const match = htmlString.match(/<.*>(.*?)<\/.*>/);
// if (match) return match[1];
// }
// 上一页 // 上一页
async function previous() { async function previous() {
if (prevLoading.value || loading.value) { if (prevLoading.value || loading.value) {

View File

@@ -0,0 +1,64 @@
<template>
<choice
v-model:checkboxAnswer="choiceValue"
:element="question"
:index="answerIndex"
:is-preview="true"
/>
</template>
<script setup lang="ts">
import { watch, ref } from 'vue';
import Choice from '@/views/Design/components/Questions/Choice.vue';
type answerType = {
[key: string]: string;
};
/**
* answer 格式
* {
* 1:"1"
* 2:"1"
* }
*/
// 预览新增 v-model
const answer = defineModel<answerType>('answer', { default: undefined });
const answerIndex = defineModel<number>('answerIndex');
// const stem = defineModel<string>('stem', { default: '' });
const question = defineModel<question>('question', { default: { config: { is_required: false } } });
// const list = defineModel<questionsList[]>('list', { default: [[]] });
initData();
function initData() {
if (question.value.list.length) {
question.value.options = question.value.list;
question.value.questions = question.value.list;
}
if (question.value.list.length) question.value.questions = question.value.list;
}
const choiceValue = ref<string[]>(parseAnswer(answer.value));
// // 预览新增 emit ['changeAnswer', 'previous', 'next']
const emit = defineEmits(['update:element', 'changeAnswer', 'previous', 'next', 'update:element']);
/**
* 解析答案
*/
function parseAnswer(answer: answerType) {
if (!answer) return [];
return Object.keys(answer);
}
watch(
() => choiceValue.value,
() => {
const res: answerType = {};
choiceValue.value.forEach((key) => {
res[key] = '1';
});
answer.value = res;
console.log(res);
emit('changeAnswer', res);
}
);
</script>