Merge branch 'feature/feature-20250331-h5' of https://e.coding.yili.com/yldc/ylst/ylst-survey-h5 into feature/feature-20250331-h5

This commit is contained in:
liu.huiying@ebiz-digits.com
2025-03-18 09:19:06 +08:00
35 changed files with 937 additions and 595 deletions

3
components.d.ts vendored
View File

@@ -2,7 +2,7 @@
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {};
export {}
/* prettier-ignore */
declare module 'vue' {
@@ -32,6 +32,7 @@ declare module 'vue' {
VanGrid: typeof import('vant/es')['Grid']
VanGridItem: typeof import('vant/es')['GridItem']
VanIcon: typeof import('vant/es')['Icon']
VanList: typeof import('vant/es')['List']
VanNavBar: typeof import('vant/es')['NavBar']
VanPicker: typeof import('vant/es')['Picker']
VanPopup: typeof import('vant/es')['Popup']

View File

@@ -22,6 +22,20 @@ export function getSurveysPage(params) {
params
});
}
// 复制问卷
export function copySurveys(sn) {
return request({
url: `/console/surveys/${sn}`,
method: 'post'
});
}
// 复制问卷
export function deleteSurveys(sn) {
return request({
url: `/console/surveys/${sn}`,
method: 'delete'
});
}
export function getListScene(params) {
return request({
url: 'console/h5_scene',
@@ -51,3 +65,11 @@ export function deleteTemplate(sn) {
method: 'delete'
});
}
// 保存为模板
export function saveTemplates(sn, data) {
return request({
url: `/console/surveys/${sn}/templates`,
method: 'post',
data
});
}

View File

@@ -1,6 +1,6 @@
/* color palette from <https://github.com/vuejs/theme> */
:root {
--primary-color: rgb(111, 185, 55);
--primary-color: #71b73c;
--vt-c-white: #fff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
@@ -16,6 +16,9 @@
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
--van-picker-confirm-action-color: var(--primary-color);
--van-calendar-range-edge-background: var(--primary-color);
--van-cascader-active-color: var(--primary-color);
--status-bar-height: 20px;
}
@@ -29,6 +32,7 @@
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
--el-select-input-focus-border-color: var(--primary-color);
}
@media (prefers-color-scheme: dark) {

View File

@@ -1,9 +1,20 @@
// main.scss
/* eslint-disable */
@import 'theme';
/* eslint-disable */
@import 'base';
@import '../../fonts/iconfont.css';
@import '../../fonts/moblie/iconfont.css';
/* eslint-disable */
@import 'public';
/* eslint-disable */
@import '../../fonts/iconfont.css';
/* eslint-disable */
@import '../../fonts/moblie/iconfont.css';
a,
.green {
padding: 3px;

View File

@@ -1,3 +1,4 @@
// public.scss
.van-nav-bar {
z-index: 999;
padding-top: calc(var(--status-bar-height) + 15px) !important;
@@ -13,6 +14,7 @@
.van-popup--bottom.van-popup--round {
border-radius: 10px 10px 0 0 !important;
background: #f2f2f2;
}
.van-radio-group {
@@ -88,7 +90,7 @@
bottom: -2px;
box-sizing: border-box;
width: 100%;
border-bottom: 0.0267rem solid #ebedf0;
border-bottom: 0.0267rem solid #fff;
pointer-events: none;
transform: scaleY(0.5);
}
@@ -115,6 +117,11 @@
color: $theme-color;
}
.van-dialog__confirm,
.van-dialog__confirm:active {
color: $theme-color;
}
.round-group {
overflow: hidden;
margin: 13px 10px;
@@ -125,3 +132,98 @@
border-color: $theme-color;
background-color: $theme-color;
}
.van-grid-item__content {
padding: 8px 10px;
border-radius: 10px;
}
.yl-select {
position: relative;
overflow: hidden;
max-width: 95vw;
}
.el-select__wrapper::after {
content: '\e65b';
position: absolute;
top: 5px;
right: 10px;
font-size: 10px;
}
.el-select__suffix {
display: none;
}
// 自定义下拉箭头样式
.el-select__caret {
color: $theme-color; // 修改箭头颜色
font-size: 18px; // 修改箭头大小
transform: rotate(0deg); // 修改箭头旋转角度默认是0度展开时会旋转180度
}
.el-cascader__tags .el-tag {
background: $theme-color;
color: #fff;
}
.el-tag .el-tag__close {
color: #fff;
}
.el-cascader-node.in-active-path,
.el-cascader-node.is-active,
.el-cascader-node.is-selectable.in-checked-path {
color: $theme-color;
}
.el-checkbox__inner:hover {
border-color: $theme-color;
}
.el-checkbox__input.is-checked .el-checkbox__inner,
.el-checkbox__input.is-indeterminate .el-checkbox__inner {
border-color: $theme-color;
background: $theme-color;
}
.el-input,
.el-input__wrapper {
background: #fafbfc;
}
.el-select__caret.is-reverse {
transform: rotate(180deg); // 展开时旋转180度
}
.el-select {
border: none;
}
.el-select__wrapper {
background: #fafbfc;
}
.el-select-dropdown__item.is-selected {
color: $theme-color;
}
input {
outline-color: transparent;
}
.el-input__wrapper,
.el-select__wrapper {
box-shadow: none;
}
.el-cascader .el-input.is-focus .el-input__wrapper,
.el-input__wrapper.is-focus,
.el-select__wrapper.is-focused,
.el-cascader:not(.is-disabled):hover .el-input__wrapper {
box-shadow: 0 0 0 0.0267rem $theme-color inset;
&::after {
color: $theme-color;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -1,7 +1,7 @@
<template>
<el-select
v-model="selectedValue"
class="yl-select-wrapper"
class="yl-select-wrapper mobilefont"
popper-class="yl-select"
@change="handleChange"
>
@@ -83,10 +83,14 @@ export default defineComponent({
<style scoped lang="scss">
.yl-select-wrapper {
position: relative;
outline-color: transparent;
border-color: transparent;
display: inline-block;
font-size: 16px; /* 增加字体大小 */
line-height: 1.5; /* 增加行高 */
//margin-botton: 10px;
padding: 2px 0;
background: #fafbfc;
border-radius: 12px;
}
.yl-select-label {
@@ -97,9 +101,6 @@ export default defineComponent({
}
</style>
<style>
.yl-select {
overflow: hidden;
max-width: 95vw;
}
<style lang="scss">
@import '@/assets/css/theme';
</style>

View File

@@ -1,9 +1,9 @@
@font-face {
font-family: mobilefont; /* Project id 4841764 */
src:
url('iconfont.woff2?t=1742191207096') format('woff2'),
url('iconfont.woff?t=1742191207096') format('woff'),
url('iconfont.ttf?t=1742191207096') format('truetype');
url('iconfont.woff2?t=1742213166999') format('woff2'),
url('iconfont.woff?t=1742213166999') format('woff'),
url('iconfont.ttf?t=1742213166999') format('truetype');
}
.mobilefont {
@@ -14,6 +14,18 @@
-moz-osx-font-smoothing: grayscale;
}
.mobilefont-xiala::before {
content: '\e65b';
}
.mobilefont-upload::before {
content: '\e682';
}
.mobilefont-shanchu1::before {
content: '\ed1b';
}
.mobilefont-left-long::before {
content: '\e601';
}

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,27 @@
"css_prefix_text": "mobilefont-",
"description": "",
"glyphs": [
{
"icon_id": "6548548",
"name": "下拉",
"font_class": "xiala",
"unicode": "e65b",
"unicode_decimal": 58971
},
{
"icon_id": "15838548",
"name": "upload",
"font_class": "upload",
"unicode": "e682",
"unicode_decimal": 59010
},
{
"icon_id": "24737052",
"name": "删除",
"font_class": "shanchu1",
"unicode": "ed1b",
"unicode_decimal": 60699
},
{
"icon_id": "36915584",
"name": "left-long",

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -121,8 +121,6 @@ const canPlanetPublishMxdAndHotArea = function (data) {
* @returns
*/
const canPlanetPublish3D = function (data) {
console.log(56, data);
{
let canFB = true;
let message = '';
@@ -136,7 +134,9 @@ const canPlanetPublish3D = function (data) {
canFB = false;
qSteams.push(`(${s.title})`);
}
} catch (error) {}
} catch (error) {
// error
}
}
});

View File

@@ -48,7 +48,9 @@ const router = createRouter({
{
path: '/preview',
name: 'preview',
meta: {},
meta: {
title: '预览'
},
component: () => import('@/views/Survey/views/Preview/Index.vue')
},
{

View File

@@ -7,7 +7,7 @@ export default {
options: [
[
{
id: 'b68d45eb-d833-4b25-b0aa-2fde1310e88d',
id: '',
is_fixed: 0,
is_other: 0,
is_remove_other: 0,

View File

@@ -121,6 +121,7 @@
position="bottom"
closeable
close-icon="close"
class="popup-bk"
round
:style="{ minHeight: '50%', maxHeight: '75%' }"
@close="saveLogics"
@@ -320,6 +321,10 @@ const updateConfig = (value) => {
//max-width: 90vw;
}
.popup-bk {
background-color: #fff !important;
}
.question-action-container {
font-size: 20px;

View File

@@ -235,7 +235,8 @@ const textTypeList = [
const emit = defineEmits(['update:modelValue', 'saveOption']);
//
const selectText = (textType) => {
return textTypeList.filter((item) => item.value === textType)[0].text;
let item = textTypeList.filter((item) => item.value === textType)[0];
return item ? item.text : '';
};
const confirm = ({ selectedValues }) => {
actionQuestion.value.config.text_type = Number(selectedValues[0]);

View File

@@ -5,9 +5,9 @@
>
<div class="mt10">
<template v-for="(log, logIndex) in item.logic" :key="logIndex">
<div class=" ">
<div>
<div class="question-before mb10">
<div>
<div class="logics-content">
<!-- 固定分组-->
<!-- if always-->
<yl-select
@@ -282,52 +282,24 @@
</template>
</BeforeMartrixComplation>
</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="addLogicItem(logIndex, item.logic)"-->
<!-- ></van-icon>-->
<!-- </div>-->
</div>
</template>
<!-- 如果是题前隐藏-->
<div v-if="skipType === 1">隐藏本题</div>
<div v-if="skipType === 1" class="tip-text">隐藏本题</div>
<!-- 如果是题后跳转-->
<div v-if="skipType === 0" class="flex align-center space-between">
<div class="jump-text mr10">跳转到</div>
<div v-if="skipType === 0" class="">
<div class="jump-text mr10 tip-text">跳转到</div>
<yl-select
v-model="item.skip_question_index"
class="skip-select"
class="skip-select mt10"
:options="skipOption"
></yl-select>
</div>
</div>
<!-- <van-divider-->
<!-- :dashed="true"-->
<!-- :hairline="false"-->
<!-- content-position="right"-->
<!-- :style="{ borderColor: '#bfbfbf' }"-->
<!-- >-->
<!-- <van-icon name="delete" @click="remoteLogic(index)">删除</van-icon>-->
<!-- </van-divider>-->
<!-- <div-->
<!-- v-if="item.skip_type === skipType && item.question_index === activeQuestion.question_index"-->
<!-- >-->
<!-- del-->
<!-- </div>-->
</div>
</template>
<div class="text-center">
<van-button size="small" @click="addLogic">添加逻辑</van-button>
<div class="text-center mt10">
<van-button class="add-action-btn br12" size="small" @click="addLogic">添加逻辑</van-button>
</div>
</template>
<script setup>
@@ -497,6 +469,13 @@ const logicIf = (value, index) => {
</script>
<style scoped lang="scss">
@import '@/assets/css/theme';
.logics-content {
border-color: transparent;
outline-color: transparent;
}
.flex1 {
flex: 1;
}
@@ -526,6 +505,20 @@ const logicIf = (value, index) => {
width: 50px;
}
.tip-text {
color: #000;
font-weight: 500;
font-size: 14px;
}
.add-action-btn {
height: 35px;
padding: 12px 18px;
border: none;
background: rgba(240, 248, 235, 1);
color: $theme-color;
}
.question-before {
width: 100%;

View File

@@ -64,6 +64,8 @@ const emitValue = () => {
:label="element.stem"
:required="element.config.is_required === 1"
label-align="top"
input-align="center"
class="contenteditable-question-title"
>
<template #left-icon>
{{ index + 1 }}
@@ -79,8 +81,8 @@ const emitValue = () => {
<template #input>
<div class="file-upload-label">
<van-icon name="photo"></van-icon>
<span>上传文件</span>
<van-icon class-prefix="mobilefont" name="upload " />
<span>文件上传</span>
</div>
</template>
</van-field>
@@ -90,12 +92,20 @@ const emitValue = () => {
<style lang="scss" scoped>
.file-upload-label {
display: flex;
gap: 10px;
flex-direction: column;
align-items: center;
width: 100%;
justify-content: center;
//width: 100%;
height: 50px;
padding: 5px;
border: 1px solid #dfdfdf;
border-radius: 3px;
margin: 60px 0;
padding: 16px 18px;
border: 1px dashed #979797;
border-radius: 8px;
color: #666;
//gap: 10px;
font-size: 12px;
text-align: center;
}
</style>

View File

@@ -1,38 +1,38 @@
<script setup lang="ts">
import { useTemplateRef, toRefs } from 'vue';
import { /* useTemplateRef, */ ref, computed, type Component } from 'vue';
import MatrixCheckbox from '@/views/Design/components/Questions/MatrixCheckbox.vue';
import MatrixText from '@/views/Design/components/Questions/MatrixText.vue';
import MatrixRadio from '@/views/Design/components/Questions/MatrixRadio.vue';
const columnLabels = useTemplateRef<HTMLElement[]>('columnLabels');
const question = defineModel<question>('element', { default: () => ({}), required: false });
// eslint-disable-next-line
const activeComponent = computed<Component>(() => {
switch (question.value.question_type) {
case 8:
return MatrixText;
case 9:
return MatrixRadio;
case 10:
return MatrixCheckbox;
}
});
if (question.value?.list) question.value.options = question.value?.list;
// 行标签
const rows = ref(question.value?.options[0] ?? []);
// 列标签
const cols = ref(question.value?.options[1] ?? []);
console.log(rows.value, cols.value);
// const columnLabels = useTemplateRef<HTMLElement[]>('columnLabels');
// 注意, element.options 里面的东西是数组,第一项内容是行标签内容,第二项内容是列标签内容
// 类型 AI 生成 切勿盲目相信,以实际为准
const props = defineProps<{
element: any;
/* const props = */ defineProps<{
index: number;
active: boolean;
}>();
const { element } = toRefs(props);
/**
* input 类型映射,里面自行处理逻辑返回对应类型
* // remark: 填空内容 question_type 8
* // remark: 单选打分矩阵 question_type 9
* // remark: 多选矩阵内容 question_type 10
* @default 'radio'
*/
const tableInputTypeMapping = (/** regx?: any */) => {
switch (element.value.question_type) {
case 8:
return 'text';
case 9:
return 'radio';
case 10:
return 'checkbox';
default:
return 'radio';
}
};
const emit = defineEmits(['update:element']);
const emitValue = () => {
emit('update:element', element.value);
};
@@ -50,67 +50,74 @@ const emitValue = () => {
</template>
<!-- 使用 title 插槽来自定义标题 -->
<template #label>
<contenteditable v-model="element.stem" :active="active" @blur="emitValue"></contenteditable>
<h1>
<contenteditable v-model="element.stem" :active="active" @blur="emitValue" />
</h1>
</template>
<!-- 使用 label 插槽来自定义标题 -->
<template #input>
<table class="martrix-table">
<thead>
<tr>
<!-- 第一行内容为空 -->
<th></th>
<!-- 第二行内容开始填充 -->
<td v-for="col in element.options[1]" :key="col.option" ref="columnLabels">
<contenteditable
v-model="col.option"
:active="active"
@blur="emitValue"
></contenteditable>
</td>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="row in element.options[0]" :key="row.option">
<!-- 编辑状态单次点击出输入框失焦后关闭 -->
<td>
<contenteditable
v-model="row.option"
:active="active"
@blur="emitValue"
></contenteditable>
</td>
<td v-for="col in element.options[1]" :key="col.option" class="td-input">
<!-- 编辑状态单次点击出输入框失焦后关闭 -->
<input :id="col.option" :type="tableInputTypeMapping()" :name="row.option" />
</td>
<td v-if="element.config.is_limit_right_content === 1">
<contenteditable
v-model="row.option_config.limit_right_content"
:active="active"
@blur="emitValue"
>
</contenteditable>
</td>
</tr>
</tbody>
</table>
<Component :is="activeComponent" v-model:rows="rows" v-model:cols="cols" />
</template>
<!-- 使用 label 插槽来自定义标题 -->
<!-- <template #input>-->
<!-- <table class="matrix-table">-->
<!-- <thead>-->
<!-- <tr>-->
<!-- &lt;!&ndash; 第一行内容为空 &ndash;&gt;-->
<!-- <th></th>-->
<!-- &lt;!&ndash; 第二行内容开始填充 &ndash;&gt;-->
<!-- <td v-for="col in element.options[1]" :key="col.option" ref="columnLabels">-->
<!-- <contenteditable-->
<!-- v-model="col.option"-->
<!-- :active="active"-->
<!-- @blur="emitValue"-->
<!-- ></contenteditable>-->
<!-- </td>-->
<!-- </tr>-->
<!-- </thead>-->
<!-- <tbody>-->
<!-- <tr v-for="row in element.options[0]" :key="row.option">-->
<!-- &lt;!&ndash; 编辑状态单次点击出输入框失焦后关闭 &ndash;&gt;-->
<!-- <td>-->
<!-- <contenteditable-->
<!-- v-model="row.option"-->
<!-- :active="active"-->
<!-- @blur="emitValue"-->
<!-- ></contenteditable>-->
<!-- </td>-->
<!-- <td v-for="col in element.options[1]" :key="col.option" class="td-input">-->
<!-- &lt;!&ndash; 编辑状态单次点击出输入框失焦后关闭 &ndash;&gt;-->
<!-- <input :id="col.option" :type="tableInputTypeMapping()" :name="row.option" />-->
<!-- </td>-->
<!-- <td v-if="element.config.is_limit_right_content === 1">-->
<!-- <contenteditable-->
<!-- v-model="row.option_config.limit_right_content"-->
<!-- :active="active"-->
<!-- @blur="emitValue"-->
<!-- >-->
<!-- </contenteditable>-->
<!-- </td>-->
<!-- </tr>-->
<!-- </tbody>-->
<!-- </table>-->
<!-- </template>-->
</van-field>
</template>
<style scoped lang="scss">
.martrix-table {
<style lang="scss">
.matrix-table {
width: 100%;
border-collapse: collapse;
color: black;
& > tbody {
overflow: scroll;
}
th,
td {
padding: 8px;
border: 1px solid #ddd;
//min-width: 80px;
//padding: 8px;
border-width: 0 0 1px;
text-align: center;
}
@@ -130,6 +137,7 @@ input[type='text'] {
input[type='checkbox'] {
border: 1px solid #ddd;
border-radius: 5px;
background-color: red;
outline: none;
}
@@ -138,23 +146,4 @@ input[type='radio'] {
border-radius: 5px;
outline: none;
}
.martrix-table-action {
margin-top: 10px;
.van-icon {
color: lightgreen;
font-size: 12px;
}
.martrix-table-action-tool {
display: flex;
justify-content: flex-end;
& > span {
margin-right: 6px;
font-size: 16px;
}
}
}
</style>

View File

@@ -4,23 +4,27 @@
<tr>
<th></th>
<td v-for="col in cols" :key="col.option">
<contenteditable v-model="col.option" :active="active" @blur="emitValue" />
<!-- 编辑状态单次点击出输入框失焦后关闭 -->
<input
v-if="col.editor"
v-model="col.option"
v-focus
type="text"
@focusout="col.editor = false"
@click="handleRowNameChange(col.option!)"
/>
<span v-else @click="handleRowNameChange(col.option!)" v-html="col.option"></span>
<!-- <input-->
<!-- v-if="col.editor"-->
<!-- v-model="col.option"-->
<!-- v-focus-->
<!-- type="text"-->
<!-- @focusout="col.editor = false"-->
<!-- @click="handleRowNameChange(col.option!)"-->
<!-- />-->
<!-- <span v-else @click="handleRowNameChange(col.option!)" v-html="col.option"></span>-->
</td>
</tr>
</thead>
<tbody>
<tr v-for="(row, rowIndex) in rows" :key="rowIndex">
<th v-html="row.option"></th>
<td v-for="(col, colIndex) in cols" :key="colIndex">
<!-- <th v-html="row.option"></th>-->
<contenteditable v-model="row.option" :active="active" @blur="emitValue" />
<th v-for="(col, colIndex) in cols" :key="colIndex">
<input
type="checkbox"
:name="`R${rowIndex + 1}`"
@@ -28,15 +32,15 @@
:checked="isOptionChecked(rowIndex, colIndex)"
@change="handleMatrixRadioChange(rowIndex, colIndex)"
/>
</td>
</th>
</tr>
</tbody>
</table>
</template>
<script setup lang="ts">
import { defineProps } from 'vue';
import { vFocus } from '@/utils/directives/useVFocus';
// import { defineProps } from 'vue';
// import { vFocus } from '@/utils/directives/useVFocus';
// 记录行和列的索引
const rowRecord = defineModel<number[][]>('rowRecord', { required: false, default: () => [] });
@@ -44,14 +48,16 @@ const rowRecord = defineModel<number[][]>('rowRecord', { required: false, defaul
// 检查 rowRecord 是否存在
// console.log(`rowRecord:`, rowRecord.value);
const active = defineModel('active', { required: false, default: true });
/* const isPreview = */ defineModel<boolean>('isPreview', { required: false, default: false });
defineProps<{
rows: OptionType[];
cols: OptionType[];
}>();
const rows = defineModel('rows', { required: false, default: () => [] });
const cols = defineModel('cols', { required: false, default: () => [] });
// const emits = defineEmits(['update:matrixAnswer', 'update:rowRecord']);
const emitValue = () => {
emit('update:element', element.value);
};
// 判断是否选中
const isOptionChecked = (rowIndex: number, colIndex: number): boolean => {
// [
@@ -64,10 +70,10 @@ const isOptionChecked = (rowIndex: number, colIndex: number): boolean => {
return rowRecord.value[rowIndex].includes(colIndex);
};
const handleRowNameChange = (/* value: string */) => {
// console.log(`row change: ${value}`);
// 你可以在这里添加其他逻辑
};
// const handleRowNameChange = (/* value: string */) => {
// // console.log(`row change: ${value}`);
// // 你可以在这里添加其他逻辑
// };
// 当 matrix radio 选中时,更新 rowRecord 和 matrixAnswer
function handleMatrixRadioChange(row: number, col: number) {
@@ -108,16 +114,4 @@ function handleMatrixRadioChange(row: number, col: number) {
// };
</script>
<style scoped lang="scss">
.matrix-table {
width: 100%;
border-collapse: collapse;
}
.matrix-table th,
.matrix-table td {
padding: 8px;
border: 1px solid #ddd;
text-align: center;
}
</style>
<style scoped lang="scss"></style>

View File

@@ -1,123 +1,127 @@
<script setup lang="ts">
import { useTemplateRef, ref, type Directive } from 'vue';
// import MatrixRadio from '@/views/Design/components/Questions/MatrixRadio.vue';
// import MatrixText from '@/views/Design/components/Questions/MatrixText.vue';
// import MatrixCheckbox from '@/views/Design/components/Questions/MatrixCheckbox.vue';
const columnLabels = useTemplateRef<HTMLElement[]>('columnLabels');
// 注意, element.options 里面的东西是数组,第一项内容是行标签内容,第二项内容是列标签内容
// 类型 AI 生成 切勿盲目相信,以实际为准
const { element, index } = defineProps<{ element: MatrixSurveyQuestion; index: number }>();
const rowRecord = new Array(element.options[0].length);
// matrix 答案
const matrixAnswer = ref({
question_index: index,
answer: {} as any
});
/**
* input 类型映射,里面自行处理逻辑返回对应类型
* // remark: 填空内容 question_type 8
* // remark: 单选打分矩阵 question_type 9
* // remark: 多选矩阵内容 question_type 10
* @default 'radio'
*/
const tableInputTypeMapping = (/** regx?: any */) => {
switch (element.question_type) {
case 8:
return 'text';
case 9:
return 'radio';
case 10:
return 'checkbox';
default:
return 'radio';
}
};
/**
* 自定义指令,用于在元素挂载后自动获取焦点
*/
const vFocus: Directive = {
mounted(el: HTMLInputElement) {
el.focus();
}
};
/**
* row 的数值变动之后,触发的事件
* @param {string} value
* @return {void}
*/
function handleRowNameChange(/* value: string */) {
// if (!value) return;
}
/**
* col 的数值变动之后,触发的事件
*/
function handleColNameChange(rowOption: string, colOption: string, e: any) {
// if (!value) return;
const col = element.options[0].findIndex((option) => {
return option.option === colOption;
});
const row = element.options[1].findIndex((option) => {
return option.option === rowOption;
});
// 不同的 question_type 的 matrix 问卷处理不同的结果
switch (element.question_type) {
case 8: {
// 获取输入框元素
const inputElement = e.target as HTMLInputElement;
// 如果没有获取输入框元素,则直接返回
if (!inputElement) return;
// 将输入框的值保存到 rowRecord 对应位置
rowRecord[col] = e!.target!.value;
// 清空 matrixAnswer 的 answer 属性
matrixAnswer.value.answer = {};
// 遍历所有行选项
element.options[0].forEach((_, rowIndex) => {
// 获取当前行记录
const colOptions = rowRecord[rowIndex];
// 如果当前行记录,则更新 matrixAnswer 的 answer 属性
if (colOptions) {
matrixAnswer.value.answer[`R${rowIndex + 1}_C${col + 1}`] = colOptions;
}
});
break;
}
case 9:
// 将选择的行索引加1后保存到 rowRecord 对应位置
rowRecord[col] = row + 1;
// 清空 matrixAnswer 的 answer 属性
matrixAnswer.value.answer = {};
// 遍历 rowRecord更新 matrixAnswer 的 answer 属性
rowRecord.forEach((row, index) => {
matrixAnswer.value.answer[`${index + 1}_${row}`] = 1;
});
break;
case 10:
// 将选择的行索引加1后添加到 rowRecord 对应位置的数组中
rowRecord[col] = (rowRecord[col] || []).concat(row + 1);
// 清空 matrixAnswer 的 answer 属性
matrixAnswer.value.answer = {};
// 遍历所有行选项
element.options[0].forEach((rowOption, rowIndex) => {
// 获取当前行记录
const colOptions = rowRecord[rowIndex];
// 如果当前行记录,则更新 matrixAnswer 的 answer 属性
if (colOptions) {
colOptions.forEach((col: any) => {
matrixAnswer.value.answer[`R${rowIndex + 1}_C${col}`] = true;
});
}
});
break;
default:
break;
}
}
// import { useTemplateRef, ref, type Directive } from 'vue';
//
// const columnLabels = useTemplateRef<HTMLElement[]>('columnLabels');
//
// // 注意, element.options 里面的东西是数组,第一项内容是行标签内容,第二项内容是列标签内容
// // 类型 AI 生成 切勿盲目相信,以实际为准
// const { element, index } = defineProps<{ element: MatrixSurveyQuestion; index: number }>();
//
// const rowRecord = new Array(element.options[0].length);
// // matrix 答案
// const matrixAnswer = ref({
// question_index: index,
// answer: {} as any
// });
//
// /**
// * input 类型映射,里面自行处理逻辑返回对应类型
// * // remark: 填空内容 question_type 8
// * // remark: 单选打分矩阵 question_type 9
// * // remark: 多选矩阵内容 question_type 10
// * @default 'radio'
// */
// const tableInputTypeMapping = (/** regx?: any */) => {
// switch (element.question_type) {
// case 8:
// return 'text';
// case 9:
// return 'radio';
// case 10:
// return 'checkbox';
// default:
// return 'radio';
// }
// };
//
// /**
// * 自定义指令,用于在元素挂载后自动获取焦点
// */
// const vFocus: Directive = {
// mounted(el: HTMLInputElement) {
// el.focus();
// }
// };
//
// /**
// * row 的数值变动之后,触发的事件
// * @param {string} value
// * @return {void}
// */
// function handleRowNameChange(/* value: string */) {
// // if (!value) return;
// }
//
// /**
// * col 的数值变动之后,触发的事件
// */
// function handleColNameChange(rowOption: string, colOption: string, e: any) {
// // if (!value) return;
// const col = element.options[0].findIndex((option) => {
// return option.option === colOption;
// });
//
// const row = element.options[1].findIndex((option) => {
// return option.option === rowOption;
// });
//
// // 不同的 question_type 的 matrix 问卷处理不同的结果
// switch (element.question_type) {
// case 8: {
// // 获取输入框元素
// const inputElement = e.target as HTMLInputElement;
// // 如果没有获取到输入框元素,则直接返回
// if (!inputElement) return;
// // 将输入框的值保存到 rowRecord 对应位置
// rowRecord[col] = e!.target!.value;
// // 清空 matrixAnswer 的 answer 属性
// matrixAnswer.value.answer = {};
// // 遍历所有行选项
// element.options[0].forEach((_, rowIndex) => {
// // 获取当前行记录
// const colOptions = rowRecord[rowIndex];
// // 如果当前行有记录,则更新 matrixAnswer 的 answer 属性
// if (colOptions) {
// matrixAnswer.value.answer[`R${rowIndex + 1}_C${col + 1}`] = colOptions;
// }
// });
// break;
// }
// case 9:
// // 将选择的行索引加1后保存到 rowRecord 对应位置
// rowRecord[col] = row + 1;
// // 清空 matrixAnswer 的 answer 属性
// matrixAnswer.value.answer = {};
// // 遍历 rowRecord更新 matrixAnswer 的 answer 属性
// rowRecord.forEach((row, index) => {
// matrixAnswer.value.answer[`${index + 1}_${row}`] = 1;
// });
// break;
// case 10:
// // 将选择的行索引加1后添加到 rowRecord 对应位置的数组中
// rowRecord[col] = (rowRecord[col] || []).concat(row + 1);
// // 清空 matrixAnswer 的 answer 属性
// matrixAnswer.value.answer = {};
// // 遍历所有行选项
// element.options[0].forEach((rowOption, rowIndex) => {
// // 获取当前行记录
// const colOptions = rowRecord[rowIndex];
// // 如果当前行有记录,则更新 matrixAnswer 的 answer 属性
// if (colOptions) {
// colOptions.forEach((col: any) => {
// matrixAnswer.value.answer[`R${rowIndex + 1}_C${col}`] = true;
// });
// }
// });
// break;
// default:
// break;
// }
// }
</script>
<template>
@@ -200,7 +204,7 @@ input[type='text'] {
width: 85%;
}
.martrix-table-action {
.matrix-table-action {
margin-top: 10px;
.van-icon {
@@ -208,7 +212,7 @@ input[type='text'] {
font-size: 12px;
}
.martrix-table-action-tool {
.matrix-table-action-tool {
display: flex;
justify-content: flex-end;

View File

@@ -3,26 +3,27 @@
<thead>
<tr>
<th></th>
<td v-for="col in cols" :key="col.option">
<th v-for="col in cols" :key="col.option">
<contenteditable v-model="col.option" :active="active" @blur="emitValue" />
<!-- 编辑状态单次点击出输入框失焦后关闭 -->
<input
v-if="col.editor"
v-model="col.option"
v-focus
type="text"
@focusout="col.editor = false"
@click="handleRowNameChange(col.option!)"
/>
<span v-else @click="handleRowNameChange(col.option!)" v-html="col.option"></span>
</td>
<!-- <input-->
<!-- v-if="col.editor"-->
<!-- v-model="col.option"-->
<!-- v-focus-->
<!-- type="text"-->
<!-- @focusout="col.editor = false"-->
<!-- @click="handleRowNameChange(col.option!)"-->
<!-- />-->
<!-- <span v-else @click="handleRowNameChange(col.option!)" v-html="col.option"></span>-->
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, rowIndex) in rows" :key="rowIndex">
<th v-html="row.option"></th>
<contenteditable v-model="row.option" :active="active" @blur="emitValue" />
<td v-for="(col, colIndex) in cols" :key="colIndex">
<input
type=" "
type="radio"
:name="`R${rowIndex + 1}`"
:value="`${rowIndex + 1}_${colIndex + 1}`"
:checked="isOptionChecked(rowIndex, colIndex)"
@@ -35,8 +36,8 @@
</template>
<script setup lang="ts">
import { defineProps } from 'vue';
import { vFocus } from '@/utils/directives/useVFocus';
// import { defineProps } from 'vue';
// import { vFocus } from '@/utils/directives/useVFocus';
// 记录行和列的索引
const rowRecord = defineModel<number[]>('rowRecord', { required: false, default: () => [] });
@@ -45,11 +46,11 @@ const rowRecord = defineModel<number[]>('rowRecord', { required: false, default:
// console.log(`rowRecord:`, rowRecord.value);
/* const isPreview = */ defineModel<boolean>('isPreview', { required: false, default: false });
defineProps<{
rows: OptionType[];
cols: OptionType[];
}>();
const rows = defineModel('rows', { required: false, default: () => [] });
const cols = defineModel('cols', { required: false, default: () => [] });
const active = defineModel('active', { required: false, default: true });
console.log(rows.value, cols.value);
// const emits = defineEmits(['update:matrixAnswer', 'update:rowRecord']);
// 判断是否选中
@@ -62,10 +63,10 @@ const isOptionChecked = (rowIndex: number, colIndex: number): boolean => {
// return !!matrixAnswer.value?.[key];
};
const handleRowNameChange = (/* value: string */) => {
// console.log(`row change: ${value}`);
// 你可以在这里添加其他逻辑
};
// const handleRowNameChange = (/* value: string */) => {
// // console.log(`row change: ${value}`);
// // 你可以在这里添加其他逻辑
// };
// 当 matrix radio 选中时,更新 rowRecord 和 matrixAnswer
function handleMatrixRadioChange(row: number, col: number) {
@@ -91,18 +92,9 @@ function handleMatrixRadioChange(row: number, col: number) {
// emits('update:matrixAnswer', props.matrixAnswer);
// emits('update:rowRecord', props.rowRecord);
// };
const emitValue = () => {
emit('update:element', element.value);
};
</script>
<style scoped lang="scss">
.matrix-table {
width: 100%;
border-collapse: collapse;
}
.matrix-table th,
.matrix-table td {
padding: 8px;
border: 1px solid #ddd;
text-align: center;
}
</style>
<style scoped lang="scss"></style>

View File

@@ -3,23 +3,26 @@
<thead>
<tr>
<th></th>
<td v-for="col in cols" :key="col.option">
<th v-for="col in cols" :key="col.option">
<contenteditable v-model="col.option" :active="active" @blur="emitValue" />
<!-- 编辑状态单次点击出输入框失焦后关闭 -->
<input
v-if="col.editor"
v-model="col.option"
v-focus
type="text"
@focusout="col.editor = false"
@click="handleRowNameChange(col.option!)"
/>
<span v-else @click="handleRowNameChange(col.option!)" v-html="col.option"></span>
</td>
<!-- <input-->
<!-- v-if="col.editor"-->
<!-- v-model="col.option"-->
<!-- v-focus-->
<!-- type="text"-->
<!-- @focusout="col.editor = false"-->
<!-- @click="handleRowNameChange(col.option!)"-->
<!-- />-->
<!-- <span v-else @click="handleRowNameChange(col.option!)" v-html="col.option"></span>-->
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, rowIndex) in rows" :key="rowIndex">
<th v-html="row.option"></th>
<!-- <th v-html="row.option"></th>-->
<contenteditable v-model="row.option" :active="active" @blur="emitValue" />
<td v-for="(col, colIndex) in cols" :key="colIndex">
<input
type="text"
@@ -34,27 +37,23 @@
</template>
<script setup lang="ts">
import { defineProps } from 'vue';
import { vFocus } from '@/utils/directives/useVFocus';
// 记录行和列的索引
const rowRecord = defineModel<string[][]>('rowRecord', { required: false, default: () => [] });
// const matrixAnswer = defineModel<{ [key: string]: 1 }>('matrixAnswer', { required: false, default: () => ({}) });
// 检查 rowRecord 是否存在
// console.log(`rowRecord:`, rowRecord.value);
const active = defineModel('active', { required: false, default: true });
/* const isPreview = */ defineModel<boolean>('isPreview', { required: false, default: false });
defineProps<{
rows: OptionType[];
cols: OptionType[];
}>();
const rows = defineModel('rows', { required: false, default: () => [] });
const cols = defineModel('cols', { required: false, default: () => [] });
// const emits = defineEmits(['update:matrixAnswer', 'update:rowRecord']);
const handleRowNameChange = (/* value: string */) => {
// console.log(`row change: ${value}`);
// 你可以在这里添加其他逻辑
};
// const handleRowNameChange = (/* value: string */) => {
// console.log(`row change: ${value}`);
// 你可以在这里添加其他逻辑
// };
function getInputValue(row: number, col: number) {
// console.log(`row: ${row}, col: ${col}`);
@@ -96,16 +95,4 @@ function handleMatrixTextChange(row: number, col: number, e: Event) {
// };
</script>
<style scoped lang="scss">
.matrix-table {
width: 100%;
border-collapse: collapse;
}
.matrix-table th,
.matrix-table td {
padding: 8px;
border: 1px solid #ddd;
text-align: center;
}
</style>
<style scoped lang="scss"></style>

View File

@@ -5,6 +5,7 @@
:label="element.stem"
:required="element.config.is_required === 1"
label-align="top"
class="contenteditable-question-title"
>
<template #left-icon>
{{ index + 1 }}
@@ -16,25 +17,24 @@
<div
v-for="(optionItem, optionItemIndex) in isPreview ? element.list : element.options"
:key="optionItemIndex"
class="rate-content"
>
<div
v-for="(item, optionIndex) in isPreview ? optionItem.options : optionItem"
:key="optionIndex"
@click="chooseOption(item)"
>
<RateCharacter
v-model="rate"
:index="optionIndex"
:config="element.config"
@change="handleRateChange"
>
</RateCharacter>
<div class="tips">
<div class="tips mb5">
<p>{{ element.config.prompt_left }}</p>
<p>{{ element.config.prompt_center }}</p>
<p>{{ element.config.prompt_right }}</p>
</div>
<RateCharacter v-model="answerValue" :config="element.config"></RateCharacter>
<RateCharacter
v-model="answerValue"
:config="element.config"
:index="optionIndex"
@change="handleRateChange"
></RateCharacter>
</div>
</div>
</template>
@@ -43,14 +43,11 @@
</template>
<script setup>
import { ref, toRefs } from 'vue';
import { ref } from 'vue';
import RateCharacter from './RateCharacter.vue';
const isPreview = defineModel('isPreview', { default: false, type: Boolean });
const props = defineProps({
element: {
type: Object
},
index: {
type: Number,
default: 0
@@ -66,6 +63,14 @@ const props = defineProps({
// answer 的答案以 矩阵形式存储, 例如 [4,7],上层更新答案的时候也容易
const rates = defineModel('rates', { default: [], type: Array });
const rate = ref(0);
const answerValue = ref();
const element = defineModel('element', {
type: Object,
default: () => {
return {};
}
});
// 不知道的 BUG ,开始的时候不能重置颜色。 故如此
setTimeout(() => {
@@ -92,7 +97,6 @@ function handleRateChange(index, rate) {
localStorage.setItem(props.sn, rate.value);
}
const { element } = toRefs(props);
const chooseId = ref('');
const emit = defineEmits(['update:element']);
const saveStem = () => {
@@ -107,11 +111,33 @@ const chooseOption = (item) => {
<style scoped lang="scss">
.content {
background-color: #fff;
& .rate-content {
width: 100%;
margin-top: 12px;
& .rate-item {
margin-bottom: 8px;
padding: 11px 13px;
border: 1px solid #f4f4f4;
border-radius: 8px;
background: #fafbfc;
}
}
}
.mb10 {
margin-bottom: 10px;
}
.mb5 {
margin-bottom: 5px;
}
.tips {
display: flex;
justify-content: space-between;
color: #b8b8b8;
font-size: 12px;
}
</style>

View File

@@ -18,14 +18,24 @@
></contenteditable>
</template>
<template #input>
<div v-for="(optionItem, optionItemIndex) in element.options" :key="optionItemIndex">
<div
v-for="(optionItem, optionItemIndex) in element.options"
:key="optionItemIndex"
class="rate-content"
>
<div
v-for="(item, optionIndex) in optionItem"
:key="optionIndex"
class="rate-item"
@click="chooseOption(item)"
>
<contenteditable v-model="item.option" :active="active"></contenteditable>
<RateCharacter :config="element.config"></RateCharacter>
<div class="mb5">
<contenteditable v-model="item.option" :active="active"></contenteditable>
</div>
<div class="mb10">
<RateCharacter :config="element.config"></RateCharacter>
</div>
<div class="tips">
<p>{{ element.config.prompt_left }}</p>
<p>{{ element.config.prompt_center }}</p>
@@ -81,8 +91,29 @@ const chooseOption = (item) => {
</script>
<style scoped lang="scss">
.mb10 {
margin-bottom: 10px;
}
.mb5 {
margin-bottom: 5px;
}
.content {
background-color: #fff;
& .rate-content {
width: 100%;
margin-top: 12px;
& .rate-item {
margin-bottom: 8px;
padding: 11px 5px;
border: 1px solid #f4f4f4;
border-radius: 8px;
background: #fafbfc;
}
}
}
.tips {

View File

@@ -107,19 +107,28 @@ watch(
<style scoped lang="scss">
ul {
display: flex;
margin-bottom: 10px;
//margin-bottom: 10px;
}
.rate_item {
margin: 0 3px;
margin-top: 5px;
padding: 0 6px;
border: 1px solid #ddd;
border-radius: 4px;
color: #666;
width: 21px;
height: 21px;
margin: 0 5px 0 0;
border: 1px solid #979797;
border-radius: 5px;
//padding: 0 6px;
color: #000;
//margin-top: 5px;
font-weight: 600;
line-height: 20px;
text-align: center;
}
.active_item {
border-color: #70b936;
background-color: #70b936;
color: #fff;
}

View File

@@ -5,7 +5,7 @@
:label="element.stem"
:required="element.config.is_required === 1"
label-align="top"
class="base-select"
class="base-select contenteditable-question-title"
>
<template #left-icon>
{{ index + 1 }}

View File

@@ -2,7 +2,7 @@
<div class="">
<div class="mark_container">
<!-- <van-row gutter="20">-->
<div v-for="(item, index) in info" :key="index" class="market-item">
<div v-for="(item, index) in info" @click="toDetail(item)" :key="index" class="market-item">
<div class="content">
<div class="title fw-bold fs-14">
<div class="flex align-center">
@@ -46,8 +46,8 @@
import { ref, defineProps, onMounted } from 'vue';
import { deleteTemplate } from '@/api/home/index.js';
import { showDialog, showSuccessToast, showFailToast } from 'vant';
// import contentSvg from './svgs/contentSvg.svg';
// import MarketItemSvg from './svgs/MarketItemSvg.svg';
import { useRouter } from 'vue-router';
const router = useRouter();
const { info } = defineProps({
info: {
type: Object,
@@ -56,6 +56,14 @@ const { info } = defineProps({
}
});
const userInfo = ref({ userName: '' });
const toDetail = (item) => {
router.push({
path: '/create',
query: {
sn: item.sn
}
});
};
const deleteItem = (item) => {
showDialog({
title: '是否确认删除此模板?',

View File

@@ -1,83 +1,114 @@
<template>
<div class="new-survey-container container">
<div v-for="item in survey" :key="item" class="new-survey_item">
<!-- 问卷详情 -->
<div class="survey_item_info">
<div style="position: relative">
<div class="survey_item_info_title">
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<div v-for="item in survey" :key="item" class="new-survey_item">
<!-- 问卷详情 -->
<div class="survey_item_info">
<div style="position: relative">
<div class="survey_item_info_title">
<el-text>
<b>{{ item.project_name }}</b>
</el-text>
<el-text>{{ item.answer_num }}</el-text>
</div>
<div class="survey_item_info_status">
<el-space spacer="|">
<!--报名签到-->
<div>
<span><img src="" alt="" /></span>
<el-text size="small">{{ item.scene_name }}</el-text>
</div>
<!-- 问卷来源 -->
<div>
<span><img src="" alt="" /></span>
<el-text size="small">{{ item.source === 1 ? '移动端' : 'PC端' }}</el-text>
</div>
<!-- 问卷时间 -->
<div>
<span><img src="" alt="" /></span>
<el-text size="small">{{ item.created_at }}</el-text>
</div>
</el-space>
</div>
<div class="survey_item_status">
<img v-if="item.status === 0" src="../../assets/img/publish/edit.png" alt="" />
<img
v-else-if="item.status === 1"
src="../../assets/img/publish/publish.png"
alt=""
/>
<img v-else-if="item.status === 2" src="../../assets/img/publish/end.png" alt="" />
<!-- <span class="survey_item_info_status_text">-{{ item.status_txt }}-</span>-->
</div>
</div>
<!--问卷描述-->
<div v-if="item.remarks" class="survey_item_info_desc">
<el-text>
<b>{{ item.project_name }}</b>
{{ item.remarks }}
</el-text>
<el-text>{{ item.answer_num }}</el-text>
</div>
<div class="survey_item_info_status">
<el-space spacer="|">
<!--报名签到-->
<div>
<span><img src="" alt="" /></span>
<el-text size="small">{{ item.scene_name }}</el-text>
</div>
<!-- 问卷来源 -->
<div>
<span><img src="" alt="" /></span>
<el-text size="small">{{ item.source === 1 ? '移动端' : 'PC端' }}</el-text>
</div>
<!-- 问卷时间 -->
<div>
<span><img src="" alt="" /></span>
<el-text size="small">{{ item.created_at }}</el-text>
</div>
</el-space>
</div>
<div class="survey_item_status">
<span class="survey_item_info_status_text">-{{ item.status_txt }}-</span>
</div>
</div>
<!--问卷描述-->
<div v-if="item.remarks" class="survey_item_info_desc">
<el-text>
{{ item.remarks }}
</el-text>
<!-- action 功能位置 -->
<div class="survey_item_action">
<!-- <el-space direction="horizontal">-->
<div>
<el-button @click="deleteItem(item)"> 删除</el-button>
<el-button @click="copyItem(item)"> 复制</el-button>
<el-button style="border: 2px solid #71b73c" @click="toPreview(item)">
<el-text style="color: #71b73c">预览</el-text>
</el-button>
<el-button color="#6fb937" @click="toPublish(item)">
<el-text style="color: white">开启投放</el-text>
</el-button>
</div>
<el-dropdown placement="top-end" trigger="click" active-color="#ee0a24">
<Io5EllipsisHorizontalSharp />
<template #dropdown>
<el-dropdown-menu
active-color="#ee0a24"
:close-on-click-overlay="false"
:close-on-click-outside="false"
>
<el-dropdown-item @click="editItem(item)">编辑</el-dropdown-item>
<el-dropdown-item @click="saveTemplate(item)">存为模板</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- </el-space>-->
</div>
</div>
<!-- action 功能位置 -->
<div class="survey_item_action">
<!-- <el-space direction="horizontal">-->
<div>
<el-button> 删除</el-button>
<el-button> 复制</el-button>
<el-button style="border: 2px solid #71b73c">
<el-text style="color: #71b73c">预览</el-text>
</el-button>
<el-button color="#6fb937">
<el-text style="color: white">开启投放</el-text>
</el-button>
</div>
<el-dropdown placement="top-end" trigger="click" style="">
<Io5EllipsisHorizontalSharp />
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>编辑</el-dropdown-item>
<el-dropdown-item>存为模板</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- </el-space>-->
</div>
</div>
</van-list>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { getSurveysPage } from '@/api/home/index.js';
import { getSurveysPage, copySurveys, deleteSurveys, saveTemplates } from '@/api/home/index.js';
import { Io5EllipsisHorizontalSharp } from 'vue-icons-plus/io5';
import { showDialog, showConfirmDialog, showFailToast, showSuccessToast, showToast } from 'vant';
import { useRouter } from 'vue-router';
const router = useRouter();
const survey = ref([]);
const total = ref(0);
const loading = ref(false);
const finished = ref(false);
const form = ref({
page: 1,
page: 0,
pageSize: 10
});
const onLoad = () => {
// 异步更新数据
setTimeout(() => {
form.value.page = form.value.page + 1;
fetchSurveys();
}, 500);
};
const fetchSurveys = async () => {
const params = {
page: form.value.page,
@@ -86,7 +117,8 @@ const fetchSurveys = async () => {
};
const res = await getSurveysPage(params);
if (res.data.code === 0) {
survey.value = res.data.data;
survey.value = survey.value.concat(res.data.data);
total.value = res.data.meta.total;
survey.value.forEach((item) => {
const sceneName = JSON.parse(JSON.stringify(item.scene_name));
const nameList = sceneName.split('-');
@@ -99,19 +131,117 @@ const fetchSurveys = async () => {
item.created_at = timeList[0];
}
});
loading.value = false;
// 数据全部加载完成
if (survey.value.length >= total.value) {
finished.value = true;
}
} else {
// Toast()
}
};
const deleteItem = (item) => {
showDialog({
title: `确认删除问卷${item.project_name} ?`,
showCancelButton: true,
confirmButtonColor: '#03B03C'
})
.then(async () => {
const res = await deleteSurveys(item.sn);
if (res.data.message) {
showToast(res.data.message);
} else {
showToast('删除成功!');
}
form.value.page = 1;
await fetchSurveys();
})
.catch(() => {
if (res.data.message) {
showToast(res.data.message);
}
form.value.page = 1;
fetchSurveys();
});
};
const copyItem = (item) => {
showDialog({
title: `确认复制问卷${item.project_name} ?`,
showCancelButton: true,
confirmButtonColor: '#03B03C'
})
.then(async () => {
const res = await copySurveys(item.sn);
if (res.data.code === 200 || res.data.code === 201) {
showSuccessToast('复制成功');
form.value.page = 1;
await fetchSurveys();
} else {
showFailToast(res.data);
}
})
.catch(() => {
// on cancel
});
};
const toPreview = (item) => {
router.push({
path: '/preview',
query: {
sn: item.sn,
name: item.project_name,
source: 0
}
});
};
const toPublish = (item) => {
router.push({
path: '/publish',
query: {
sn: item.sn
}
});
};
const editItem = (item) => {
router.push({
path: '/create',
query: {
sn: item.sn
}
});
};
// 保存为模板
const saveTemplate = async (item) => {
const data = JSON.parse(JSON.stringify(item));
const res = await saveTemplates(item.sn, data);
if (res.data.code === 200 || res.data.code === 201) {
showConfirmDialog({
message: '模板保存成功,请前往模板市场查看!',
showCancelButton: false
})
.then(() => {
form.value.page = 1;
fetchSurveys();
})
.catch(() => {
// on cancel
});
} else {
showFailToast(res.data);
}
};
onMounted(() => {
fetchSurveys();
// fetchSurveys();
});
</script>
<style scoped lang="scss">
@import '@/assets/css/base';
@import '@/assets/css/main';
.el-dropdown-menu__item:not(.is-disabled):focus,
.el-dropdown-menu__item:not(.is-disabled):hover {
background-color: #000000;
}
.new-survey-container {
min-height: calc(100vh - 100px);
@@ -132,8 +262,8 @@ onMounted(() => {
.survey_item_status {
position: absolute;
top: 0;
right: 10px;
top: -40px;
right: -24px;
padding: 28px 13px 19px 7px;
background: url('https://lanhu-oss-2537-2.lanhuapp.com/SketchPngbb370d01215f9cedc28d567c637c011047f83a99fbb5e7ac348ebd0ef0015f32')
100% no-repeat;

View File

@@ -565,7 +565,7 @@ onMounted(async () => {
max-height: 200px;
color: #fff;
::v-deep .content-title {
:deep(.content-title) {
overflow: auto;
width: 230px;
height: 30px;
@@ -574,7 +574,7 @@ onMounted(async () => {
font-size: 18px;
}
::v-deep .introduction {
:deep(.introduction) {
overflow: auto;
width: 230px;
height: 45px;
@@ -617,8 +617,8 @@ onMounted(async () => {
}
}
::v-deep .van-popup--bottom.van-popup--round {
background-color: #f2f2f2 !important;
:deep(.van-popup--bottom.van-popup--round) {
background-color: #f2f2f2;
}
.setting_title {
@@ -730,20 +730,20 @@ onMounted(async () => {
}
.child-group {
& ::v-deep .van-field__label {
& :deep(.van-field__label) {
width: 140px;
color: #bfbfbf;
font-size: 12px;
}
& ::v-deep .van-cell__title {
& :deep(.van-cell__title) {
width: 140px;
color: #bfbfbf;
font-size: 12px;
}
}
::v-deep .van-button--plain.van-button--primary {
:deep(.van-button--plain.van-button--primary) {
padding: 13px 15px;
border: 1px solid #f0f0f0;
border-radius: 10px;
@@ -752,7 +752,7 @@ onMounted(async () => {
font-weight: bold;
}
::v-deep .van-button--plain.van-button--success {
:deep(.van-button--plain.van-button--success) {
padding: 13px 15px;
border: 1px solid #70b937;
border-radius: 10px;

View File

@@ -1,18 +1,5 @@
<template>
<van-nav-bar
left-arrow
style="background-color: var(--primary-color)"
:border="false"
class="preview-nav"
@click-left="$route.go(-1)"
>
<template #left>
<van-icon name="left-long" class-prefix="mobilefont" size="18" style="color: white" />
</template>
<template #title>
<el-text style="color: white">预览</el-text>
</template>
</van-nav-bar>
<layout />
<div ref="scrollbar" class="preview-container">
<!-- <van-nav-bar :title="getDomString(questionsData?.survey?.title)" left-arrow />-->
@@ -545,7 +532,7 @@ import PreviewMatrixText from '@/views/Survey/views/Preview/components/questions
import PreviewNPS from '@/views/Survey/views/Preview/components/questions/PreviewNPS.vue';
import msg from './js/message';
import answerMock from '@/views/Survey/views/Preview/js/mock.js';
import layout from '@/layouts/index.vue';
// const isPreview = ref(true);
// scrollbar
const scrollbar = useTemplateRef('scrollbar');
@@ -688,9 +675,9 @@ async function answer(callback, callbackBeforePage) {
question.error = translatedText.value.ThisIsARequiredQuestion;
}
} else if (
answer
&& questionType === 1
&& Object.keys(answer).findIndex((value) => !answer[value]) !== -1
answer &&
questionType === 1 &&
Object.keys(answer).findIndex((value) => !answer[value]) !== -1
) {
// 单选题
isError = true;
@@ -865,51 +852,51 @@ async function answer(callback, callbackBeforePage) {
const { value } = answer;
const newValue = value.replace(/\n|\r|\r\n/g, '');
switch (config.text_type) {
// 字母
case 3:
isError
= config.include_mark === 1
? !/^[a-zA-Z·~@#¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、`~!@#$%^&*()_\-+=<>?:"{}|,./;'\\[\]]+$/.test(
newValue
) || !newValue.length
: !/^[a-zA-Z]+$/.test(newValue) || !newValue.length;
question.error = isError ? translatedText.value.PleaseEnterEnglishLetters : '';
break;
// 字母
case 3:
isError =
config.include_mark === 1
? !/^[a-zA-Z·~@#¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、`~!@#$%^&*()_\-+=<>?:"{}|,./;'\\[\]]+$/.test(
newValue
) || !newValue.length
: !/^[a-zA-Z]+$/.test(newValue) || !newValue.length;
question.error = isError ? translatedText.value.PleaseEnterEnglishLetters : '';
break;
// 中文
case 4:
isError
= config.include_mark === 1
? !/^(?:[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|[·~@#¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、`~!@#$%^&*()_\-+=<>?:"{}|,./;'\\[\]])+$/.test(
newValue
) || !newValue.length
: !/^(?:[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0])+$/.test(
newValue
) || !newValue.length;
question.error = isError ? translatedText.value.PleaseEnterChineseWords : '';
break;
case 4:
isError =
config.include_mark === 1
? !/^(?:[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|[·~@#¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、`~!@#$%^&*()_\-+=<>?:"{}|,./;'\\[\]])+$/.test(
newValue
) || !newValue.length
: !/^(?:[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0])+$/.test(
newValue
) || !newValue.length;
question.error = isError ? translatedText.value.PleaseEnterChineseWords : '';
break;
// 邮箱
case 5:
isError
= !/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
value
);
question.error = isError ? translatedText.value.PleaseEnterACorrectEmail : '';
break;
case 5:
isError =
!/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
value
);
question.error = isError ? translatedText.value.PleaseEnterACorrectEmail : '';
break;
// 手机号
case 6:
isError = !/^(?:(?:\+|00)86)?1[3-9]\d{9}$/.test(value);
question.error = isError ? translatedText.value.PleaseEnterACorrectPhone : '';
break;
case 6:
isError = !/^(?:(?:\+|00)86)?1[3-9]\d{9}$/.test(value);
question.error = isError ? translatedText.value.PleaseEnterACorrectPhone : '';
break;
// 身份证号
case 7:
isError
= !/^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[1-2]\d|30|31)\d{3}[\dXx]$/.test(
value
);
question.error = isError ? translatedText.value.PleaseEnterACorrectID : '';
break;
default:
break;
case 7:
isError =
!/^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[1-2]\d|30|31)\d{3}[\dXx]$/.test(
value
);
question.error = isError ? translatedText.value.PleaseEnterACorrectID : '';
break;
default:
break;
}
if (!isError && value.length < config.min && ![1, 2].includes(config.text_type)) {
isError = true;
@@ -921,54 +908,54 @@ async function answer(callback, callbackBeforePage) {
Object.keys(answer).forEach((key) => {
const value = answer[key];
switch (config.text_type) {
// 字母
case 3:
if (
!/^[a-zA-Z·~@#¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、`~!@#$%^&*()_\-+=<>?:"{}|,./;'\\[\]]+$/.test(
value
)
) {
question.error = translatedText.value.PleaseEnterEnglishLetters;
}
break;
// 字母
case 3:
if (
!/^[a-zA-Z·~@#¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、`~!@#$%^&*()_\-+=<>?:"{}|,./;'\\[\]]+$/.test(
value
)
) {
question.error = translatedText.value.PleaseEnterEnglishLetters;
}
break;
// 中文
case 4:
if (
!/^(?:[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|[·~@#¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、`~!@#$%^&*()_\-+=<>?:"{}|,./;'\\[\]])+$/.test(
value
)
) {
question.error = translatedText.value.PleaseEnterChineseWords;
}
break;
case 4:
if (
!/^(?:[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|[·~@#¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、`~!@#$%^&*()_\-+=<>?:"{}|,./;'\\[\]])+$/.test(
value
)
) {
question.error = translatedText.value.PleaseEnterChineseWords;
}
break;
// 邮箱
case 5:
if (
!/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
value
)
) {
question.error = translatedText.value.PleaseEnterACorrectEmail;
}
break;
case 5:
if (
!/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
value
)
) {
question.error = translatedText.value.PleaseEnterACorrectEmail;
}
break;
// 手机号
case 6:
if (!/^(?:(?:\+|00)86)?1[3-9]\d{9}$/.test(value)) {
question.error = translatedText.value.PleaseEnterACorrectPhone;
}
break;
case 6:
if (!/^(?:(?:\+|00)86)?1[3-9]\d{9}$/.test(value)) {
question.error = translatedText.value.PleaseEnterACorrectPhone;
}
break;
// 身份证号
case 7:
if (
!/^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[1-2]\d|30|31)\d{3}[\dXx]$/.test(
value
)
) {
question.error = translatedText.value.PleaseEnterACorrectID;
}
break;
default:
break;
case 7:
if (
!/^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|10|11|12)(?:0[1-9]|[1-2]\d|30|31)\d{3}[\dXx]$/.test(
value
)
) {
question.error = translatedText.value.PleaseEnterACorrectID;
}
break;
default:
break;
}
if (!question.error && value.length < config.min && ![1, 2].includes(config.text_type)) {
question.error = translatedText.value.PleaseEnterMoreThanOneCharacters(config.min);
@@ -1045,14 +1032,14 @@ async function answer(callback, callbackBeforePage) {
currentQuestions.forEach((question, index) => {
if (index >= warnStart && index < warnEnd) {
if (repeat.repeat_type) {
question.warning
= translatedText.value.TheAnswerIsRepeatedMoreThanOneTimesPleaseRevise(
question.warning =
translatedText.value.TheAnswerIsRepeatedMoreThanOneTimesPleaseRevise(
repeat.allow_repeat_num,
repeat.repeat_type
);
} else {
question.error
= translatedText.value.TheAnswerIsRepeatedMoreThanOneTimesPleaseRevise(
question.error =
translatedText.value.TheAnswerIsRepeatedMoreThanOneTimesPleaseRevise(
repeat.allow_repeat_num,
repeat.repeat_type
);