feat[preview]: 抽离矩阵的验证内容
- 抽离 preview 矩阵的验证内容,多选矩阵的校验放到 PreviewMatrixCheckbox 内 - 添加新的错误提示支持 - 增加 矩阵 类型 - 添加矩阵测试文件
This commit is contained in:
4
src/types/question.d.ts
vendored
4
src/types/question.d.ts
vendored
@@ -34,7 +34,7 @@ declare interface IQuestionOption {
|
||||
relation_last_scope: number;
|
||||
relation_first_scope: number;
|
||||
relation_question_index: number;
|
||||
options?: questionOptionType[];
|
||||
options?: IMatrixCheckboxOption[];
|
||||
}
|
||||
|
||||
// 答案 config 类型
|
||||
@@ -89,7 +89,7 @@ export declare interface IQuestion<QuestionConfig> {
|
||||
stem?: string;
|
||||
other?: string;
|
||||
// options 列表项,第一个是默认
|
||||
list: questionOptionType[];
|
||||
list: questionOptionType[] | IQuestionOption[];
|
||||
question_index?: number;
|
||||
question_type?: number;
|
||||
// 如果没有自定义类型,那么就直接用基础 config 类型
|
||||
|
||||
70
src/types/questions/matrixCheckbox.ts
Normal file
70
src/types/questions/matrixCheckbox.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
export declare interface IMatrixCheckboxConfig extends IBaseConfig {
|
||||
/** 是否必选 (1-是 0-否) */
|
||||
is_required: 0 | 1;
|
||||
/** 是否交换行列 */
|
||||
is_change_row_cell?: boolean;
|
||||
/** 暂未得知 */
|
||||
quick_type: 0 | 1;
|
||||
/** 每题选项数 */
|
||||
each_number: number;
|
||||
/** 最大选择数 (空字符串表示无限制) */
|
||||
max_select: number | '';
|
||||
/** 最小选择数 (空字符串表示无限制) */
|
||||
min_select: number | '';
|
||||
/** 随机选择数量 */
|
||||
select_random: number;
|
||||
}
|
||||
|
||||
export declare interface IMatrixCheckboxOption {
|
||||
/** 选项HTML内容 */
|
||||
option: string;
|
||||
/** 是否为"其他"选项 (0-否 1-是) */
|
||||
is_other: 0 | 1;
|
||||
/** 是否固定位置 (0-否 1-是) */
|
||||
is_fixed: 0 | 1;
|
||||
/** 是否移除其他选项 (0-否 1-是) */
|
||||
is_remove_other: 0 | 1;
|
||||
/** 层级深度 */
|
||||
level: number;
|
||||
/** 选项唯一键 */
|
||||
option_key: string;
|
||||
/** 选项索引(可能不连续) */
|
||||
option_index: string;
|
||||
/** 选项编码 */
|
||||
option_code: string;
|
||||
/** 选项配置 */
|
||||
option_config: IMatrixCheckboxOptionConfig;
|
||||
/** 父选项索引 */
|
||||
parent_option_index: number;
|
||||
/** 子选项(树形结构) */
|
||||
children: null;
|
||||
/** 最小选择数 */
|
||||
min_select: number | string;
|
||||
}
|
||||
|
||||
export declare interface IMatrixCheckboxOptionConfig {
|
||||
/** 选项类型 */
|
||||
type: number;
|
||||
/** 关联价格 */
|
||||
price: number;
|
||||
/** 标题 */
|
||||
title: string;
|
||||
/** 渐变样式 */
|
||||
gradient: string;
|
||||
/** 图片URL数组 */
|
||||
image_url: string[];
|
||||
/** 子区域配置 */
|
||||
child_area: null | {
|
||||
// 根据实际业务补充具体结构
|
||||
area_type?: number;
|
||||
coordinates?: number[];
|
||||
};
|
||||
/** 选项类型标记 */
|
||||
option_type: number;
|
||||
/** 说明内容数组 */
|
||||
instructions: string[];
|
||||
/** 绑定商品ID */
|
||||
binding_goods_id: string;
|
||||
/** 右侧限制内容(HTML) */
|
||||
limit_right_content: string;
|
||||
}
|
||||
@@ -9,13 +9,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { IQuestion } from '@/types/question';
|
||||
import type { IMatrixCheckboxConfig } from '@/types/questions/matrixCheckbox';
|
||||
|
||||
// 接受获取到的 col row 的索引参数
|
||||
const rowIndex = defineModel<number>('rowIndex', { required: true, default: 0 });
|
||||
const colIndex = defineModel<number>('colIndex', { required: true, default: 0 });
|
||||
|
||||
const rowRecord = defineModel<number[][]>('rowRecord', { required: false, default: () => [] });
|
||||
|
||||
const element = defineModel<question>('element', {
|
||||
const element = defineModel<IQuestion<IMatrixCheckboxConfig>>('element', {
|
||||
required: false,
|
||||
default: () => {
|
||||
/**/
|
||||
@@ -61,6 +64,7 @@ const emitValue = (/* val: unknown */) => {
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
@import '@/assets/css/main';
|
||||
|
||||
input[type='checkbox'] {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
@@ -73,9 +77,11 @@ input[type='checkbox'] {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: border-color 0.4s ease;
|
||||
|
||||
&:checked {
|
||||
border-color: $theme-color;
|
||||
background: $theme-color;
|
||||
|
||||
&::after {
|
||||
content: '\2713';
|
||||
font-family: 'Arial', sans-serif; // 确保符号正常显示
|
||||
|
||||
@@ -4,6 +4,8 @@ import { Io5EllipsisVerticalSharp } from 'vue-icons-plus/io5';
|
||||
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';
|
||||
import type { IQuestion } from '@/types/question';
|
||||
import type { IMatrixCheckboxConfig } from '@/types/questions/matrixCheckbox';
|
||||
|
||||
// 添加激活 action 的选项
|
||||
interface _questionOptionType extends questionOptionType {
|
||||
@@ -11,7 +13,7 @@ interface _questionOptionType extends questionOptionType {
|
||||
}
|
||||
|
||||
// 题目
|
||||
const element = defineModel<question>('element', {
|
||||
const element = defineModel<IQuestion<IMatrixCheckboxConfig>>('element', {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {};
|
||||
@@ -41,6 +43,7 @@ const emitValue = () => {
|
||||
|
||||
// 组件选择
|
||||
const activeComponent = selectActiveComponent();
|
||||
|
||||
function selectActiveComponent(): Component {
|
||||
switch (element.value.question_type) {
|
||||
case 8:
|
||||
@@ -87,6 +90,7 @@ function handleActionSelect(action: { text: string }, axi: any, type: 'row' | 'c
|
||||
}
|
||||
|
||||
addShowActionOption();
|
||||
|
||||
/**
|
||||
* 给行或者列选项 添加 showAction 选项
|
||||
*/
|
||||
@@ -113,7 +117,7 @@ const errorMessage = defineModel('errorMessage', {
|
||||
label-align="top"
|
||||
class="contenteditable-question-title"
|
||||
>
|
||||
<template #left-icon> {{ isPreview ? element.title : index + 1 }}. </template>
|
||||
<template #left-icon> {{ isPreview ? element.title : index + 1 }}.</template>
|
||||
<!-- 使用 title 插槽来自定义标题 -->
|
||||
<template #label>
|
||||
<contenteditable
|
||||
@@ -220,6 +224,7 @@ input[type='text'] {
|
||||
//top: 15%;
|
||||
//right: 0;
|
||||
color: #606266;
|
||||
|
||||
& > svg {
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
@@ -539,6 +539,7 @@ import PreviewCheckbox from '@/views/Survey/views/Preview/components/questions/P
|
||||
import PreviewRate from '@/views/Survey/views/Preview/components/questions/PreviewRate.vue';
|
||||
import PreviewSign from '@/views/Survey/views/Preview/components/questions/PreviewSign.vue';
|
||||
import PreviewTextWithImages from '@/views/Survey/views/Preview/components/questions/PreviewTextWithImages.vue';
|
||||
|
||||
const isPreview = defineModel('isPreview', {
|
||||
type: Boolean,
|
||||
default: true
|
||||
@@ -697,86 +698,6 @@ async function answer(callback, callbackBeforePage) {
|
||||
question.error = translatedText.value.PleaseInputAValue;
|
||||
} else if (answer && questionType === 2) {
|
||||
} else if (answer && questionType === 10) {
|
||||
// 矩阵多选题,列分组时,校验选项数量
|
||||
const cellGroups = (config?.cell_option_groups?.option_group || []).filter(
|
||||
(i) => i.groups?.length
|
||||
);
|
||||
if (cellGroups.length) {
|
||||
const rows = question.list.reduce(
|
||||
(p, c) => [...p, ...(c.type === 1 ? c.options || [] : [])],
|
||||
[]
|
||||
);
|
||||
const cols = question.list.reduce(
|
||||
(p, c) => [...p, ...(c.type === 2 ? c.options || [] : [])],
|
||||
[]
|
||||
);
|
||||
const freeCols = cols.filter(
|
||||
(col) => !cellGroups.some((g) => g.groups.find((c) => c.option_key === col.option_key))
|
||||
);
|
||||
|
||||
const arr = rows.map((row) => {
|
||||
return cellGroups.map((group) => {
|
||||
return group.groups.map((col) => {
|
||||
return `${row.option_key}_${col.option_key}`;
|
||||
});
|
||||
});
|
||||
});
|
||||
arr.forEach((a, idx) =>
|
||||
a.push(freeCols.map((col) => `${rows[idx].option_key}_${col.option_key}`))
|
||||
);
|
||||
const answered = Object.keys(answer);
|
||||
|
||||
cellGroups.forEach((group, idx) => {
|
||||
if (!group.groups?.length || (!group.max && !group.min) || isError) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < arr.length; i += 1) {
|
||||
const row = arr[i];
|
||||
const selectedCount = row[idx].filter((key) => answered.includes(key)).length;
|
||||
if (group.min && selectedCount < group.min) {
|
||||
isError = true;
|
||||
question.error = translatedText.value.PleaseSelectAtLeastOneOptionsInTitleGroup(
|
||||
group.min,
|
||||
group.title
|
||||
);
|
||||
break;
|
||||
}
|
||||
if (group.max && selectedCount > group.max) {
|
||||
isError = true;
|
||||
question.error = translatedText.value.PleaseSelectAtMostOneOptionsInTitleGroup(
|
||||
group.max,
|
||||
group.title
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// 最少选择数量校验
|
||||
const minSelect = +config.min_select || 0;
|
||||
const perLineSelectedCount = Object.keys(answer).reduce(
|
||||
(p, c) => {
|
||||
if (+answer[c]) {
|
||||
p[+c.split('_')[0] - 1] += 1;
|
||||
}
|
||||
return p;
|
||||
},
|
||||
question.list.filter((i) => i.type === 1).flatMap((i) => i.options.map(() => 0))
|
||||
);
|
||||
if (minSelect && minSelect > Math.max(...perLineSelectedCount)) {
|
||||
if (question.config.is_change_row_cell) {
|
||||
question.error = translatedText.value.PleaseSelectAtLeastOneOptionsPerColumn(
|
||||
config.min_select || 1
|
||||
);
|
||||
} else {
|
||||
question.error = translatedText.value.PleaseSelectAtLeastOneOptionsPerLine(
|
||||
config.min_select || 1
|
||||
);
|
||||
}
|
||||
isError = true;
|
||||
}
|
||||
// console.log('===', minSelect, Object.keys(answer), answer, perLineSelectedCount);
|
||||
} else if (answer && questionType === 12) {
|
||||
question.error = '';
|
||||
} else if (answer && questionType === 14 && Object.keys(answer).length < config.min_select) {
|
||||
@@ -1301,6 +1222,7 @@ function updateAnswer(auto) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 选项隐藏
|
||||
function hideOptions(hide) {
|
||||
const questionIndex = hide?.question_index;
|
||||
@@ -1353,6 +1275,7 @@ function toUrl(url) {
|
||||
open(modifiedUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-enable */
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,18 +14,21 @@
|
||||
<script setup lang="ts">
|
||||
import MatrixQuestion from '@/views/Design/components/Questions/MatrixQuestion.vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
// const questionType = defineModel<number>('questionType', { required: false });
|
||||
import type { IQuestion, IQuestionOption } from '@/types/question';
|
||||
import { validateMatrixCheckbox } from '@/views/Survey/views/Preview/components/questions/validate/validateMatrixCheckbox';
|
||||
import type { MatrixCheckboxAnswerType } from '@/views/Survey/views/Preview/components/questions/types/previewMatrix';
|
||||
import type { IMatrixCheckboxConfig } from '@/types/questions/matrixCheckbox';
|
||||
|
||||
// 矩阵多选的答案类型
|
||||
type answerType = {
|
||||
[key: string]: 1;
|
||||
};
|
||||
// const questionType = defineModel<number>('questionType', { required: false });
|
||||
|
||||
// preview props
|
||||
// const stem = defineModel('stem');
|
||||
// const list = defineModel<questionsList[]>('list', { required: false });
|
||||
// const config = defineModel<OptionConfigType>('config', { required: false });
|
||||
const question = defineModel<question>('question', { default: () => {} });
|
||||
const question = defineModel<IQuestion<IMatrixCheckboxConfig>>('question', {
|
||||
default: () => {}
|
||||
});
|
||||
console.log(`question value: `, question.value);
|
||||
const answerIndex = computed(() => question.value.title);
|
||||
const emit = defineEmits(['changeAnswer', 'previous', 'next']);
|
||||
// 示例
|
||||
@@ -35,7 +38,7 @@ const emit = defineEmits(['changeAnswer', 'previous', 'next']);
|
||||
// "2_1": 1,
|
||||
// "2_2": 1
|
||||
// }
|
||||
const answer = defineModel<answerType>('answer', {
|
||||
const answer = defineModel<MatrixCheckboxAnswerType>('answer', {
|
||||
// 临时赋值, 用于测试
|
||||
// default: () => ({
|
||||
// "1_1": 1,
|
||||
@@ -63,7 +66,7 @@ answer.value && parseAnswer(answer.value);
|
||||
/**
|
||||
* 解析 answer
|
||||
*/
|
||||
function parseAnswer(answer: answer) {
|
||||
function parseAnswer(answer: MatrixCheckboxAnswerType) {
|
||||
// console.log(`come in parseAnswer`);
|
||||
const rowRecordList: number[][] = [];
|
||||
Object.entries(answer).forEach(([key]) => {
|
||||
@@ -75,6 +78,7 @@ function parseAnswer(answer: answer) {
|
||||
|
||||
return rowRecordList;
|
||||
}
|
||||
|
||||
// 查看parseAnswer的返回值
|
||||
// console.log(`parseAnswer value:`, parseAnswer(answer.value!))
|
||||
|
||||
@@ -88,15 +92,32 @@ const cols = computed(() => question.value?.list[1]?.options ?? []);
|
||||
|
||||
watch(
|
||||
rowRecord,
|
||||
() => {
|
||||
(value) => {
|
||||
// console.log(`record has changed`, rowRecord.value);
|
||||
// 重新生成 answer
|
||||
const newAnswer: answer = {};
|
||||
const newAnswer = {} as MatrixCheckboxAnswerType;
|
||||
rowRecord.value.forEach((rowOptions, rowIndex) => {
|
||||
rowOptions.forEach((colIndex) => {
|
||||
newAnswer[`${rowIndex + 1}_${colIndex + 1}`] = 1;
|
||||
const key = `${rowIndex + 1}_${colIndex + 1}`;
|
||||
newAnswer[key] = 1;
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 校验
|
||||
*/
|
||||
const res: string = validateMatrixCheckbox(
|
||||
value,
|
||||
question.value.config!,
|
||||
question.value.list as IQuestionOption[]
|
||||
);
|
||||
console.log('res', res);
|
||||
question.value.error = res;
|
||||
// 如果校验失败,清空 answer
|
||||
if (res.length) {
|
||||
question.value.answer = undefined;
|
||||
return;
|
||||
}
|
||||
answer.value = newAnswer;
|
||||
emit('changeAnswer', newAnswer);
|
||||
},
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export type MatrixCheckboxAnswerType = { [key: string]: 1 | number };
|
||||
s;
|
||||
@@ -0,0 +1,179 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { validateMatrixCheckbox } from '../validateMatrixCheckbox';
|
||||
import { getLanguage } from '@/views/Survey/views/Preview/js/language';
|
||||
import type { ITranslatedText } from '@/views/Survey/views/Preview/components/questions/types/translatedText';
|
||||
|
||||
// 模拟翻译函数
|
||||
const mockTranslatedText: ITranslatedText = getLanguage(['zh']);
|
||||
|
||||
describe('validateMatrixCheckbox 函数测试', () => {
|
||||
// 基础测试数据
|
||||
const baseOptions = [
|
||||
{
|
||||
type: 1, // 行选项
|
||||
options: [
|
||||
{ option_key: 'row1', option: '行1' },
|
||||
{ option_key: 'row2', option: '行2' },
|
||||
{ option_key: 'row3', option: '行3' }
|
||||
],
|
||||
cite_type: 0,
|
||||
relation_type: 0,
|
||||
relation_out_scope: [],
|
||||
relation_last_scope: 0,
|
||||
relation_first_scope: 0,
|
||||
relation_question_index: 0
|
||||
},
|
||||
{
|
||||
type: 2, // 列选项
|
||||
options: [
|
||||
{ option_key: 'col1', option: '列1' },
|
||||
{ option_key: 'col2', option: '列2' },
|
||||
{ option_key: 'col3', option: '列3' }
|
||||
],
|
||||
cite_type: 0,
|
||||
relation_type: 0,
|
||||
relation_out_scope: [],
|
||||
relation_last_scope: 0,
|
||||
relation_first_scope: 0,
|
||||
relation_question_index: 0
|
||||
}
|
||||
];
|
||||
|
||||
const baseConfig = {
|
||||
is_required: 1,
|
||||
quick_type: 0,
|
||||
each_number: 3,
|
||||
max_select: 3,
|
||||
min_select: 1,
|
||||
select_random: 0,
|
||||
is_change_row_cell: false,
|
||||
float_window_content: '',
|
||||
is_show: [],
|
||||
is_brand: 0,
|
||||
is_behavior: 0,
|
||||
is_initialize: 0,
|
||||
is_price_tag: 0,
|
||||
is_three_dimensions: 0,
|
||||
is_default_perspective: 0,
|
||||
popup_window: 0,
|
||||
popup_window_content: ''
|
||||
};
|
||||
|
||||
it('当选项为空数组时,应返回空字符串', () => {
|
||||
const answer = [];
|
||||
const result = validateMatrixCheckbox(answer, baseConfig, [], mockTranslatedText);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('当所有行都满足最小选择数要求时,应返回空字符串', () => {
|
||||
const answer = [
|
||||
[1], // 第1行选择了1个
|
||||
[1, 2], // 第2行选择了2个
|
||||
[1, 2, 3] // 第3行选择了3个
|
||||
];
|
||||
const result = validateMatrixCheckbox(answer, baseConfig, baseOptions, mockTranslatedText);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('当某行选择数小于最小要求时,应返回相应错误信息', () => {
|
||||
const answer = [
|
||||
[1], // 第1行选择了1个
|
||||
[], // 第2行没有选择
|
||||
[1, 2] // 第3行选择了2个
|
||||
];
|
||||
const result = validateMatrixCheckbox(answer, baseConfig, baseOptions, mockTranslatedText);
|
||||
expect(result).toBe('第2行最少选1个。');
|
||||
});
|
||||
|
||||
it('当某行选择数超过最大要求时,应返回相应错误信息', () => {
|
||||
const config = { ...baseConfig, max_select: 2 };
|
||||
const answer = [
|
||||
[1], // 第1行选择了1个
|
||||
[1, 2], // 第2行选择了2个
|
||||
[1, 2, 3] // 第3行选择了3个,超过了最大值2
|
||||
];
|
||||
const result = validateMatrixCheckbox(answer, config, baseOptions, mockTranslatedText);
|
||||
expect(result).toBe('第3行最多选2个。');
|
||||
});
|
||||
|
||||
it('当设置is_change_row_cell=true时,应返回列相关的错误信息', () => {
|
||||
const config = { ...baseConfig, is_change_row_cell: true };
|
||||
const answer = [
|
||||
[1], // 第1行选择了1个
|
||||
[], // 第2行没有选择
|
||||
[1, 2] // 第3行选择了2个
|
||||
];
|
||||
const result = validateMatrixCheckbox(answer, config, baseOptions, mockTranslatedText);
|
||||
expect(result).toBe('第2列最少选1个。');
|
||||
});
|
||||
|
||||
it('当min_select为0时,不应进行最小值校验', () => {
|
||||
const config = { ...baseConfig, min_select: 0 };
|
||||
const answer = [
|
||||
[], // 第1行没有选择
|
||||
[], // 第2行没有选择
|
||||
[] // 第3行没有选择
|
||||
];
|
||||
const result = validateMatrixCheckbox(answer, config, baseOptions, mockTranslatedText);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('当min_select为空字符串时,应将其转换为0并且不进行最小值校验', () => {
|
||||
const config = { ...baseConfig, min_select: '' as any };
|
||||
const answer = [
|
||||
[], // 第1行没有选择
|
||||
[], // 第2行没有选择
|
||||
[] // 第3行没有选择
|
||||
];
|
||||
const result = validateMatrixCheckbox(answer, config, baseOptions, mockTranslatedText);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('当max_select为0或空字符串时,不应进行最大值校验', () => {
|
||||
const config1 = { ...baseConfig, max_select: 0 };
|
||||
const config2 = { ...baseConfig, max_select: '' as any };
|
||||
const answer = [
|
||||
[1, 2, 3, 4, 5], // 第1行选择了5个
|
||||
[1, 2, 3, 4], // 第2行选择了4个
|
||||
[1, 2, 3] // 第3行选择了3个
|
||||
];
|
||||
|
||||
const result1 = validateMatrixCheckbox(answer, config1, baseOptions, mockTranslatedText);
|
||||
const result2 = validateMatrixCheckbox(answer, config2, baseOptions, mockTranslatedText);
|
||||
|
||||
expect(result1).toBe('');
|
||||
expect(result2).toBe('');
|
||||
});
|
||||
|
||||
it('当有多行不满足条件时,应返回第一个检测到的错误', () => {
|
||||
const config = { ...baseConfig, min_select: 2, max_select: 3 };
|
||||
const answer = [
|
||||
[1], // 第1行选择不足
|
||||
[], // 第2行选择不足
|
||||
[1, 2, 3, 4] // 第3行选择过多
|
||||
];
|
||||
|
||||
const result = validateMatrixCheckbox(answer, config, baseOptions, mockTranslatedText);
|
||||
expect(result).toBe('第1行最少选2个。');
|
||||
});
|
||||
|
||||
it('当min_select和max_select都有限制时,应先检查最小值再检查最大值', () => {
|
||||
const config = { ...baseConfig, min_select: 2, max_select: 3 };
|
||||
|
||||
// 测试最小值不满足的情况
|
||||
const answer1 = [
|
||||
[1], // 第1行选择不足
|
||||
[1, 2, 3, 4] // 第2行选择过多
|
||||
];
|
||||
const result1 = validateMatrixCheckbox(answer1, config, baseOptions, mockTranslatedText);
|
||||
expect(result1).toBe('第1行最少选2个。');
|
||||
|
||||
// 测试最大值不满足的情况
|
||||
const answer2 = [
|
||||
[1, 2], // 第1行满足最小值
|
||||
[1, 2, 3, 4] // 第2行选择过多
|
||||
];
|
||||
const result2 = validateMatrixCheckbox(answer2, config, baseOptions, mockTranslatedText);
|
||||
expect(result2).toBe('第2行最多选3个。');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,117 @@
|
||||
import { getLanguage } from '@/views/Survey/views/Preview/js/language';
|
||||
import type { IQuestionOption } from '@/types/question';
|
||||
import type { IMatrixCheckboxConfig } from '@/types/questions/matrixCheckbox';
|
||||
|
||||
/**
|
||||
* 矩阵多选题校验
|
||||
* @param answer 答案
|
||||
* @param config 配置
|
||||
* @param options 选项
|
||||
* @param translatedText 语言
|
||||
*/
|
||||
function validateMatrixCheckbox(
|
||||
answer: number[][],
|
||||
config: IMatrixCheckboxConfig,
|
||||
options: IQuestionOption[],
|
||||
translatedText = getLanguage(['zh'])
|
||||
) {
|
||||
const [rows, cols] = options;
|
||||
// 矩阵多选题,列选项分组时,校验选项数量
|
||||
// const cellGroups = (config?.cell_option_groups?.option_group || []).filter(
|
||||
// (i) => i.groups?.length
|
||||
// );
|
||||
// if (cellGroups.length) {
|
||||
// const rows = question.list.reduce(
|
||||
// (p, c) => [...p, ...(c.type === 1 ? c.options || [] : [])],
|
||||
// []
|
||||
// );
|
||||
// const cols = question.list.reduce(
|
||||
// (p, c) => [...p, ...(c.type === 2 ? c.options || [] : [])],
|
||||
// []
|
||||
// );
|
||||
// const freeCols = cols.filter(
|
||||
// (col) => !cellGroups.some((g) => g.groups.find((c) => c.option_key === col.option_key))
|
||||
// );
|
||||
//
|
||||
// const arr = rows.map((row) => {
|
||||
// return cellGroups.map((group) => {
|
||||
// return group.groups.map((col) => {
|
||||
// return `${row.option_key}_${col.option_key}`;
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// arr.forEach((a, idx) =>
|
||||
// a.push(freeCols.map((col) => `${rows[idx].option_key}_${col.option_key}`))
|
||||
// );
|
||||
// const answered = Object.keys(answer);
|
||||
//
|
||||
// cellGroups.forEach((group, idx) => {
|
||||
// if (!group.groups?.length || (!group.max && !group.min) || isError) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// for (let i = 0; i < arr.length; i += 1) {
|
||||
// const row = arr[i];
|
||||
// const selectedCount = row[idx].filter((key) => answered.includes(key)).length;
|
||||
// if (group.min && selectedCount < group.min) {
|
||||
// isError = true;
|
||||
// question.error = translatedText.value.PleaseSelectAtLeastOneOptionsInTitleGroup(
|
||||
// group.min,
|
||||
// group.title
|
||||
// );
|
||||
// break;
|
||||
// }
|
||||
// if (group.max && selectedCount > group.max) {
|
||||
// isError = true;
|
||||
// question.error = translatedText.value.PleaseSelectAtMostOneOptionsInTitleGroup(
|
||||
// group.max,
|
||||
// group.title
|
||||
// );
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// 最少选择数量校验
|
||||
// 转换配置值为数字
|
||||
const minSelect = +config.min_select;
|
||||
const maxSelect = +config.max_select;
|
||||
// 如果选项为空,直接返回空字符串
|
||||
if (!options.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// 错误信息
|
||||
let errorMessage = '';
|
||||
|
||||
// 最大和最小选择数量校验
|
||||
answer.forEach((row, rowIndex) => {
|
||||
// 如果没有最小选择要求或最小选择为0,不进行最小值校验
|
||||
if (minSelect >= 0 && row.length < minSelect) {
|
||||
// 如果已经有错误信息,不进行最小值校验
|
||||
if (errorMessage) return;
|
||||
// 是否行列转换
|
||||
errorMessage = config.is_change_row_cell
|
||||
? translatedText.PleaseSelectAtLeastOneOptionsPerColumn(minSelect, rowIndex)
|
||||
: translatedText.PleaseSelectAtLeastOneOptionsPerLine(minSelect, rowIndex);
|
||||
}
|
||||
|
||||
// 如果有最大选择限制,进行最大值校验
|
||||
if (maxSelect > 0 && row.length > maxSelect) {
|
||||
// 如果已经有错误信息,不进行最大值校验
|
||||
if (errorMessage) return;
|
||||
// 是否行列转换
|
||||
errorMessage = config.is_change_row_cell
|
||||
? translatedText.PleaseSelectLessOptionsPerColumn(maxSelect, rowIndex)
|
||||
: translatedText.PleaseSelectLessOptionsPerLine(maxSelect, rowIndex);
|
||||
}
|
||||
});
|
||||
|
||||
// 检测所有的答案是否正常选择
|
||||
if (errorMessage.length === 0 && rows.options!.length > answer.length) {
|
||||
errorMessage = translatedText.PleaseSelectAllRows;
|
||||
}
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
export { validateMatrixCheckbox };
|
||||
@@ -5,13 +5,36 @@ export const language = {
|
||||
},
|
||||
|
||||
PleaseSelectAtLeastOneOptionsPerLine: {
|
||||
en: (count) => `Please select at least ${count} answer option${count > 1 ? 's' : ''} per row.`,
|
||||
zh: (count) => `每行最少选${count}个。`
|
||||
en: (count, row) =>
|
||||
typeof row === 'number'
|
||||
? `Please select at least ${count} answer option${count > 1 ? 's' : ''} at row ${row + 1}.`
|
||||
: `Please select at least ${count} answer option${count > 1 ? 's' : ''} per row.`,
|
||||
zh: (count, row) =>
|
||||
typeof row === 'number' ? `第${row + 1}行最少选${count}个。` : `每行最少选${count}个。`
|
||||
},
|
||||
PleaseSelectLessOptionsPerLine: {
|
||||
en: (count, row) =>
|
||||
typeof row === 'number'
|
||||
? `Please select no more than ${count} answer option${count > 1 ? 's' : ''} at row ${row + 1}.`
|
||||
: `Please select no more than ${count} answer option${count > 1 ? 's' : ''} per row.`,
|
||||
zh: (count, row) =>
|
||||
typeof row === 'number' ? `第${row + 1}行最多选${count}个。` : `每行最多选${count}个。`
|
||||
},
|
||||
PleaseSelectAtLeastOneOptionsPerColumn: {
|
||||
en: (count) =>
|
||||
`Please select at least ${count} answer option${count > 1 ? 's' : ''} per column.`,
|
||||
zh: (count) => `每列最少选${count}个。`
|
||||
en: (count, column) =>
|
||||
typeof column === 'number'
|
||||
? `Please select at least ${count} answer option${count > 1 ? 's' : ''} at column ${column + 1}.`
|
||||
: `Please select at least ${count} answer option${count > 1 ? 's' : ''} per column.`,
|
||||
zh: (count, column) =>
|
||||
typeof column === 'number' ? `第${column + 1}列最少选${count}个。` : `每列最少选${count}个。`
|
||||
},
|
||||
PleaseSelectLessOptionsPerColumn: {
|
||||
en: (count, column) =>
|
||||
typeof column === 'number'
|
||||
? `Please select no more than ${count} answer option${count > 1 ? 's' : ''} at column ${column + 1}.`
|
||||
: `Please select no more than ${count} answer option${count > 1 ? 's' : ''} per column.`,
|
||||
zh: (count, column) =>
|
||||
typeof column === 'number' ? `第${column + 1}列最多选${count}个。` : `每列最多选${count}个。`
|
||||
},
|
||||
PleaseCategorizeAllOptions: {
|
||||
en: 'Please categorize all answer options.',
|
||||
@@ -21,6 +44,10 @@ export const language = {
|
||||
en: 'This is compulsory.',
|
||||
zh: '请填写当前题目'
|
||||
},
|
||||
PleaseSelectAllRows: {
|
||||
en: 'Please select all rows.',
|
||||
zh: '请完成所有行。'
|
||||
},
|
||||
PleaseInputAValue: {
|
||||
en: 'Please input a value.',
|
||||
zh: '请输入。'
|
||||
|
||||
Reference in New Issue
Block a user