feat: 矩阵抽离组件

- 矩阵的三个组件抽离,由 MatrixQuestion 内部管理
This commit is contained in:
Huangzhe
2025-03-22 12:18:06 +08:00
parent 7727ea5b2b
commit d647cc3a02
10 changed files with 268 additions and 829 deletions

View File

@@ -49,12 +49,14 @@
></Completion> ></Completion>
<!-- 矩阵题 --> <!-- 矩阵题 -->
<martrix-question <matrix-question
v-if=" v-if="
element.question_type === 8 || element.question_type === 8 ||
element.question_type === 9 || element.question_type === 9 ||
element.question_type === 10 element.question_type === 10
" "
v-model:cols="element.options[1]"
v-model:rows="element.options[0]"
:element="computedElement(element)" :element="computedElement(element)"
:index="index" :index="index"
:active="chooseQuestionId === element.id" :active="chooseQuestionId === element.id"
@@ -149,7 +151,7 @@ import Choice from './components/Questions/Choice.vue';
import ChooseQuestion from './components/ChooseQuestion.vue'; import ChooseQuestion from './components/ChooseQuestion.vue';
import Paging from './components/Questions/paging/Paging.vue'; import Paging from './components/Questions/paging/Paging.vue';
import Completion from './components/Questions/Completion.vue'; import Completion from './components/Questions/Completion.vue';
import MartrixQuestion from './components/Questions/MartrixQuestion.vue'; import MatrixQuestion from './components/Questions/MatrixQuestion.vue';
import Rate from './components/Questions/Rate.vue'; import Rate from './components/Questions/Rate.vue';
import TextWithImages from '@/views/Design/components/Questions/TextWithImages.vue'; import TextWithImages from '@/views/Design/components/Questions/TextWithImages.vue';
import SignQuestion from './components/Questions/SignQuestion.vue'; import SignQuestion from './components/Questions/SignQuestion.vue';

View File

@@ -1,114 +0,0 @@
<script setup lang="ts">
import { type Component, computed, defineModel } 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 question = defineModel<question>('element', {
type: Object,
default: () => {
return {};
}
});
const rowRecord = defineModel<unknown[]>('rowRecord', { required: false, default: () => [] });
const isPreview = defineModel<boolean>('isPreview', { required: false, default: 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 = defineModel<questionOptionType[]>('rows', { required: false, default: () => [] });
rows.value.length < 1 && (rows.value = question.value?.options[0] ?? []);
// 列标签
const cols = defineModel<questionOptionType[]>('cols', { required: false, default: () => [] });
cols.value.length < 1 && (cols.value = question.value?.options[1] ?? []);
// console.log(rows.value, cols.value);
// const columnLabels = useTemplateRef<HTMLElement[]>('columnLabels');
// 注意, element.options 里面的东西是数组,第一项内容是行标签内容,第二项内容是列标签内容
// 类型 AI 生成 切勿盲目相信,以实际为准
/* const props = */
defineProps<{
index: number;
active: boolean;
}>();
const emit = defineEmits(['update:element']);
const emitValue = () => {
// console.log(question.value);
emit('update:element', question.value);
};
const errorMessage = defineModel('errorMessage', {
type: String,
default: ''
});
</script>
<template>
<van-field
v-model="question.stem"
:label="question.stem"
:required="question.config.is_required === 1"
label-align="top"
class="contenteditable-question-title"
>
<template #left-icon>
{{ index + 1 }}
</template>
<!-- 使用 title 插槽来自定义标题 -->
<template #label>
<contenteditable
v-model="question.stem"
:active="active"
className="contenteditable-label"
@blur="emitValue"
:errorMessage="errorMessage"
/>
</template>
<template #input>
<Component
:is="activeComponent"
v-model:rowRecord="rowRecord"
v-model:element="question"
v-model:rows="rows"
v-model:cols="cols"
:is-preview="isPreview"
:active="active"
style="overflow: scroll; width: 88vw"
/>
</template>
</van-field>
</template>
<style lang="scss" scoped>
.matrix-table {
width: 100%;
border-collapse: collapse;
color: black;
& > tbody {
overflow: scroll;
}
th,
td {
border-width: 0 0 1px;
text-align: center;
}
}
.td-input {
text-align: center;
}
</style>

View File

@@ -1,86 +1,25 @@
<template> <template>
<el-table :data="rows" style="width: 100%">
<el-table-column :width="tableWidth">
<template #header></template>
<template #default="{ row /*, column, $index*/ }">
<div style="position: relative">
<el-text truncated :style="{ width: `${tableWidth}px` }">
<contenteditable v-model="row.option" :active="active" @blur="emitValue" />
</el-text>
<van-popover
v-model="row.showAction"
placement="left-end"
trigger="click"
:actions="popoverActions"
@select="handleActionSelect($event, row, 'row')"
>
<template #reference>
<div v-if="active" class="icon">
<Io5EllipsisVerticalSharp />
</div>
</template>
</van-popover>
</div>
</template>
</el-table-column>
<el-table-column v-for="(col, colIndex) in cols" :key="col.option" :width="tableWidth">
<template #header>
<div style="position: relative">
<el-text truncated :style="{ width: `${tableWidth}px` }">
<contenteditable v-model="col.option" :active="active" @blur="emitValue" />
</el-text>
<van-popover
v-model="col.showAction"
placement="left-end"
trigger="click"
:actions="popoverActions"
@select="handleActionSelect($event, col, 'col')"
>
<template #reference>
<div v-if="active" class="icon">
<Io5EllipsisVerticalSharp />
</div>
</template>
</van-popover>
</div>
</template>
<template #default="{ /*row, column, */ $index: rowIndex }">
<input <input
type="checkbox" type="checkbox"
:name="`R${rowIndex + 1}`" :name="`R${rowIndex + 1}`"
:checked="isOptionChecked(rowIndex, colIndex)" :checked="isOptionChecked(rowIndex, colIndex)"
@change="handleMatrixCheckboxChange(rowIndex, colIndex)" @change="handleMatrixCheckboxChange(rowIndex, colIndex)"
/> />
</template>
</el-table-column>
</el-table>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Io5EllipsisVerticalSharp } from 'vue-icons-plus/io5'; // 接受获取到的 col row 的索引参数
const rowIndex = defineModel<number>('rowIndex', { required: true, default: 0 });
const colIndex = defineModel<number>('colIndex', { required: true, default: 0 });
// 添加激活 action 的选项
interface _questionOptionType extends questionOptionType {
showAction?: boolean;
}
// 记录行和列的索引
const rowRecord = defineModel<number[][]>('rowRecord', { required: false, default: () => [] }); const rowRecord = defineModel<number[][]>('rowRecord', { required: false, default: () => [] });
// const matrixAnswer = defineModel<{ [key: string]: 1 }>('matrixAnswer', { required: false, default: () => ({}) });
// 检查 rowRecord 是否存在
// console.log(`rowRecord:`, rowRecord.value);
const tableWidth = defineModel<number>('tableWidth', { required: false, default: 110 });
const active = defineModel<boolean>('active', { required: false, default: true });
/* const isPreview = */ defineModel<boolean>('isPreview', { required: false, default: false });
const rows = defineModel<_questionOptionType[]>('rows', { required: false, default: () => [] });
const cols = defineModel<_questionOptionType[]>('cols', { required: false, default: () => [] });
const element = defineModel<question>('element', { const element = defineModel<question>('element', {
required: false, required: false,
default: () => { default: () => {
/**/ /**/
} }
}); });
const emit = defineEmits(['update:matrixAnswer', 'update:rowRecord', 'update:element']); const emit = defineEmits(['update:matrixAnswer', 'update:rowRecord', 'update:element']);
// 判断是否选中 // 判断是否选中
@@ -95,45 +34,6 @@ const isOptionChecked = (rowIndex: number, colIndex: number): boolean => {
return rowRecord.value[rowIndex].includes(colIndex); return rowRecord.value[rowIndex].includes(colIndex);
}; };
addShowActionOption();
/**
* 给行或者列选项 添加 showAction 选项
*/
function addShowActionOption() {
rows.value.forEach((row) => {
row.showAction = true;
});
cols.value.forEach((col) => {
col.showAction = true;
});
}
// 增加 popover 的 actions
const popoverActions = [
{
text: '删除'
}
// {
// text: '置底'
// }
];
// popover 的事件
function handleActionSelect(action: { text: string }, axi: any, type: 'row' | 'col') {
const data = type === 'row' ? rows : cols;
const index = data.value.indexOf(axi);
if (index < 0) return;
switch (action.text) {
case '删除':
data.value.splice(index, 1);
break;
case '置底':
data.value.push(data.value.splice(index, 1)[0]);
break;
}
}
// 当 matrix radio 选中时,更新 rowRecord 和 matrixAnswer // 当 matrix radio 选中时,更新 rowRecord 和 matrixAnswer
function handleMatrixCheckboxChange(row: number, col: number) { function handleMatrixCheckboxChange(row: number, col: number) {
// 获取 colIndexArray // 获取 colIndexArray
@@ -158,49 +58,3 @@ const emitValue = (/* val: unknown */) => {
emit('update:element', element.value); emit('update:element', element.value);
}; };
</script> </script>
<style scoped lang="scss">
@import '@/assets/css/theme';
.matrix-table {
text-align: left;
}
.matrix-checkbox {
position: relative;
overflow: hidden;
width: 15px;
height: 15px;
border: 1px solid #f4f4f4;
border-radius: 4px;
outline: none;
cursor: pointer;
appearance: none; /* 去除默认样式 */
//transition: border-color 0.3s;
}
.matrix-checkbox:checked {
border-color: transparent; /* 选中时边框颜色 */
}
.matrix-checkbox:checked::before {
content: '\e728';
position: absolute;
width: 15px;
height: 15px;
background-color: $theme-color; /* 选中颜色 */
color: #fff;
line-height: 15px;
text-align: center;
}
.icon {
position: absolute;
top: 3px;
right: -10px;
& > svg {
height: 15px;
}
}
</style>

View File

@@ -1,225 +1,246 @@
<script setup lang="ts"> <script setup lang="ts">
// import MatrixRadio from '@/views/Design/components/Questions/MatrixRadio.vue'; import { type Component, defineModel } from 'vue';
// import MatrixText from '@/views/Design/components/Questions/MatrixText.vue'; import { Io5EllipsisVerticalSharp } from 'vue-icons-plus/io5';
// import MatrixCheckbox from '@/views/Design/components/Questions/MatrixCheckbox.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';
// import { useTemplateRef, ref, type Directive } from 'vue'; // 添加激活 action 的选项
// interface _questionOptionType extends questionOptionType {
// const columnLabels = useTemplateRef<HTMLElement[]>('columnLabels'); showAction?: boolean;
// }
// // 注意, element.options 里面的东西是数组,第一项内容是行标签内容,第二项内容是列标签内容
// // 类型 AI 生成 切勿盲目相信,以实际为准 // 题目
// const { element, index } = defineProps<{ element: MatrixSurveyQuestion; index: number }>(); const element = defineModel<question>('element', {
// type: Object,
// const rowRecord = new Array(element.options[0].length); default: () => {
// // matrix 答案 return {};
// const matrixAnswer = ref({ }
// question_index: index, });
// answer: {} as any
// }); // table 宽度
// const tableWidth = defineModel<number>('tableWidth', { required: false, default: 110 });
// /** // 行记录(相关答案的记录)
// * input 类型映射,里面自行处理逻辑返回对应类型 const rowRecord = defineModel<unknown[]>('rowRecord', { required: false, default: () => [] });
// * // remark: 填空内容 question_type 8 // 是否是预览状态
// * // remark: 单选打分矩阵 question_type 9 const isPreview = defineModel<boolean>('isPreview', { required: false, default: false });
// * // remark: 多选矩阵内容 question_type 10 // 行标签
// * @default 'radio' const rows = defineModel<_questionOptionType[]>('rows', { required: false, default: () => [] });
// */ // 列标签
// const tableInputTypeMapping = (/** regx?: any */) => { const cols = defineModel<_questionOptionType[]>('cols', { required: false, default: () => [] });
// switch (element.question_type) { // 题的序号
// case 8: const index = defineModel<number>('index', { required: false, default: 0 });
// return 'text'; // 是否是编辑状态
// case 9: const active = defineModel<boolean>('active', { required: false, default: false });
// return 'radio';
// case 10: const emit = defineEmits(['update:element']);
// return 'checkbox'; // 更新题
// default: const emitValue = () => {
// return 'radio'; // console.log(question.value);
// } emit('update:element', element.value);
// }; };
//
// /** // 组件选择
// * 自定义指令,用于在元素挂载后自动获取焦点 const activeComponent = selectActiveComponent();
// */ function selectActiveComponent(): Component {
// const vFocus: Directive = { console.log(`question_type`, element.value.question_type);
// mounted(el: HTMLInputElement) { switch (element.value.question_type) {
// el.focus(); case 8:
// } return MatrixText;
// }; case 9:
// return MatrixRadio;
// /** case 10:
// * row 的数值变动之后,触发的事件 return MatrixCheckbox;
// * @param {string} value default:
// * @return {void} return MatrixText;
// */ }
// function handleRowNameChange(/* value: string */) { }
// // if (!value) return;
// } console.log(`active component`, activeComponent);
// // 增加 popover 的 actions
// /** const popoverActions = [
// * col 的数值变动之后,触发的事件 {
// */ text: '删除'
// function handleColNameChange(rowOption: string, colOption: string, e: any) { }
// // if (!value) return; // 置底功能暂时屏蔽
// const col = element.options[0].findIndex((option) => { // {
// return option.option === colOption; // text: '置底'
// }); // }
// ];
// const row = element.options[1].findIndex((option) => {
// return option.option === rowOption; /**
// }); * popover 被选中的事件
// * @param action { text: string }
// // 不同的 question_type 的 matrix 问卷处理不同的结果 * @param axi { option: string, showAction: boolean }
// switch (element.question_type) { * @param type { 'row' | 'col' }
// case 8: { */
// // 获取输入框元素 function handleActionSelect(action: { text: string }, axi: any, type: 'row' | 'col') {
// const inputElement = e.target as HTMLInputElement; const data = type === 'row' ? rows : cols;
// // 如果没有获取到输入框元素,则直接返回 const index = data.value.indexOf(axi);
// if (!inputElement) return; if (index < 0) return;
// // 将输入框的值保存到 rowRecord 对应位置 switch (action.text) {
// rowRecord[col] = e!.target!.value; case '删除':
// // 清空 matrixAnswer 的 answer 属性 data.value.splice(index, 1);
// matrixAnswer.value.answer = {}; break;
// // 遍历所有行选项
// element.options[0].forEach((_, rowIndex) => { case '置底':
// // 获取当前行记录 data.value.push(data.value.splice(index, 1)[0]);
// const colOptions = rowRecord[rowIndex]; break;
// // 如果当前行有记录,则更新 matrixAnswer 的 answer 属性 }
// if (colOptions) { }
// matrixAnswer.value.answer[`R${rowIndex + 1}_C${col + 1}`] = colOptions;
// } addShowActionOption();
// }); /**
// break; * 给行或者列选项 添加 showAction 选项
// } */
// case 9: function addShowActionOption() {
// // 将选择的行索引加1后保存到 rowRecord 对应位置 rows.value.forEach((row) => {
// rowRecord[col] = row + 1; row.showAction = true;
// // 清空 matrixAnswer 的 answer 属性 });
// matrixAnswer.value.answer = {}; cols.value.forEach((col) => {
// // 遍历 rowRecord更新 matrixAnswer 的 answer 属性 col.showAction = true;
// rowRecord.forEach((row, index) => { });
// matrixAnswer.value.answer[`${index + 1}_${row}`] = 1; }
// });
// break; const errorMessage = defineModel('errorMessage', {
// case 10: type: String,
// // 将选择的行索引加1后添加到 rowRecord 对应位置的数组中 default: ''
// 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> </script>
<template> <template>
<van-cell> <van-field
v-model="element.stem"
:label="element.stem"
:required="element.config?.is_required === 1"
label-align="top"
class="contenteditable-question-title"
>
<template #left-icon>
{{ index + 1 }}
</template>
<!-- 使用 title 插槽来自定义标题 --> <!-- 使用 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 插槽来自定义标题 -->
<template #label> <template #label>
<table class="matrix-table"> <contenteditable
<thead> v-model="element.stem"
<tr> :active="active"
<!-- 第一行内容为空 --> @blur="emitValue"
<th></th> :errorMessage="errorMessage"
<!-- 第二行内容开始填充 -->
<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"
@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">
<!-- 编辑状态单次点击出输入框失焦后关闭 -->
<td>
<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)"
/>
</td>
</tr>
</tbody>
</table>
</template> </template>
</van-cell>
<template #input>
<el-table :data="rows" style="width: 90vw">
<el-table-column :width="tableWidth">
<template #header></template>
<template #default="{ row /*, column, $index*/ }">
<div style="position: relative">
<el-text truncated :style="{ width: `${tableWidth}px` }">
<contenteditable v-model="row.option" :active="active" @blur="emitValue" />
</el-text>
<van-popover
v-model="row.showAction"
placement="left-end"
trigger="click"
:actions="popoverActions"
@select="handleActionSelect($event, row, 'row')"
>
<template #reference>
<div v-if="active" class="icon">
<Io5EllipsisVerticalSharp />
</div>
</template>
</van-popover>
</div>
</template>
</el-table-column>
<el-table-column v-for="(col, colIndex) in cols" :key="col.option" :width="tableWidth">
<template #header>
<div style="position: relative">
<el-text truncated :style="{ width: `${tableWidth}px` }">
<contenteditable v-model="col.option" :active="active" @blur="emitValue" />
</el-text>
<van-popover
v-model="col.showAction"
placement="left-end"
trigger="click"
:actions="popoverActions"
@select="handleActionSelect($event, col, 'col')"
>
<template #reference>
<div v-if="active" class="icon">
<Io5EllipsisVerticalSharp />
</div>
</template>
</van-popover>
</div>
</template>
<template #default="{ /*row, column, */ $index: rowIndex }">
<component
:is="activeComponent"
:element="element"
:rowIndex="rowIndex"
:colIndex="colIndex"
v-model:rowRecord="rowRecord"
/>
</template>
</el-table-column>
</el-table>
</template>
</van-field>
</template> </template>
<style scoped lang="scss"> <style lang="scss">
@import '@/assets/css/theme';
.matrix-table { .matrix-table {
width: 100%;
border-collapse: collapse;
color: black;
th,
td {
padding: 8px;
border-width: 0 0 1px;
text-align: left; text-align: left;
}
} }
input[type='text'] { input[type='text'] {
width: 85%; width: 70%;
padding: 0 5px;
border: none;
border-radius: 4px;
outline: 1px solid #f4f4f4;
&:focus {
outline: 1px solid $theme-color;
}
}
.matrix-radio {
position: relative;
overflow: hidden;
width: 15px;
height: 15px;
border: 1px solid #f4f4f4;
border-radius: 50%;
outline: none;
cursor: pointer;
appearance: none; /* 去除默认样式 */
//transition: border-color 0.3s;
} }
.matrix-table-action { .matrix-radio:checked {
margin-top: 10px; border-color: transparent; /* 选中时边框颜色 */
}
.van-icon { .matrix-radio:checked::before {
color: lightgreen; content: '\e728';
font-size: 12px; position: absolute;
} width: 15px;
height: 15px;
background-color: $theme-color; /* 选中颜色 */
color: #fff;
line-height: 15px;
text-align: center;
}
.matrix-table-action-tool { .icon {
display: flex; position: absolute;
justify-content: flex-end; top: 3px;
right: -10px;
& > span { & > svg {
margin-right: 6px; height: 15px;
font-size: 16px;
}
} }
} }
</style> </style>

View File

@@ -1,75 +1,19 @@
<template> <template>
<el-table :data="rows" style="width: 100%">
<el-table-column :width="tableWidth">
<template #header></template>
<template #default="{ row /*, column, $index*/ }">
<div style="position: relative">
<el-text truncated :style="{ width: `${tableWidth}px` }">
<contenteditable v-model="row.option" :active="active" @blur="emitValue" />
</el-text>
<van-popover
v-model="row.showAction"
placement="left-end"
trigger="click"
:actions="popoverActions"
@select="handleActionSelect($event, row, 'row')"
>
<template #reference>
<div v-if="active" class="icon">
<Io5EllipsisVerticalSharp />
</div>
</template>
</van-popover>
</div>
</template>
</el-table-column>
<el-table-column v-for="(col, colIndex) in cols" :key="col.option" :width="tableWidth">
<template #header>
<div style="position: relative">
<el-text truncated :style="{ width: `${tableWidth}px` }">
<contenteditable v-model="col.option" :active="active" @blur="emitValue" />
</el-text>
<van-popover
v-model="col.showAction"
placement="left-end"
trigger="click"
:actions="popoverActions"
@select="handleActionSelect($event, col, 'col')"
>
<template #reference>
<div v-if="active" class="icon">
<Io5EllipsisVerticalSharp />
</div>
</template>
</van-popover>
</div>
</template>
<template #default="{ /*row, column, */ $index: rowIndex }">
<input <input
type="radio" type="radio"
:name="`R${rowIndex + 1}`" :name="`R${rowIndex + 1}`"
:checked="isOptionChecked(rowIndex, colIndex)" :checked="isOptionChecked(rowIndex, colIndex)"
@change="handleMatrixRadioChange(rowIndex, colIndex)" @change="handleMatrixRadioChange(rowIndex, colIndex)"
/> />
</template>
</el-table-column>
</el-table>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Io5EllipsisVerticalSharp } from 'vue-icons-plus/io5'; // 接受获取到的 col row 的索引参数
const rowIndex = defineModel('rowIndex', { required: true, default: 0 });
// 添加激活 action 的选项 const colIndex = defineModel('colIndex', { required: true, default: 0 });
interface _questionOptionType extends questionOptionType {
showAction?: boolean;
}
const rowRecord = defineModel<number[]>('rowRecord', { required: false, default: () => [] }); const rowRecord = defineModel<number[]>('rowRecord', { required: false, default: () => [] });
/* const isPreview = */ defineModel<boolean>('isPreview', { required: false, default: false });
const tableWidth = defineModel<number>('tableWidth', { required: false, default: 110 });
const rows = defineModel<_questionOptionType[]>('rows', { required: false, default: () => [] });
const cols = defineModel<_questionOptionType[]>('cols', { required: false, default: () => [] });
const active = defineModel<boolean>('active', { required: false, default: true });
const element = defineModel<question>('element', { const element = defineModel<question>('element', {
required: false, required: false,
default: () => { default: () => {
@@ -78,45 +22,6 @@ const element = defineModel<question>('element', {
}); });
const emit = defineEmits(['update:matrixAnswer', 'update:rowRecord', 'update:element']); const emit = defineEmits(['update:matrixAnswer', 'update:rowRecord', 'update:element']);
addShowActionOption();
/**
* 给行或者列选项 添加 showAction 选项
*/
function addShowActionOption() {
rows.value.forEach((row) => {
row.showAction = true;
});
cols.value.forEach((col) => {
col.showAction = true;
});
}
// 增加 popover 的 actions
const popoverActions = [
{
text: '删除'
}
// {
// text: '置底'
// }
];
// popover 的事件
function handleActionSelect(action: { text: string }, axi: any, type: 'row' | 'col') {
const data = type === 'row' ? rows : cols;
const index = data.value.indexOf(axi);
if (index < 0) return;
switch (action.text) {
case '删除':
data.value.splice(index, 1);
break;
case '置底':
data.value.push(data.value.splice(index, 1)[0]);
break;
}
}
// 判断是否选中 // 判断是否选中
const isOptionChecked = (rowIndex: number, colIndex: number): boolean => { const isOptionChecked = (rowIndex: number, colIndex: number): boolean => {
// console.log(`rowIndex: ${rowIndex}, colIndex: ${colIndex}`); // console.log(`rowIndex: ${rowIndex}, colIndex: ${colIndex}`);
@@ -140,49 +45,3 @@ const emitValue = () => {
emit('update:element', element.value); emit('update:element', element.value);
}; };
</script> </script>
<style scoped lang="scss">
@import '@/assets/css/theme';
.matrix-table {
text-align: left;
}
.matrix-radio {
position: relative;
overflow: hidden;
width: 15px;
height: 15px;
border: 1px solid #f4f4f4;
border-radius: 50%;
outline: none;
cursor: pointer;
appearance: none; /* 去除默认样式 */
//transition: border-color 0.3s;
}
.matrix-radio:checked {
border-color: transparent; /* 选中时边框颜色 */
}
.matrix-radio:checked::before {
content: '\e728';
position: absolute;
width: 15px;
height: 15px;
background-color: $theme-color; /* 选中颜色 */
color: #fff;
line-height: 15px;
text-align: center;
}
.icon {
position: absolute;
top: 3px;
right: -10px;
& > svg {
height: 15px;
}
}
</style>

View File

@@ -1,50 +1,4 @@
<template> <template>
<el-table :data="rows" style="width: 100%">
<el-table-column :width="tableWidth">
<template #header></template>
<template #default="{ row /*, column, $index*/ }">
<div style="position: relative">
<el-text truncated :style="{ width: `${tableWidth}px` }">
<contenteditable v-model="row.option" :active="active" @blur="emitValue" />
</el-text>
<van-popover
v-model="row.showAction"
placement="left-end"
trigger="click"
:actions="popoverActions"
@select="handleActionSelect($event, row, 'row')"
>
<template #reference>
<div v-if="active" class="icon">
<Io5EllipsisVerticalSharp />
</div>
</template>
</van-popover>
</div>
</template>
</el-table-column>
<el-table-column v-for="(col, colIndex) in cols" :key="col.option" :width="tableWidth">
<template #header>
<div style="position: relative">
<el-text truncated :style="{ width: `${tableWidth}px` }">
<contenteditable v-model="col.option" :active="active" @blur="emitValue" />
</el-text>
<van-popover
v-model="col.showAction"
placement="left-end"
trigger="click"
:actions="popoverActions"
@select="handleActionSelect($event, col, 'col')"
>
<template #reference>
<div v-if="active" class="icon">
<Io5EllipsisVerticalSharp />
</div>
</template>
</van-popover>
</div>
</template>
<template #default="{ /*row, column, */ $index: rowIndex }">
<input <input
type="text" type="text"
style="width: 100%" style="width: 100%"
@@ -52,77 +6,22 @@
:value="getInputValue(rowIndex, colIndex)" :value="getInputValue(rowIndex, colIndex)"
@change="handleMatrixTextChange(rowIndex, colIndex, $event)" @change="handleMatrixTextChange(rowIndex, colIndex, $event)"
/> />
</template>
</el-table-column>
</el-table>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Io5EllipsisVerticalSharp } from 'vue-icons-plus/io5'; // 接受获取到的 col row 的索引参数
const rowIndex = defineModel('rowIndex', { required: true, default: 0 });
// 添加激活 action 的选项 const colIndex = defineModel('colIndex', { required: true, default: 0 });
interface _questionOptionType extends questionOptionType {
showAction?: boolean;
}
// 记录行和列的索引 // 记录行和列的索引
const rowRecord = defineModel<string[][]>('rowRecord', { required: false, default: () => [] }); 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<boolean>('active', { required: false, default: true });
const element = defineModel<question>('element', { const element = defineModel<question>('element', {
required: false, required: false,
default: () => { default: () => {
/**/ /**/
} }
}); });
/* const isPreview = */ defineModel<boolean>('isPreview', { required: false, default: false });
const rows = defineModel<_questionOptionType[]>('rows', { required: false, default: () => [] });
const cols = defineModel<_questionOptionType[]>('cols', { required: false, default: () => [] });
const tableWidth = defineModel<number>('tableWidth', { required: false, default: 110 });
const emit = defineEmits(['update:matrixAnswer', 'update:rowRecord', 'update:element']); const emit = defineEmits(['update:matrixAnswer', 'update:rowRecord', 'update:element']);
addShowActionOption();
/**
* 给行或者列选项 添加 showAction 选项
*/
function addShowActionOption() {
rows.value.forEach((row) => {
row.showAction = true;
});
cols.value.forEach((col) => {
col.showAction = true;
});
}
// 增加 popover 的 actions
const popoverActions = [
{
text: '删除'
}
// {
// text: '置底'
// }
];
// popover 的事件
function handleActionSelect(action: { text: string }, axi: any, type: 'row' | 'col') {
const data = type === 'row' ? rows : cols;
const index = data.value.indexOf(axi);
if (index < 0) return;
switch (action.text) {
case '删除':
data.value.splice(index, 1);
break;
case '置底':
data.value.push(data.value.splice(index, 1)[0]);
break;
}
}
function getInputValue(row: number, col: number) { function getInputValue(row: number, col: number) {
// console.log(`row: ${row}, col: ${col}`); // console.log(`row: ${row}, col: ${col}`);
// console.log(`rowRecord:`, rowRecord.value); // console.log(`rowRecord:`, rowRecord.value);
@@ -153,35 +52,4 @@ const emitValue = () => {
<style scoped lang="scss"> <style scoped lang="scss">
@import '@/assets/css/theme'; @import '@/assets/css/theme';
.matrix-table {
text-align: left;
tr,
td {
//width: 200px;
}
}
input {
width: 70%;
padding: 0 5px;
border: none;
border-radius: 4px;
outline: 1px solid #f4f4f4;
&:focus {
outline: 1px solid $theme-color;
}
}
.icon {
position: absolute;
top: 3px;
right: -10px;
& > svg {
height: 15px;
}
}
</style> </style>

View File

@@ -216,7 +216,6 @@
:list="question.list" :list="question.list"
:config="question.config" :config="question.config"
:question="question" :question="question"
isMobile
@change-answer="onRelation($event, question)" @change-answer="onRelation($event, question)"
/> />
<!-- &lt;!&ndash; 矩阵打分题 &ndash;&gt;--> <!-- &lt;!&ndash; 矩阵打分题 &ndash;&gt;-->
@@ -538,7 +537,6 @@ import answerMock from '@/views/Survey/views/Preview/js/mock.js';
import { getQuestionIndex } from '@/utils/utils.js'; import { getQuestionIndex } from '@/utils/utils.js';
// hooks file // hooks file
import icon from '@/assets/img/create-right-back.png';
import PreviewCheckbox from '@/views/Survey/views/Preview/components/questions/PreviewCheckbox.vue'; import PreviewCheckbox from '@/views/Survey/views/Preview/components/questions/PreviewCheckbox.vue';
import PreviewRate from '@/views/Survey/views/Preview/components/questions/PreviewRate.vue'; import PreviewRate from '@/views/Survey/views/Preview/components/questions/PreviewRate.vue';
import PreviewSign from '@/views/Survey/views/Preview/components/questions/PreviewSign.vue'; import PreviewSign from '@/views/Survey/views/Preview/components/questions/PreviewSign.vue';
@@ -937,7 +935,7 @@ async function answer(callback, callbackBeforePage) {
// 邮箱 // 邮箱
case 5: case 5:
if ( 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( !/^(([^<>()[\]\\.,;:\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 value
) )
) { ) {

View File

@@ -1,18 +1,18 @@
<template> <template>
<MatrixCheckbox <MatrixQuestion
v-model:rowRecord="rowRecord" v-model:rowRecord="rowRecord"
v-model:matrix-radio-answer="answer!" v-model:matrix-radio-answer="answer!"
:rows="rows" :rows="rows"
:cols="cols" :cols="cols"
:is-preview="true" :is-preview="true"
:errorMessage="question.error" :errorMessage="question.error"
:element="question"
/> />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import MatrixCheckbox from '@/views/Design/components/Questions/MatrixCheckbox.vue'; import MatrixQuestion from '@/views/Design/components/Questions/MatrixQuestion.vue';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import Rate from '@/views/Design/components/Questions/Rate.vue';
// const questionType = defineModel<number>('questionType', { required: false }); // const questionType = defineModel<number>('questionType', { required: false });
// 矩阵多选的答案类型 // 矩阵多选的答案类型
@@ -101,17 +101,3 @@ watch(
{ deep: true } { deep: true }
); );
</script> </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>

View File

@@ -1,5 +1,5 @@
<template> <template>
<MartrixQuestion <MatrixQuestion
v-model:rowRecord="rowRecord" v-model:rowRecord="rowRecord"
:rows="rows" :rows="rows"
:cols="cols" :cols="cols"
@@ -14,8 +14,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import MartrixQuestion from '@/views/Design/components/Questions/MartrixQuestion.vue'; import MatrixQuestion from '@/views/Design/components/Questions/MatrixQuestion.vue';
import Rate from '@/views/Design/components/Questions/Rate.vue';
// const questionType = defineModel<number>('questionType', { required: false }); // const questionType = defineModel<number>('questionType', { required: false });
// 矩阵单选的答案类型 // 矩阵单选的答案类型
@@ -89,17 +88,3 @@ watch(
{ deep: true } { deep: true }
); );
</script> </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>

View File

@@ -1,5 +1,5 @@
<template> <template>
<MartrixQuestion <MatrixQuestion
v-model:rowRecord="rowRecord" v-model:rowRecord="rowRecord"
:rows="rows" :rows="rows"
:cols="cols" :cols="cols"
@@ -14,9 +14,7 @@
<script setup lang="ts"> <script setup lang="ts">
// import MatrixText from '@/views/Design/components/Questions/MatrixText.vue'; // import MatrixText from '@/views/Design/components/Questions/MatrixText.vue';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import MartrixQuestion from '@/views/Design/components/Questions/MartrixQuestion.vue'; import MatrixQuestion from '@/views/Design/components/Questions/MatrixQuestion.vue';
import Rate from '@/views/Design/components/Questions/Rate.vue';
// const questionType = defineModel<number>('questionType', { required: false });
const questionIndex = defineModel<number>('answerIndex', { required: false, default: 0 }); const questionIndex = defineModel<number>('answerIndex', { required: false, default: 0 });
// 矩阵多选的答案类型 // 矩阵多选的答案类型
@@ -24,10 +22,6 @@ type answerType = {
[key: string]: string; [key: string]: string;
}; };
// 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<question>('question', { default: () => {} });
const emit = defineEmits(['changeAnswer', 'previous', 'next']); const emit = defineEmits(['changeAnswer', 'previous', 'next']);
// 示例 // 示例
@@ -105,17 +99,3 @@ watch(
{ deep: true } { deep: true }
); );
</script> </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>