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:
@@ -1,5 +1,5 @@
|
||||
# .env.development
|
||||
VITE_APP_BASEURL=https://yls-api-uat.dctest.digitalyili.com/
|
||||
VITE_APP_BASEURL=https://yls-api-uat.dctest.digitalyili.com
|
||||
VITE_APP_ENV=development
|
||||
VITE_APP_CURRENTMODE=dev
|
||||
VITE_APP_BASEOSS=https://diaoyan-files.automark.cc
|
||||
|
||||
2
.env.uat
2
.env.uat
@@ -1,5 +1,5 @@
|
||||
# .env.development
|
||||
VITE_APP_BASEURL=https://yls-api-uat.dctest.digitalyili.com/api/
|
||||
VITE_APP_BASEURL=https://yls-api-uat.dctest.digitalyili.com
|
||||
VITE_APP_ENV=uat
|
||||
VITE_APP_CURRENTMODE=uat
|
||||
VITE_APP_BASEOSS=https://diaoyan-files.automark.cc
|
||||
|
||||
3
components.d.ts
vendored
3
components.d.ts
vendored
@@ -11,7 +11,6 @@ declare module 'vue' {
|
||||
RichText: typeof import('./src/components/RichText.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
'Van-': typeof import('vant/es')['-']
|
||||
VanActionSheet: typeof import('vant/es')['ActionSheet']
|
||||
VanButton: typeof import('vant/es')['Button']
|
||||
VanCell: typeof import('vant/es')['Cell']
|
||||
@@ -20,6 +19,7 @@ declare module 'vue' {
|
||||
VanCheckboxGroup: typeof import('vant/es')['CheckboxGroup']
|
||||
VanCol: typeof import('vant/es')['Col']
|
||||
VanDivider: typeof import('vant/es')['Divider']
|
||||
VanFeild: typeof import('vant/es')['Feild']
|
||||
VanField: typeof import('vant/es')['Field']
|
||||
VanGrid: typeof import('vant/es')['Grid']
|
||||
VanGridItem: typeof import('vant/es')['GridItem']
|
||||
@@ -35,6 +35,7 @@ declare module 'vue' {
|
||||
VanSwitch: typeof import('vant/es')['Switch']
|
||||
VanTabbar: typeof import('vant/es')['Tabbar']
|
||||
VanTabbarItem: typeof import('vant/es')['TabbarItem']
|
||||
YLCascader: typeof import('./src/components/YLCascader.vue')['default']
|
||||
YLPicker: typeof import('./src/components/YLPicker.vue')['default']
|
||||
YLSelect: typeof import('./src/components/YLSelect.vue')['default']
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
"lint": "npx eslint -c ./node_modules/@yl/yili-fe-lint-config/eslintrc.vue3.js \"src/**/*.{js,ts,tsx,vue,html}\" --fix",
|
||||
"stylelint": "npx stylelint -c ./node_modules/@yl/yili-fe-lint-config/stylelintrc.js \"**/*.{css,scss,sass,less,vue,html}\" --fix"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-linux-x64-musl": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"axios": "^1.8.2",
|
||||
|
||||
@@ -11,6 +11,10 @@ a,
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
.ml10 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
a:hover {
|
||||
background-color: hsla(160deg, 100%, 37%, 0.2);
|
||||
|
||||
57
src/components/YLCascader.vue
Normal file
57
src/components/YLCascader.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import 'element-plus/dist/index.css';
|
||||
import { ElCascader } from 'element-plus';
|
||||
|
||||
const props = defineProps({
|
||||
options: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
const cascader = ref(props.modelValue);
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
// 直接使用 toRef 保持响应式
|
||||
const selectedValue = ref(props.modelValue);
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
selectedValue.value = newVal;
|
||||
cascader.value = newVal;
|
||||
}
|
||||
);
|
||||
const changeValue = (value) => {
|
||||
const hasEmpty = value.some((item) => item[0] === '0');
|
||||
const propEmpty = cascader.value.some((item) => item[0] === '0');
|
||||
if (hasEmpty && !propEmpty) {
|
||||
emit('update:modelValue', [['0']]);
|
||||
} else {
|
||||
emit(
|
||||
'update:modelValue',
|
||||
value.filter((item) => item[0] !== '0')
|
||||
);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-cascader
|
||||
v-model="selectedValue"
|
||||
class="yl-cascader"
|
||||
style="width: 100%"
|
||||
:show-all-levels="false"
|
||||
:options="props.options"
|
||||
:props="{ multiple: true }"
|
||||
@change="changeValue"
|
||||
>
|
||||
</el-cascader>
|
||||
</template>
|
||||
<style scoped>
|
||||
::v-deep .yl-cascader {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -84,7 +84,9 @@ const getType = (v: any) => {
|
||||
return Object.prototype.toString.call(v).slice(8, -1).toLowerCase();
|
||||
};
|
||||
// 返回指定格式的日期时间
|
||||
const getDateByFormat = (date: Date | string, fmt: dateFormat) => {
|
||||
const getDateByFormat = (dateParam: Date | string, fmt: dateFormat) => {
|
||||
// 避免直接修改参数
|
||||
let date = dateParam;
|
||||
const thisDateType = getType(date);
|
||||
if (date === '' || (thisDateType !== 'date' && thisDateType !== 'string')) {
|
||||
date = new Date();
|
||||
@@ -100,7 +102,6 @@ const getDateByFormat = (date: Date | string, fmt: dateFormat) => {
|
||||
const m = date.getMinutes();
|
||||
const s = date.getSeconds();
|
||||
// 个位数补0
|
||||
// 月份比实际获取的少1,所以要加1
|
||||
const _M = M < 10 ? `0${M}` : M.toString();
|
||||
const _D = D < 10 ? `0${D}` : D.toString();
|
||||
const _h = h < 10 ? `0${h}` : h.toString();
|
||||
@@ -116,13 +117,15 @@ const getDateByFormat = (date: Date | string, fmt: dateFormat) => {
|
||||
};
|
||||
|
||||
// 比较两个日期大小
|
||||
const dateRangeLegal = (sDate: string | Date, eDate: string | Date) => {
|
||||
const dateRangeLegal = (sDateParam: string | Date, eDateParam: string | Date) => {
|
||||
// 避免直接修改参数
|
||||
let sDate = sDateParam;
|
||||
let eDate = eDateParam;
|
||||
if (!sDate || !eDate) {
|
||||
return false;
|
||||
}
|
||||
// 补全模板
|
||||
const complateStr = '0000-01-01 00:00:00';
|
||||
// 兼容ios
|
||||
if (typeof sDate === 'string') {
|
||||
sDate = (sDate + complateStr.slice(sDate.length)).replace(/-/g, '/');
|
||||
}
|
||||
@@ -167,8 +170,8 @@ const getMaxDateLimit = computed(() => {
|
||||
props.format
|
||||
);
|
||||
const tempStr = '0000-12-31 23:59:59';
|
||||
const result
|
||||
= props.maxDate.length !== 0 && thisMax.length > props.maxDate.length
|
||||
const result =
|
||||
props.maxDate.length !== 0 && thisMax.length > props.maxDate.length
|
||||
? thisMax.slice(0, props.maxDate.length) + tempStr.slice(props.maxDate.length)
|
||||
: thisMax;
|
||||
return result.slice(0, props.format.length);
|
||||
@@ -191,8 +194,8 @@ function onChange({ selectedValues, columnIndex }) {
|
||||
renderMinuteColumns,
|
||||
renderSecondColumns
|
||||
];
|
||||
updateColumns[columnIndex]
|
||||
&& updateColumns[columnIndex](changeValue, getMinDateLimit.value, getMaxDateLimit.value, false);
|
||||
updateColumns[columnIndex] &&
|
||||
updateColumns[columnIndex](changeValue, getMinDateLimit.value, getMaxDateLimit.value, false);
|
||||
}
|
||||
|
||||
// 渲染全部列
|
||||
@@ -318,15 +321,16 @@ const renderMonthColumns = (
|
||||
s: string,
|
||||
e: string,
|
||||
isFirst: boolean,
|
||||
outRange: boolean = false
|
||||
outRangeParam: boolean = false
|
||||
) => {
|
||||
const thisY = Number(v.slice(0, 4)); // 获取当前月
|
||||
const thisM = Number(v.slice(5, 7)); // 获取当前月
|
||||
const minY = Number(s.slice(0, 4)); // 最小年份
|
||||
const maxY = Number(e.slice(0, 4)); // 最大年份
|
||||
const listArr: any = []; // 获取月份数组
|
||||
let forStart = -1; // 最小月份
|
||||
let forEnd = -1; // 最小月份
|
||||
let outRange = outRangeParam;
|
||||
const thisY = Number(v.slice(0, 4));
|
||||
const thisM = Number(v.slice(5, 7));
|
||||
const minY = Number(s.slice(0, 4));
|
||||
const maxY = Number(e.slice(0, 4));
|
||||
const listArr: any = [];
|
||||
let forStart = -1;
|
||||
let forEnd = -1;
|
||||
if (thisY === minY && thisY === maxY) {
|
||||
forStart = Number(s.slice(5, 7));
|
||||
forEnd = Number(e.slice(5, 7));
|
||||
@@ -389,24 +393,25 @@ const renderDayColumns = (
|
||||
s: string,
|
||||
e: string,
|
||||
isFirst: boolean,
|
||||
outRange: boolean = false
|
||||
outRangeParam: boolean = false
|
||||
) => {
|
||||
const thisYM = v.slice(0, 7); // 获取当前年月
|
||||
const thisD = Number(v.slice(8, 10)); // 获取当前日
|
||||
const startYM = s.slice(0, 7); // 开始时间临界值
|
||||
const endYM = e.slice(0, 7); // 结束时间临界值
|
||||
const listArr: any = []; // 获取月份数组
|
||||
let forStart = -1; // 最小月份
|
||||
let forEnd = -1; // 最小月份
|
||||
let outRange = outRangeParam;
|
||||
const thisYM = v.slice(0, 7);
|
||||
const thisD = Number(v.slice(8, 10));
|
||||
const startYM = s.slice(0, 7);
|
||||
const endYM = e.slice(0, 7);
|
||||
const listArr: any = [];
|
||||
let forStart = -1;
|
||||
let forEnd = -1;
|
||||
if (thisYM === startYM && thisYM === endYM) {
|
||||
forStart = Number(s.slice(8, 10)); // 开始时间的天临界值
|
||||
forEnd = Number(e.slice(8, 10)); // 结束时间的天临界值
|
||||
forStart = Number(s.slice(8, 10));
|
||||
forEnd = Number(e.slice(8, 10));
|
||||
} else if (thisYM === startYM) {
|
||||
forStart = Number(s.slice(8, 10));
|
||||
forEnd = getCountDays(Number(v.slice(0, 4)), Number(v.slice(5, 7)));
|
||||
} else if (thisYM === endYM) {
|
||||
forStart = 1;
|
||||
forEnd = Number(e.slice(8, 10)); // 结束时间的天临界值
|
||||
forEnd = Number(e.slice(8, 10));
|
||||
} else {
|
||||
forStart = 1;
|
||||
forEnd = getCountDays(Number(v.slice(0, 4)), Number(v.slice(5, 7)));
|
||||
@@ -460,18 +465,24 @@ const renderHourColumns = (
|
||||
s: string,
|
||||
e: string,
|
||||
isFirst: boolean,
|
||||
outRange: boolean = false
|
||||
outRangeParam: boolean = false
|
||||
) => {
|
||||
const thisYMD = v.slice(0, 10); // 获取当前年月日
|
||||
const startYMD = s.slice(0, 10); // 开始时间临界值
|
||||
const endYMD = e.slice(0, 10); // 结束时间临界值
|
||||
const thisH = Number(v.slice(11, 13)); // 获取当前小时
|
||||
const listArr: any = []; // 获取小时数组
|
||||
let forStart = -1; // 最小月份
|
||||
let forEnd = -1; // 最小月份
|
||||
// 避免直接修改参数
|
||||
let outRange = outRangeParam;
|
||||
// 获取当前年月日
|
||||
const thisYMD = v.slice(0, 10);
|
||||
// 开始时间临界值
|
||||
const startYMD = s.slice(0, 10);
|
||||
// 结束时间临界值
|
||||
const endYMD = e.slice(0, 10);
|
||||
// 获取当前小时
|
||||
const thisH = Number(v.slice(11, 13));
|
||||
const listArr: any = [];
|
||||
let forStart = -1;
|
||||
let forEnd = -1;
|
||||
if (thisYMD === startYMD && thisYMD === endYMD) {
|
||||
forStart = Number(s.slice(11, 13)); // 开始时间的小时临界值
|
||||
forEnd = Number(e.slice(11, 13)); // 结束时间的小时临界值
|
||||
forStart = Number(s.slice(11, 13));
|
||||
forEnd = Number(e.slice(11, 13));
|
||||
} else if (thisYMD === startYMD) {
|
||||
forStart = Number(s.slice(11, 13));
|
||||
forEnd = 23;
|
||||
@@ -531,15 +542,20 @@ const renderMinuteColumns = (
|
||||
s: string,
|
||||
e: string,
|
||||
isFirst: boolean,
|
||||
outRange: boolean = false
|
||||
outRangeParam: boolean = false
|
||||
) => {
|
||||
const thisYMDH = v.slice(0, 13); // 获取当前年月日小时
|
||||
const startYMDH = s.slice(0, 13); // 开始时间临界值
|
||||
const endYMDH = e.slice(0, 13); // 结束时间临界值
|
||||
const thisM = Number(v.slice(14, 16)); // 获取当前分钟
|
||||
const listArr: any = []; // 获取数组
|
||||
let forStart = -1; // 循环最小值
|
||||
let forEnd = -1; // 循环最大值
|
||||
// 避免直接修改参数
|
||||
let outRange = outRangeParam;
|
||||
// 获取当前年月日小时
|
||||
const thisYMDH = v.slice(0, 13);
|
||||
// 开始时间临界值
|
||||
const startYMDH = s.slice(0, 13);
|
||||
// 结束时间临界值
|
||||
const endYMDH = e.slice(0, 13);
|
||||
const thisM = Number(v.slice(14, 16));
|
||||
const listArr: any = [];
|
||||
let forStart = -1;
|
||||
let forEnd = -1;
|
||||
if (thisYMDH === startYMDH && thisYMDH === endYMDH) {
|
||||
forStart = Number(s.slice(14, 16));
|
||||
forEnd = Number(e.slice(14, 16));
|
||||
@@ -602,15 +618,16 @@ const renderSecondColumns = (
|
||||
s: string,
|
||||
e: string,
|
||||
isFirst: boolean,
|
||||
outRange: boolean = false
|
||||
outRangeParam: boolean = false
|
||||
) => {
|
||||
const thisYMDHM = v.slice(0, 16); // 获取当前年月日小时
|
||||
const startYMDHM = s.slice(0, 16); // 开始时间临界值
|
||||
const endYMDHM = e.slice(0, 16); // 结束时间临界值
|
||||
const thisS = Number(v.slice(17, 19)); // 获取当前分钟
|
||||
const listArr: any = []; // 获取数组
|
||||
let forStart = -1; // 循环最小值
|
||||
let forEnd = -1; // 循环最大值
|
||||
let outRange = outRangeParam;
|
||||
const thisYMDHM = v.slice(0, 16);
|
||||
const startYMDHM = s.slice(0, 16);
|
||||
const endYMDHM = e.slice(0, 16);
|
||||
const thisS = Number(v.slice(17, 19));
|
||||
const listArr: any = [];
|
||||
let forStart = -1;
|
||||
let forEnd = -1;
|
||||
if (thisYMDHM === startYMDHM && thisYMDHM === endYMDHM) {
|
||||
forStart = Number(s.slice(17, 19));
|
||||
forEnd = Number(e.slice(17, 19));
|
||||
@@ -665,7 +682,7 @@ watch(
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true // 立即监听--进入就会执行一次 监听显影状态
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -88,7 +88,6 @@ const checkContains = (element, target) => {
|
||||
try {
|
||||
return element?.contains(target) ?? false;
|
||||
} catch (e) {
|
||||
console.error('Contains check failed:', e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -134,11 +133,11 @@ onMounted(() => {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 2008;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
padding: 0 10px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
line-height: 40px;
|
||||
|
||||
& button {
|
||||
|
||||
@@ -10,7 +10,8 @@ import axios from 'axios';
|
||||
const service = axios.create({
|
||||
// baseURL: `${baseURL}`, // url = base url + request url
|
||||
// withCredentials: true, // send cookies when cross-domain requests
|
||||
timeout: 30000 // request timeout
|
||||
// request timeout
|
||||
timeout: 30000
|
||||
});
|
||||
|
||||
// request interceptor
|
||||
@@ -36,7 +37,12 @@ service.interceptors.request.use(
|
||||
// response interceptor
|
||||
service.interceptors.response.use(
|
||||
(response) => {
|
||||
if (response.status === 200 || response.status === 201 || response.status === 202 || response.status === 204) {
|
||||
if (
|
||||
response.status === 200
|
||||
|| response.status === 201
|
||||
|| response.status === 202
|
||||
|| response.status === 204
|
||||
) {
|
||||
if (response.config.method === 'put') {
|
||||
// message.success('保存中...');
|
||||
}
|
||||
|
||||
@@ -407,7 +407,6 @@ export const useCommonStore = defineStore('common', {
|
||||
}),
|
||||
actions: {
|
||||
async fetchQuestionInfo(questionInfo) {
|
||||
console.log(questionInfo, 456);
|
||||
try {
|
||||
if (!questionInfo) return;
|
||||
|
||||
|
||||
@@ -7,44 +7,44 @@ export default {
|
||||
title: '',
|
||||
options: [
|
||||
[
|
||||
{
|
||||
id: '',
|
||||
is_fixed: 0,
|
||||
is_other: 0,
|
||||
is_remove_other: 0,
|
||||
option: '<p>选项1</p>',
|
||||
option_config: {
|
||||
image_url: [],
|
||||
title: '',
|
||||
instructions: [],
|
||||
option_type: 0,
|
||||
limit_right_content: ''
|
||||
},
|
||||
option_index: 1,
|
||||
parent_id: 0,
|
||||
type: 0,
|
||||
cascade: [],
|
||||
config: []
|
||||
},
|
||||
{
|
||||
id: '',
|
||||
is_fixed: 0,
|
||||
is_other: 0,
|
||||
is_remove_other: 0,
|
||||
option: '<p>选项2</p>',
|
||||
option_config: {
|
||||
image_url: [],
|
||||
title: '',
|
||||
instructions: [],
|
||||
option_type: 0,
|
||||
limit_right_content: ''
|
||||
},
|
||||
option_index: 1,
|
||||
parent_id: 0,
|
||||
type: 0,
|
||||
cascade: [],
|
||||
config: []
|
||||
}
|
||||
// {
|
||||
// id: '',
|
||||
// is_fixed: 0,
|
||||
// is_other: 0,
|
||||
// is_remove_other: 0,
|
||||
// option: '<p>选项1</p>',
|
||||
// option_config: {
|
||||
// image_url: [],
|
||||
// title: '',
|
||||
// instructions: [],
|
||||
// option_type: 0,
|
||||
// limit_right_content: ''
|
||||
// },
|
||||
// option_index: 1,
|
||||
// parent_id: 0,
|
||||
// type: 0,
|
||||
// cascade: [],
|
||||
// config: []
|
||||
// },
|
||||
// {
|
||||
// id: '',
|
||||
// is_fixed: 0,
|
||||
// is_other: 0,
|
||||
// is_remove_other: 0,
|
||||
// option: '<p>选项2</p>',
|
||||
// option_config: {
|
||||
// image_url: [],
|
||||
// title: '',
|
||||
// instructions: [],
|
||||
// option_type: 0,
|
||||
// limit_right_content: ''
|
||||
// },
|
||||
// option_index: 1,
|
||||
// parent_id: 0,
|
||||
// type: 0,
|
||||
// cascade: [],
|
||||
// config: []
|
||||
// }
|
||||
],
|
||||
[
|
||||
{
|
||||
|
||||
@@ -12,9 +12,11 @@ const baseURL = config.default.proxyUrl;
|
||||
|
||||
// create an axios instance
|
||||
const service = axios.create({
|
||||
baseURL: `${baseURL}`, // url = base url + request url
|
||||
// url = base url + request url
|
||||
baseURL: `${baseURL}/api`,
|
||||
// withCredentials: true, // send cookies when cross-domain requests
|
||||
timeout: 30000 // request timeout
|
||||
// request timeout
|
||||
timeout: 30000
|
||||
});
|
||||
|
||||
// request interceptor
|
||||
@@ -24,10 +26,10 @@ service.interceptors.request.use(
|
||||
config.headers.Accept = 'application/json';
|
||||
}
|
||||
config.headers.Authorization = `${localStorage.getItem('plantToken')}`;
|
||||
if (!config.headers.remoteIp) {
|
||||
config.baseURL += '/api';
|
||||
}
|
||||
delete config.headers.host;
|
||||
// if (!config.headers.remoteIp) {
|
||||
// config.baseURL += '/api';
|
||||
// }
|
||||
// delete config.headers.host;
|
||||
config.headers.remoteIp = localStorage.getItem('plantIp') || '127.0.0.1';
|
||||
// if (store.state.common.token) {
|
||||
// config.headers['Login-Type'] = 'pc';
|
||||
@@ -42,10 +44,10 @@ service.interceptors.request.use(
|
||||
service.interceptors.response.use(
|
||||
(response) => {
|
||||
if (
|
||||
response.status === 200
|
||||
|| response.status === 201
|
||||
|| response.status === 202
|
||||
|| response.status === 204
|
||||
response.status === 200 ||
|
||||
response.status === 201 ||
|
||||
response.status === 202 ||
|
||||
response.status === 204
|
||||
) {
|
||||
if (response.config.method === 'put') {
|
||||
// message.success('保存中...');
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
*/
|
||||
export function getSequenceName(names, list, options) {
|
||||
const result = [];
|
||||
const start = options?.start ?? 1; // 从数字几开始
|
||||
// 从数字几开始
|
||||
const start = options?.start ?? 1;
|
||||
|
||||
names.forEach((name) => {
|
||||
const sequence = [];
|
||||
@@ -61,7 +62,8 @@ export function getDomText(dom) {
|
||||
* @returns {boolean[]}
|
||||
*/
|
||||
export function isRichRepeat(list, comparedList, options) {
|
||||
const imageAsDifference = options?.imageAsDifference !== false; // 不比较图片。只要存在图片,则认为两段富文本不同
|
||||
// 不比较图片。只要存在图片,则认为两段富文本不同
|
||||
const imageAsDifference = options?.imageAsDifference !== false;
|
||||
|
||||
return list.map((rich) => {
|
||||
return comparedList.some((compared) => {
|
||||
@@ -83,7 +85,8 @@ export function isRichRepeat(list, comparedList, options) {
|
||||
* @returns {boolean[]}
|
||||
*/
|
||||
export function isRichEmpty(list, options) {
|
||||
const imageAsEmpty = options?.imageAsEmpty === true; // 不判断图片。只要存在图片,则认为两段富文本不为空
|
||||
// 不判断图片。只要存在图片,则认为两段富文本不为空
|
||||
const imageAsEmpty = options?.imageAsEmpty === true;
|
||||
|
||||
return list.map((rich) => {
|
||||
if (!imageAsEmpty) {
|
||||
|
||||
@@ -293,7 +293,6 @@ const actionEvent = (item, el) => {
|
||||
const actionFun = {
|
||||
// 单选事件 添加选项
|
||||
radioAddOption: (element) => {
|
||||
console.log(element);
|
||||
element.options.map((item) => {
|
||||
item.push({
|
||||
id: uuidv4(),
|
||||
@@ -313,6 +312,8 @@ const actionFun = {
|
||||
});
|
||||
});
|
||||
element.last_option_index += 1;
|
||||
|
||||
saveQueItem(questionInfo.value.logics, [element]);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -320,16 +321,59 @@ const actionFun = {
|
||||
* @param element {import('./components/Questions/types/martrix.js').MatrixSurveyQuestion}
|
||||
*/
|
||||
addMatrixRowOption: (element) => {
|
||||
const optionIndex = element.last_option_index;
|
||||
element.options[0].push({
|
||||
option: '新增行'
|
||||
cascade: [],
|
||||
config: [],
|
||||
is_fixed: 0,
|
||||
is_other: 0,
|
||||
is_remove_other: 0,
|
||||
option: `<p style="text-align:center">行标签${element.options[0].length + 1}</p>`,
|
||||
option_config: {
|
||||
image_url: [],
|
||||
title: '',
|
||||
instructions: [],
|
||||
option_type: 0,
|
||||
limit_right_content: '<p>右极文字1</p>'
|
||||
},
|
||||
parent_id: 0,
|
||||
type: 1,
|
||||
id: uuidv4(),
|
||||
option_index: optionIndex + 1
|
||||
});
|
||||
|
||||
element.last_option_index = optionIndex + 1;
|
||||
|
||||
saveQueItem(questionInfo.value.logics, [element]);
|
||||
},
|
||||
/**
|
||||
* martrix 矩阵列数增加
|
||||
* @param element {import('./components/Questions/types/martrix.js').MatrixSurveyQuestion}
|
||||
*/
|
||||
addMatrixColumnOption: (element) => {
|
||||
element.options[1].push({ option: '新增列' });
|
||||
const optionIndex = element.last_option_index;
|
||||
element.options[1].push({
|
||||
cascade: [],
|
||||
config: [],
|
||||
is_fixed: 0,
|
||||
is_other: 0,
|
||||
is_remove_other: 0,
|
||||
option: `<p style="text-align:center">列标签${element.options[1].length + 1}</p>`,
|
||||
option_config: {
|
||||
image_url: [],
|
||||
title: '',
|
||||
instructions: [],
|
||||
option_type: 0,
|
||||
limit_right_content: '<p>右极文字1</p>'
|
||||
},
|
||||
parent_id: 0,
|
||||
type: 2,
|
||||
id: uuidv4(),
|
||||
option_index: optionIndex + 1
|
||||
});
|
||||
|
||||
element.last_option_index = optionIndex + 1;
|
||||
saveQueItem(questionInfo.value.logics, [element]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
></van-switch>
|
||||
</template>
|
||||
</van-cell>
|
||||
|
||||
<van-divider></van-divider>
|
||||
<van-cell title="题前隐藏" :border="false" @click="questionSetting('before')">
|
||||
<template #right-icon>
|
||||
@@ -69,13 +70,25 @@
|
||||
:config="activeQuestion.config"
|
||||
@update-config="updateConfig"
|
||||
></rate-question-action>
|
||||
|
||||
<!-- 填空-->
|
||||
<completion-question-action
|
||||
v-if="activeQuestion.question_type === 4"
|
||||
v-if="[4, 8].includes(activeQuestion.question_type)"
|
||||
v-model="activeQuestion"
|
||||
@save-option="saveSettings"
|
||||
></completion-question-action>
|
||||
|
||||
<field-upload-question-action
|
||||
v-if="activeQuestion.question_type === 18"
|
||||
v-model="activeQuestion"
|
||||
@save-option="saveSettings"
|
||||
></field-upload-question-action>
|
||||
|
||||
<!-- 矩阵题目-->
|
||||
<martrix-question-action
|
||||
v-if="[8, 9, 10].includes(activeQuestion.question_type)"
|
||||
v-model="activeQuestion"
|
||||
@save-option="saveSettings"
|
||||
></martrix-question-action>
|
||||
</van-cell-group>
|
||||
</van-action-sheet>
|
||||
<!-- 移动 复制-->
|
||||
@@ -125,6 +138,8 @@ import { storeToRefs } from 'pinia';
|
||||
import QuestionBefore from '@/views/Design/components/ActionCompoents/components/QuestionItemAction/QuestionBefore.vue';
|
||||
import RateQuestionAction from './components/QuestionItemAction/RateQuestionAction.vue';
|
||||
import CompletionQuestionAction from '@/views/Design/components/ActionCompoents/components/QuestionItemAction/CompletionQuestionAction.vue';
|
||||
import FieldUploadQuestionAction from '@/views/Design/components/ActionCompoents/components/QuestionItemAction/FieldUploadQuestionAction.vue';
|
||||
import MartrixQuestionAction from '@/views/Design/components/ActionCompoents/components/QuestionItemAction/MartrixQuestionAction.vue';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
const store = useCounterStore();
|
||||
const { questionsInfo } = storeToRefs(store);
|
||||
@@ -238,8 +253,8 @@ const getSkipTypeText = (skipType) => {
|
||||
const ls = [];
|
||||
logics.map((item) => {
|
||||
if (
|
||||
item.skip_type === skipType
|
||||
&& item.question_index === activeQuestion.value.question_index
|
||||
item.skip_type === skipType &&
|
||||
item.question_index === activeQuestion.value.question_index
|
||||
) {
|
||||
ls.push(item);
|
||||
}
|
||||
@@ -255,13 +270,13 @@ const getSkipTypeText = (skipType) => {
|
||||
|
||||
const questionSetting = (type) => {
|
||||
switch (type) {
|
||||
case 'before':
|
||||
questionBeforeShow.value = true;
|
||||
case 'before':
|
||||
questionBeforeShow.value = true;
|
||||
|
||||
break;
|
||||
case 'after':
|
||||
questionBeforeShow.value = true;
|
||||
break;
|
||||
break;
|
||||
case 'after':
|
||||
questionBeforeShow.value = true;
|
||||
break;
|
||||
}
|
||||
skipType.value = type === 'before' ? 1 : 0;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import request from '@/utils/request.js';
|
||||
|
||||
export function getFileType() {
|
||||
return request({
|
||||
url: `/console/file_type`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
@@ -68,7 +68,6 @@
|
||||
</template>
|
||||
</van-field>
|
||||
</van-cell-group>
|
||||
<van-divider></van-divider>
|
||||
|
||||
<van-cell
|
||||
v-if="![5, 6, 7].includes(actionQuestion.config.text_type)"
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<van-cell title="文件数量" :border="false" label-align="left"> </van-cell>
|
||||
<van-field
|
||||
v-model="actionQuestion.config.min_number"
|
||||
label="最少"
|
||||
type="number"
|
||||
:border="false"
|
||||
label-align="left"
|
||||
input-align="right"
|
||||
class="action-field"
|
||||
placeholder="不限"
|
||||
@blur="emit('saveOption')"
|
||||
@update:model-value="
|
||||
(value) => {
|
||||
actionQuestion.config.min_number = Number(value);
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #right-icon>
|
||||
<span>个</span>
|
||||
</template>
|
||||
</van-field>
|
||||
<van-field
|
||||
v-model="actionQuestion.config.max_number"
|
||||
label="最多"
|
||||
type="number"
|
||||
:border="false"
|
||||
placeholder="不限"
|
||||
input-align="right"
|
||||
class="action-field"
|
||||
@blur="emit('saveOption')"
|
||||
@update:model-value="
|
||||
(value) => {
|
||||
actionQuestion.config.max_number = Number(value);
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #right-icon>
|
||||
<span>个</span>
|
||||
</template>
|
||||
</van-field>
|
||||
<van-divider></van-divider>
|
||||
<van-cell title="文件大小" :border="false" label-align="left"> </van-cell>
|
||||
<van-field
|
||||
v-model="actionQuestion.config.min_size"
|
||||
label="最少"
|
||||
type="number"
|
||||
:border="false"
|
||||
label-align="left"
|
||||
input-align="right"
|
||||
class="action-field"
|
||||
placeholder="不限"
|
||||
@blur="emit('saveOption')"
|
||||
@update:model-value="
|
||||
(value) => {
|
||||
actionQuestion.config.min_size = Number(value);
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #right-icon>
|
||||
<span>M</span>
|
||||
</template>
|
||||
</van-field>
|
||||
<van-field
|
||||
v-model="actionQuestion.config.max_size"
|
||||
label="最多"
|
||||
type="number"
|
||||
:border="false"
|
||||
placeholder="不限"
|
||||
input-align="right"
|
||||
class="action-field"
|
||||
@blur="emit('saveOption')"
|
||||
@update:model-value="
|
||||
(value) => {
|
||||
actionQuestion.config.max_size = Number(value);
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #right-icon>
|
||||
<span>M</span>
|
||||
</template>
|
||||
</van-field>
|
||||
|
||||
<van-divider></van-divider>
|
||||
<van-field label="文件格式" :border="false" label-align="top">
|
||||
<template #input>
|
||||
<div style="width: 100%">
|
||||
<YLCascader
|
||||
v-if="fileTypeList.length > 0"
|
||||
v-model="fileType"
|
||||
:options="fileTypeList"
|
||||
@update:model-value="changeFileType"
|
||||
></YLCascader>
|
||||
</div>
|
||||
</template>
|
||||
</van-field>
|
||||
</template>
|
||||
<script setup>
|
||||
import { computed, defineEmits, onMounted, ref } from 'vue';
|
||||
import YLCascader from '@/components/YLCascader.vue';
|
||||
|
||||
import { getFileType } from './Api/Index.js';
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'saveOption']);
|
||||
const actionQuestion = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(newValue) {
|
||||
emit('update:modelValue', newValue);
|
||||
}
|
||||
});
|
||||
|
||||
const fileTypeList = ref([]);
|
||||
const fileType = ref([]);
|
||||
actionQuestion.value.config.file_type.split(',').map((item) => {
|
||||
const type = item.split('-');
|
||||
fileType.value.push(type);
|
||||
});
|
||||
const getFileTypeList = () => {
|
||||
getFileType().then((res) => {
|
||||
if (res.data) {
|
||||
// 对象keymap
|
||||
fileTypeList.value = Object.keys(res.data.data).map((key) => {
|
||||
return {
|
||||
label: key,
|
||||
value: key,
|
||||
children: res.data.data[key].map((item) => {
|
||||
return {
|
||||
label: item,
|
||||
value: item
|
||||
};
|
||||
})
|
||||
};
|
||||
});
|
||||
fileTypeList.value.unshift({
|
||||
label: '不限',
|
||||
value: '0',
|
||||
isUnlimitSelected: 1,
|
||||
children: null
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const changeFileType = (value) => {
|
||||
if (value === [['0']]) {
|
||||
actionQuestion.value.config.file_type = '0';
|
||||
emit('saveOption');
|
||||
return;
|
||||
}
|
||||
const ls = [];
|
||||
value.map((item) => {
|
||||
const obj = [];
|
||||
item.map((s) => {
|
||||
obj.push(s);
|
||||
});
|
||||
ls.push(obj.join('-'));
|
||||
});
|
||||
actionQuestion.value.config.file_type = ls.join(',');
|
||||
emit('saveOption');
|
||||
};
|
||||
onMounted(() => {
|
||||
getFileTypeList();
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.action-field {
|
||||
& ::v-deep .van-field__label {
|
||||
color: #bfbfbf;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<van-cell v-if="[9, 10].includes(actionQuestion.question_type)" title="选项随机" :border="false">
|
||||
<template #right-icon>
|
||||
<van-switch
|
||||
v-model="actionQuestion.config.select_random"
|
||||
class="option-action-sheet-switch"
|
||||
size="0.5rem"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
@change="emit('saveOption')"
|
||||
></van-switch>
|
||||
</template>
|
||||
</van-cell>
|
||||
<van-field
|
||||
v-if="
|
||||
[9, 10].includes(actionQuestion.question_type) && actionQuestion.config.select_random === 1
|
||||
"
|
||||
v-model="actionQuestion.config.min_select"
|
||||
label=""
|
||||
:border="false"
|
||||
label-align="left"
|
||||
input-align="right"
|
||||
class="action-field"
|
||||
placeholder="不限"
|
||||
@blur="emit('saveOption')"
|
||||
@update:model-value="
|
||||
(value) => {
|
||||
actionQuestion.config.min_select = Number(value);
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #input>
|
||||
<div class="flex">
|
||||
<van-checkbox
|
||||
v-model="actionQuestion.config.row_random"
|
||||
shape="square"
|
||||
name="actionQuestion.config.row_random"
|
||||
icon-size="0.5rem"
|
||||
@change="emit('saveOption')"
|
||||
>
|
||||
行随机
|
||||
</van-checkbox>
|
||||
<van-checkbox
|
||||
v-model="actionQuestion.config.cell_random"
|
||||
class="ml10"
|
||||
shape="square"
|
||||
icon-size="0.5rem"
|
||||
name="actionQuestion.config.cell_random"
|
||||
@change="emit('saveOption')"
|
||||
>
|
||||
列随机
|
||||
</van-checkbox>
|
||||
</div>
|
||||
</template>
|
||||
</van-field>
|
||||
<van-cell
|
||||
title="右极文字"
|
||||
type="number"
|
||||
:border="false"
|
||||
label-align="left"
|
||||
input-align="right"
|
||||
label-width="8em"
|
||||
placeholder="不限"
|
||||
readonly
|
||||
@blur="emit('saveOption')"
|
||||
>
|
||||
<template #right-icon>
|
||||
<van-switch
|
||||
v-model="actionQuestion.config.is_limit_right_content"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
size="0.5rem"
|
||||
@change="emit('saveOption')"
|
||||
></van-switch>
|
||||
</template>
|
||||
</van-cell>
|
||||
<!-- 每行可选数量-->
|
||||
<van-cell
|
||||
v-if="[10].includes(actionQuestion.question_type)"
|
||||
title="每行可选数量"
|
||||
:border="false"
|
||||
label-align="left"
|
||||
>
|
||||
</van-cell>
|
||||
<van-cell-group v-if="[10].includes(actionQuestion.question_type)">
|
||||
<van-field
|
||||
v-model="actionQuestion.config.min_select"
|
||||
label="最少"
|
||||
type="number"
|
||||
:border="false"
|
||||
label-align="left"
|
||||
input-align="right"
|
||||
class="action-field"
|
||||
placeholder="不限"
|
||||
@blur="emit('saveOption')"
|
||||
@update:model-value="
|
||||
(value) => {
|
||||
actionQuestion.config.min_select = Number(value);
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #right-icon>
|
||||
<span>个</span>
|
||||
</template>
|
||||
</van-field>
|
||||
<van-field
|
||||
v-model="actionQuestion.config.max_select"
|
||||
label="最多"
|
||||
type="number"
|
||||
:border="false"
|
||||
placeholder="不限"
|
||||
input-align="right"
|
||||
class="action-field"
|
||||
@blur="emit('saveOption')"
|
||||
@update:model-value="
|
||||
(value) => {
|
||||
actionQuestion.config.max_select = Number(value);
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #right-icon>
|
||||
<span>个</span>
|
||||
</template>
|
||||
</van-field>
|
||||
</van-cell-group>
|
||||
</template>
|
||||
<script setup>
|
||||
import { computed, defineEmits } from 'vue';
|
||||
import { saveQuestion } from '@/api/design/index.js';
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'saveOption']);
|
||||
const actionQuestion = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(newValue) {
|
||||
emit('update:modelValue', newValue);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.action-field {
|
||||
& ::v-deep .van-field__label {
|
||||
color: #bfbfbf;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -183,7 +183,6 @@ skipOption.push(
|
||||
let optionOptions = [];
|
||||
// todo 不同题型逻辑对应不同 需要开发
|
||||
const changeQuestionIndex = (value, logicItem) => {
|
||||
console.log(logicItem);
|
||||
if (!value) {
|
||||
return [];
|
||||
}
|
||||
@@ -315,10 +314,8 @@ const symbolOptions = [
|
||||
}
|
||||
];
|
||||
|
||||
const getQuestionType = (value) => {
|
||||
console.log(beforeQuesOptions);
|
||||
const type = beforeQuesOptions.filter((item) => item.question_index === value)[0];
|
||||
console.log(type);
|
||||
const getQuestionType = () => {
|
||||
// const type = beforeQuesOptions.filter((item) => item.question_index === value)[0];
|
||||
};
|
||||
|
||||
const logicIf = (value, index) => {
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import OptionAction from '@/views/Design/components/ActionCompoents/OptionAction.vue';
|
||||
import { defineAsyncComponent, toRefs } from 'vue';
|
||||
import { defineAsyncComponent, toRefs, ref } from 'vue';
|
||||
|
||||
const choiceValue = ref('checked');
|
||||
const Contenteditable = defineAsyncComponent(() => import('@/components/contenteditable.vue'));
|
||||
|
||||
@@ -1,71 +1,89 @@
|
||||
<script setup lang="ts">
|
||||
import { Toast } from 'vant';
|
||||
import { toRefs } from 'vue';
|
||||
|
||||
const { element } = defineProps<{ element: FileUploadQuestion }>();
|
||||
const props = defineProps<{
|
||||
element: any;
|
||||
index: number;
|
||||
active: boolean;
|
||||
}>();
|
||||
const { element } = toRefs(props);
|
||||
|
||||
/**
|
||||
* 文件大小限制
|
||||
* @property {number} max - 最大文件大小
|
||||
* @property {number} min - 最小文件大小
|
||||
*/
|
||||
const fileLimit = {
|
||||
// 默认3MB
|
||||
max: 1024 * 1024 * 4,
|
||||
min: 0
|
||||
};
|
||||
// const fileLimit = {
|
||||
// // 默认3MB
|
||||
// max: 1024 * 1024 * 4,
|
||||
// min: 0
|
||||
// };
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
* @description 上传文件
|
||||
*/
|
||||
function handleFileUpload() {
|
||||
const fileInput = document.createElement('input');
|
||||
fileInput.type = 'file';
|
||||
// fileInput.accept = '.jpg,.jpeg,.png,.gif';
|
||||
// fileInput.multiple = true;
|
||||
fileInput.click();
|
||||
// function handleFileUpload() {
|
||||
// const fileInput = document.createElement('input');
|
||||
// fileInput.type = 'file';
|
||||
// // fileInput.accept = '.jpg,.jpeg,.png,.gif';
|
||||
// // fileInput.multiple = true;
|
||||
// fileInput.click();
|
||||
//
|
||||
// fileInput.addEventListener('change', handleFileChange);
|
||||
//
|
||||
// function handleFileChange(event: Event) {
|
||||
// const files = (event.target as HTMLInputElement).files;
|
||||
// if (files) {
|
||||
// for (let i = 0; i < files.length; i++) {
|
||||
// const file = files[i];
|
||||
// // console.log(file.size);
|
||||
//
|
||||
// if (file.size > fileLimit.max) {
|
||||
// Toast.fail(`文件太大,超过${fileLimit.max / 1024 / 1024}MB`);
|
||||
// return;
|
||||
// } else if (file.size < fileLimit.min) {
|
||||
// Toast.fail(`文件太小,小于${fileLimit.min / 1024 / 1024}MB`);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
fileInput.addEventListener('change', handleFileChange);
|
||||
|
||||
function handleFileChange(event: Event) {
|
||||
const files = (event.target as HTMLInputElement).files;
|
||||
if (files) {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
// console.log(file.size);
|
||||
|
||||
if (file.size > fileLimit.max) {
|
||||
Toast.fail(`文件太大,超过${fileLimit.max / 1024 / 1024}MB`);
|
||||
return;
|
||||
} else if (file.size < fileLimit.min) {
|
||||
Toast.fail(`文件太小,小于${fileLimit.min / 1024 / 1024}MB`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const emit = defineEmits(['update:element']);
|
||||
const emitValue = () => {
|
||||
emit('update:element', element.value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<van-cell-group>
|
||||
<van-cell>
|
||||
<van-field
|
||||
v-model="element.stem"
|
||||
:label="element.stem"
|
||||
:required="element.config.is_required === 1"
|
||||
label-align="top"
|
||||
>
|
||||
<template #left-icon>
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
<!-- 使用 title 插槽来自定义标题 -->
|
||||
<template #title>
|
||||
<span>
|
||||
<span v-if="element?.config?.is_required">*</span>
|
||||
<span v-html="element.title"></span>
|
||||
<span v-html="element.stem"></span>
|
||||
</span>
|
||||
<template #label>
|
||||
<contenteditable
|
||||
v-model="element.stem"
|
||||
:active="active"
|
||||
@blur="emitValue"
|
||||
></contenteditable>
|
||||
</template>
|
||||
|
||||
<template #label>
|
||||
<div class="file-upload-label" @click="handleFileUpload">
|
||||
<template #input>
|
||||
<div class="file-upload-label">
|
||||
<van-icon name="photo"></van-icon>
|
||||
<span>上传文件</span>
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-field>
|
||||
</van-cell-group>
|
||||
</template>
|
||||
|
||||
@@ -76,5 +94,8 @@ function handleFileUpload() {
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
padding: 5px;
|
||||
border: 1px solid #dfdfdf;
|
||||
border-radius: 3px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { useTemplateRef, type Directive } from 'vue';
|
||||
import { useTemplateRef, toRefs } from 'vue';
|
||||
|
||||
const columnLabels = useTemplateRef<HTMLElement[]>('columnLabels');
|
||||
|
||||
// 注意, element.options 里面的东西是数组,第一项内容是行标签内容,第二项内容是列标签内容
|
||||
// 类型 AI 生成 切勿盲目相信,以实际为准
|
||||
const { element } = defineProps<{ element: MatrixSurveyQuestion }>();
|
||||
const props = defineProps<{
|
||||
element: Any;
|
||||
index: number;
|
||||
active: boolean;
|
||||
}>();
|
||||
|
||||
const { element } = toRefs(props);
|
||||
/**
|
||||
* input 类型映射,里面自行处理逻辑返回对应类型
|
||||
* // remark: 填空内容 question_type 8
|
||||
@@ -15,41 +20,41 @@ const { element } = defineProps<{ element: MatrixSurveyQuestion }>();
|
||||
* @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';
|
||||
switch (element.value.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();
|
||||
}
|
||||
const emit = defineEmits(['update:element']);
|
||||
const emitValue = () => {
|
||||
emit('update:element', element.value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<van-cell>
|
||||
<van-field
|
||||
v-model="element.stem"
|
||||
:label="element.stem"
|
||||
:required="element.config.is_required === 1"
|
||||
label-align="top"
|
||||
>
|
||||
<template #left-icon>
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
<!-- 使用 title 插槽来自定义标题 -->
|
||||
<template #title>
|
||||
<span>
|
||||
<span v-if="element?.config?.is_required">*</span>
|
||||
<span v-html="element.title"></span>
|
||||
<span v-html="element.stem"></span>
|
||||
</span>
|
||||
<template #label>
|
||||
<contenteditable v-model="element.stem" :active="active" @blur="emitValue"></contenteditable>
|
||||
</template>
|
||||
|
||||
<!-- 使用 label 插槽来自定义标题 -->
|
||||
<template #label>
|
||||
<template #input>
|
||||
<table class="martrix-table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -57,40 +62,43 @@ const vFocus: Directive = {
|
||||
<th></th>
|
||||
<!-- 第二行内容开始填充 -->
|
||||
<td v-for="col in element.options[1]" :key="col.option" ref="columnLabels">
|
||||
<!-- 编辑状态,单次点击出输入框,失焦后关闭 -->
|
||||
<input
|
||||
v-if="col.editor"
|
||||
<contenteditable
|
||||
v-model="col.option"
|
||||
v-focus
|
||||
type="text"
|
||||
@focusout="col.editor = false"
|
||||
/>
|
||||
<span v-else @click="col.editor = true" v-html="col.option"></span>
|
||||
:active="active"
|
||||
@blur="emitValue"
|
||||
></contenteditable>
|
||||
</td>
|
||||
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in element.options[0]" :key="row.option">
|
||||
<!-- 编辑状态,单次点击出输入框,失焦后关闭 -->
|
||||
<td>
|
||||
<input
|
||||
v-if="row.editor"
|
||||
<contenteditable
|
||||
v-model="row.option"
|
||||
v-focus
|
||||
type="text"
|
||||
@focusout="row.editor = false"
|
||||
/>
|
||||
<span v-else @click="row.editor = true" v-html="row.option"></span>
|
||||
:active="active"
|
||||
@blur="emitValue"
|
||||
></contenteditable>
|
||||
</td>
|
||||
<td v-for="col in element.options[1]" :key="col.option">
|
||||
<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>
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-field>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.martrix-table {
|
||||
@@ -103,12 +111,31 @@ const vFocus: Directive = {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-width: 0 0 1px;
|
||||
text-align: left;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.td-input {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
width: 85%;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
outline: 1px solid #ddd;
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type='radio'] {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.martrix-table-action {
|
||||
|
||||
@@ -52,8 +52,7 @@ const isOptionChecked = (rowIndex: number, colIndex: number): boolean => {
|
||||
return !!props.matrixAnswer[key];
|
||||
};
|
||||
|
||||
const handleRowNameChange = (value: string) => {
|
||||
console.log(`row change: ${value}`);
|
||||
const handleRowNameChange = () => {
|
||||
// 你可以在这里添加其他逻辑
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ const columnLabels = useTemplateRef<HTMLElement[]>('columnLabels');
|
||||
|
||||
// 注意, element.options 里面的东西是数组,第一项内容是行标签内容,第二项内容是列标签内容
|
||||
// 类型 AI 生成 切勿盲目相信,以实际为准
|
||||
const { element, index } = defineProps<{ element: MatrixSurveyQuestion, index: number }>();
|
||||
const { element, index } = defineProps<{ element: MatrixSurveyQuestion; index: number }>();
|
||||
|
||||
const rowRecord = new Array(element.options[0].length);
|
||||
// matrix 答案
|
||||
@@ -23,14 +23,14 @@ const matrixAnswer = ref({
|
||||
*/
|
||||
const tableInputTypeMapping = (/** regx?: any */) => {
|
||||
switch (element.question_type) {
|
||||
case 8:
|
||||
return 'text';
|
||||
case 9:
|
||||
return 'radio';
|
||||
case 10:
|
||||
return 'checkbox';
|
||||
default:
|
||||
return 'radio';
|
||||
case 8:
|
||||
return 'text';
|
||||
case 9:
|
||||
return 'radio';
|
||||
case 10:
|
||||
return 'checkbox';
|
||||
default:
|
||||
return 'radio';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -50,7 +50,6 @@ const vFocus: Directive = {
|
||||
*/
|
||||
function handleRowNameChange(/* value: string */) {
|
||||
// if (!value) return;
|
||||
console.log(`row change`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,69 +57,67 @@ function handleRowNameChange(/* value: string */) {
|
||||
*/
|
||||
function handleColNameChange(rowOption: string, colOption: string, e: any) {
|
||||
// if (!value) return;
|
||||
const col = element.options[0].findIndex(option => {
|
||||
const col = element.options[0].findIndex((option) => {
|
||||
return option.option === colOption;
|
||||
});
|
||||
|
||||
const row = element.options[1].findIndex(option => {
|
||||
const row = element.options[1].findIndex((option) => {
|
||||
return option.option === rowOption;
|
||||
});
|
||||
|
||||
console.log(`${col + 1}_${row + 1}`);
|
||||
// 不同的 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;
|
||||
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>
|
||||
@@ -145,25 +142,38 @@ function handleColNameChange(rowOption: string, colOption: string, e: any) {
|
||||
<td v-for="col in element.options[1]" :key="col.option" ref="columnLabels">
|
||||
<!-- 编辑状态,单次点击出输入框,失焦后关闭 -->
|
||||
<input
|
||||
v-if="col.editor" v-model="col.option" v-focus type="text" @focusout="col.editor = false"
|
||||
v-if="col.editor"
|
||||
v-model="col.option"
|
||||
v-focus
|
||||
type="text"
|
||||
@focusout="col.editor = false"
|
||||
@click="handleRowNameChange(col.option!)"
|
||||
/>
|
||||
<span v-else @click="col.editor = true" v-html="col.option"></span>
|
||||
</td>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row) in element.options[0]" :key="row.option">
|
||||
<tr v-for="row in element.options[0]" :key="row.option">
|
||||
<!-- 编辑状态,单次点击出输入框,失焦后关闭 -->
|
||||
<td>
|
||||
<input v-if="row.editor" v-model="row.option" v-focus type="text" @focusout="row.editor = false" />
|
||||
<input
|
||||
v-if="row.editor"
|
||||
v-model="row.option"
|
||||
v-focus
|
||||
type="text"
|
||||
@focusout="row.editor = false"
|
||||
/>
|
||||
<span v-else @click="row.editor = true" v-html="row.option"></span>
|
||||
</td>
|
||||
<td v-for="col in element.options[1]" :key="col.option">
|
||||
<!-- 编辑状态,单次点击出输入框,失焦后关闭 -->
|
||||
<input
|
||||
:id="col.option" :type="tableInputTypeMapping()" :name="row.option"
|
||||
@change="handleColNameChange(col.option!, row.option!,$event)"
|
||||
:id="col.option"
|
||||
:type="tableInputTypeMapping()"
|
||||
:name="row.option"
|
||||
@change="handleColNameChange(col.option!, row.option!, $event)"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, useTemplateRef } from 'vue';
|
||||
import { computed, onMounted, ref, useTemplateRef, toRefs } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
element: any;
|
||||
active: boolean;
|
||||
index: number;
|
||||
}>();
|
||||
|
||||
const { element, active } = toRefs(props);
|
||||
|
||||
const signatureCanvas = useTemplateRef('signatureCanvas');
|
||||
|
||||
const canvasWidth = ref(100);
|
||||
@@ -63,6 +72,9 @@ onMounted(() => {
|
||||
|
||||
// 触摸开始,开始绘制适用于移动设备
|
||||
signatureCanvas.value?.addEventListener('touchstart', (e) => {
|
||||
if (!active.value) {
|
||||
return;
|
||||
}
|
||||
// 防止页面滚动
|
||||
e.preventDefault();
|
||||
isDrawing = true;
|
||||
@@ -150,25 +162,51 @@ const undo = () => {
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
}
|
||||
};
|
||||
|
||||
const emit = defineEmits(['update:element']);
|
||||
const emitValue = () => {
|
||||
emit('update:element', element.value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<van-cell>
|
||||
<div class="sign-question">
|
||||
<canvas
|
||||
ref="signatureCanvas"
|
||||
:width="canvasWidth"
|
||||
:height="canvasHeight"
|
||||
style="border: 1px solid #ccc; border-radius: 4px"
|
||||
>
|
||||
</canvas>
|
||||
<div class="sign-text" :class="{ show: showSignText }">
|
||||
<span @click="clearCanvas">清空</span>
|
||||
<span @click="undo">撤销</span>
|
||||
<span @click="togglePen">{{ isEraser ? '画笔' : '橡皮擦' }}</span>
|
||||
<span @click="saveCanvas">完成并上传</span>
|
||||
</div>
|
||||
</div>
|
||||
<van-field
|
||||
:label="element.stem"
|
||||
:required="element.config.is_required === 1"
|
||||
label-align="top"
|
||||
:border="false"
|
||||
readonly
|
||||
class="base-select"
|
||||
>
|
||||
<template #left-icon>
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
<template #label>
|
||||
<contenteditable
|
||||
v-model="element.stem"
|
||||
:active="active"
|
||||
@blur="emitValue"
|
||||
></contenteditable>
|
||||
</template>
|
||||
<template #input>
|
||||
<div class="sign-question">
|
||||
<canvas
|
||||
ref="signatureCanvas"
|
||||
:width="canvasWidth"
|
||||
:height="canvasHeight"
|
||||
style="border: 1px solid #ccc; border-radius: 4px"
|
||||
>
|
||||
</canvas>
|
||||
<div class="sign-text" :class="{ show: showSignText }">
|
||||
<span @click="clearCanvas">清空</span>
|
||||
<span @click="undo">撤销</span>
|
||||
<span @click="togglePen">{{ isEraser ? '画笔' : '橡皮擦' }}</span>
|
||||
<span @click="saveCanvas">完成并上传</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</van-field>
|
||||
</van-cell>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user