feat: 矩阵增加操作选项

- 矩阵可以删除某个行或者列,实现置底的功能
This commit is contained in:
Huangzhe
2025-03-20 17:50:13 +08:00
parent d7d083190e
commit 7e5d0f1ae7
4 changed files with 280 additions and 98 deletions

View File

@@ -75,6 +75,7 @@ const emitValue = () => {
v-model:rows="rows" v-model:rows="rows"
v-model:cols="cols" v-model:cols="cols"
:is-preview="isPreview" :is-preview="isPreview"
:active="active"
style="overflow: scroll; width: 88vw" style="overflow: scroll; width: 88vw"
/> />
</template> </template>

View File

@@ -3,16 +3,46 @@
<el-table-column :width="tableWidth"> <el-table-column :width="tableWidth">
<template #header></template> <template #header></template>
<template #default="{ row /*, column, $index*/ }"> <template #default="{ row /*, column, $index*/ }">
<div style="position: relative">
<el-text truncated :style="{ width: `${tableWidth}px` }"> <el-text truncated :style="{ width: `${tableWidth}px` }">
<contenteditable v-model="row.option" :active="active" @blur="emitValue" /> <contenteditable v-model="row.option" :active="active" @blur="emitValue" />
</el-text> </el-text>
<van-popover
v-model="row.showAction"
placement="right"
trigger="click"
:actions="popoverActions"
@select="handleActionSelect($event, row, 'row')"
>
<template #reference>
<div v-if="active" class="icon">
<Io5EllipsisVerticalSharp />
</div>
</template>
</van-popover>
</div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column v-for="(col, colIndex) in cols" :key="col.option" :width="tableWidth"> <el-table-column v-for="(col, colIndex) in cols" :key="col.option" :width="tableWidth">
<template #header> <template #header>
<div style="position: relative">
<el-text truncated :style="{ width: `${tableWidth}px` }"> <el-text truncated :style="{ width: `${tableWidth}px` }">
<contenteditable v-model="col.option" :active="active" @blur="emitValue" /> <contenteditable v-model="col.option" :active="active" @blur="emitValue" />
</el-text> </el-text>
<van-popover
v-model="col.showAction"
placement="right"
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>
<template #default="{ /*row, column, */ $index: rowIndex }"> <template #default="{ /*row, column, */ $index: rowIndex }">
<input <input
@@ -27,8 +57,12 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// import { defineProps } from 'vue'; import { Io5EllipsisVerticalSharp } from 'vue-icons-plus/io5';
// import { vFocus } from '@/utils/directives/useVFocus';
// 添加激活 action 的选项
interface _questionOptionType extends questionOptionType {
showAction?: boolean;
}
// 记录行和列的索引 // 记录行和列的索引
const rowRecord = defineModel<number[][]>('rowRecord', { required: false, default: () => [] }); const rowRecord = defineModel<number[][]>('rowRecord', { required: false, default: () => [] });
@@ -38,8 +72,8 @@ const rowRecord = defineModel<number[][]>('rowRecord', { required: false, defaul
const tableWidth = defineModel<number>('tableWidth', { required: false, default: 110 }); const tableWidth = defineModel<number>('tableWidth', { required: false, default: 110 });
const active = defineModel<boolean>('active', { required: false, default: true }); const active = defineModel<boolean>('active', { required: false, default: true });
/* const isPreview = */ defineModel<boolean>('isPreview', { required: false, default: false }); /* const isPreview = */ defineModel<boolean>('isPreview', { required: false, default: false });
const rows = defineModel<questionOptionType[]>('rows', { required: false, default: () => [] }); const rows = defineModel<_questionOptionType[]>('rows', { required: false, default: () => [] });
const cols = defineModel<questionOptionType[]>('cols', { 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: () => {
@@ -61,10 +95,44 @@ const isOptionChecked = (rowIndex: number, colIndex: number): boolean => {
return rowRecord.value[rowIndex].includes(colIndex); return rowRecord.value[rowIndex].includes(colIndex);
}; };
// const handleRowNameChange = (/* value: string */) => { addShowActionOption();
// // console.log(`row change: ${value}`); /**
// // 你可以在这里添加其他逻辑 * 给行或者列选项 添加 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) {
@@ -84,25 +152,8 @@ function handleMatrixCheckboxChange(row: number, col: number) {
// 如果没有,添加 // 如果没有,添加
cols.push(col); cols.push(col);
} }
// console.log(`rowRecord:`, rowRecord.value);
} }
// const handleColNameChange = (rowOption: string, colOption: string) => {
// // const target = e.target as HTMLInputElement;
// const col = props.columns.findIndex((option) => option.option === colOption);
// const row = props.rows.findIndex((option) => option.option === rowOption);
// if (props.questionType === 9) {
// props.rowRecord[col] = row + 1;
// props.matrixAnswer = {};
// props.rowRecord.forEach((row, index) => {
// props.matrixAnswer[`${index + 1}_${row}`] = 1;
// });
// }
// emits('update:matrixAnswer', props.matrixAnswer);
// emits('update:rowRecord', props.rowRecord);
// };
const emitValue = (/* val: unknown */) => { const emitValue = (/* val: unknown */) => {
emit('update:element', element.value); emit('update:element', element.value);
}; };
@@ -142,4 +193,14 @@ const emitValue = (/* val: unknown */) => {
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
} }
.icon {
position: absolute;
top: 0;
right: -5px;
& > svg {
height: 15px;
}
}
</style> </style>

View File

@@ -3,16 +3,46 @@
<el-table-column :width="tableWidth"> <el-table-column :width="tableWidth">
<template #header></template> <template #header></template>
<template #default="{ row /*, column, $index*/ }"> <template #default="{ row /*, column, $index*/ }">
<div style="position: relative">
<el-text truncated :style="{ width: `${tableWidth}px` }"> <el-text truncated :style="{ width: `${tableWidth}px` }">
<contenteditable v-model="row.option" :active="active" @blur="emitValue" /> <contenteditable v-model="row.option" :active="active" @blur="emitValue" />
</el-text> </el-text>
<van-popover
v-model="row.showAction"
placement="right"
trigger="click"
:actions="popoverActions"
@select="handleActionSelect($event, row, 'row')"
>
<template #reference>
<div v-if="active" class="icon">
<Io5EllipsisVerticalSharp />
</div>
</template>
</van-popover>
</div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column v-for="(col, colIndex) in cols" :key="col.option" :width="tableWidth"> <el-table-column v-for="(col, colIndex) in cols" :key="col.option" :width="tableWidth">
<template #header> <template #header>
<div style="position: relative">
<el-text truncated :style="{ width: `${tableWidth}px` }"> <el-text truncated :style="{ width: `${tableWidth}px` }">
<contenteditable v-model="col.option" :active="active" @blur="emitValue" /> <contenteditable v-model="col.option" :active="active" @blur="emitValue" />
</el-text> </el-text>
<van-popover
v-model="col.showAction"
placement="right"
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>
<template #default="{ /*row, column, */ $index: rowIndex }"> <template #default="{ /*row, column, */ $index: rowIndex }">
<input <input
@@ -27,18 +57,19 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// 记录行和列的索引 import { Io5EllipsisVerticalSharp } from 'vue-icons-plus/io5';
// import { getDomText } from '@/utils/utils';
// 添加激活 action 的选项
interface _questionOptionType extends questionOptionType {
showAction?: boolean;
}
const rowRecord = defineModel<number[]>('rowRecord', { required: false, default: () => [] }); const rowRecord = defineModel<number[]>('rowRecord', { required: false, default: () => [] });
// 检查 rowRecord 是否存在
// console.log(`rowRecord:`, rowRecord.value);
/* const isPreview = */ defineModel<boolean>('isPreview', { required: false, default: false }); /* const isPreview = */ defineModel<boolean>('isPreview', { required: false, default: false });
const tableWidth = defineModel<number>('tableWidth', { required: false, default: 110 }); const tableWidth = defineModel<number>('tableWidth', { required: false, default: 110 });
const rows = defineModel<questionOptionType[]>('rows', { required: false, default: () => [] }); const rows = defineModel<_questionOptionType[]>('rows', { required: false, default: () => [] });
const cols = defineModel<questionOptionType[]>('cols', { required: false, default: () => [] }); const cols = defineModel<_questionOptionType[]>('cols', { required: false, default: () => [] });
/* const active = */ defineModel<boolean>('active', { required: false, default: true }); 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: () => {
@@ -47,6 +78,45 @@ 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}`);
@@ -57,11 +127,6 @@ const isOptionChecked = (rowIndex: number, colIndex: number): boolean => {
// return !!matrixAnswer.value?.[key]; // return !!matrixAnswer.value?.[key];
}; };
// const handleRowNameChange = (/* value: string */) => {
// // console.log(`row change: ${value}`);
// // 你可以在这里添加其他逻辑
// };
// 当 matrix radio 选中时,更新 rowRecord 和 matrixAnswer // 当 matrix radio 选中时,更新 rowRecord 和 matrixAnswer
function handleMatrixRadioChange(row: number, col: number) { function handleMatrixRadioChange(row: number, col: number) {
rowRecord.value[row] = col; rowRecord.value[row] = col;
@@ -70,22 +135,7 @@ function handleMatrixRadioChange(row: number, col: number) {
// matrixAnswer.value[`${col + 1}_${row}`] = 1; // matrixAnswer.value[`${col + 1}_${row}`] = 1;
// }); // });
} }
// const handleColNameChange = (rowOption: string, colOption: string) => {
// // const target = e.target as HTMLInputElement;
// const col = props.columns.findIndex((option) => option.option === colOption);
// const row = props.rows.findIndex((option) => option.option === rowOption);
// if (props.questionType === 9) {
// props.rowRecord[col] = row + 1;
// props.matrixAnswer = {};
// props.rowRecord.forEach((row, index) => {
// props.matrixAnswer[`${index + 1}_${row}`] = 1;
// });
// }
// emits('update:matrixAnswer', props.matrixAnswer);
// emits('update:rowRecord', props.rowRecord);
// };
const emitValue = () => { const emitValue = () => {
emit('update:element', element.value); emit('update:element', element.value);
}; };
@@ -125,4 +175,14 @@ const emitValue = () => {
line-height: 15px; line-height: 15px;
text-align: center; text-align: center;
} }
.icon {
position: absolute;
top: 0;
right: -5px;
& > svg {
height: 15px;
}
}
</style> </style>

View File

@@ -3,22 +3,46 @@
<el-table-column :width="tableWidth"> <el-table-column :width="tableWidth">
<template #header></template> <template #header></template>
<template #default="{ row /*, column, $index*/ }"> <template #default="{ row /*, column, $index*/ }">
<div style="position: relative">
<el-text truncated :style="{ width: `${tableWidth}px` }"> <el-text truncated :style="{ width: `${tableWidth}px` }">
<contenteditable v-model="row.option" :active="active" @blur="emitValue" /> <contenteditable v-model="row.option" :active="active" @blur="emitValue" />
</el-text> </el-text>
<van-popover
v-model="row.showAction"
placement="right"
trigger="click"
:actions="popoverActions"
@select="handleActionSelect($event, row, 'row')"
>
<template #reference>
<div v-if="active" class="icon">
<Io5EllipsisVerticalSharp />
</div>
</template>
</van-popover>
</div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column v-for="(col, colIndex) in cols" :key="col.option" :width="tableWidth"> <el-table-column v-for="(col, colIndex) in cols" :key="col.option" :width="tableWidth">
<template #header> <template #header>
<div style="position: relative">
<el-text truncated :style="{ width: `${tableWidth}px` }"> <el-text truncated :style="{ width: `${tableWidth}px` }">
<contenteditable <contenteditable v-model="col.option" :active="active" @blur="emitValue" />
v-if="col.option"
v-model="col.option"
:active="active"
@blur="emitValue"
/>
<van-field v-else v-model="col.option" placeholder="请输入"></van-field>
</el-text> </el-text>
<van-popover
v-model="col.showAction"
placement="right"
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>
<template #default="{ /*row, column, */ $index: rowIndex }"> <template #default="{ /*row, column, */ $index: rowIndex }">
<input <input
@@ -34,6 +58,13 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Io5EllipsisVerticalSharp } from 'vue-icons-plus/io5';
// 添加激活 action 的选项
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: () => ({}) }); // const matrixAnswer = defineModel<{ [key: string]: 1 }>('matrixAnswer', { required: false, default: () => ({}) });
@@ -47,16 +78,50 @@ const element = defineModel<question>('element', {
} }
}); });
/* const isPreview = */ defineModel<boolean>('isPreview', { required: false, default: false }); /* const isPreview = */ defineModel<boolean>('isPreview', { required: false, default: false });
const rows = defineModel<questionOptionType[]>('rows', { required: false, default: () => [] }); const rows = defineModel<_questionOptionType[]>('rows', { required: false, default: () => [] });
const cols = defineModel<questionOptionType[]>('cols', { required: false, default: () => [] }); const cols = defineModel<_questionOptionType[]>('cols', { required: false, default: () => [] });
const tableWidth = defineModel<number>('tableWidth', { required: false, default: 110 }); 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']);
// const handleRowNameChange = (/* value: string */) => { addShowActionOption();
// console.log(`row change: ${value}`); /**
// 你可以在这里添加其他逻辑 * 给行或者列选项 添加 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}`);
@@ -80,22 +145,7 @@ function handleMatrixTextChange(row: number, col: number, e: Event) {
cols[col] = inputValue; cols[col] = inputValue;
// console.log(`rowRecord:`, rowRecord.value); // console.log(`rowRecord:`, rowRecord.value);
} }
// const handleColNameChange = (rowOption: string, colOption: string) => {
// // const target = e.target as HTMLInputElement;
// const col = props.columns.findIndex((option) => option.option === colOption);
// const row = props.rows.findIndex((option) => option.option === rowOption);
// if (props.questionType === 9) {
// props.rowRecord[col] = row + 1;
// props.matrixAnswer = {};
// props.rowRecord.forEach((row, index) => {
// props.matrixAnswer[`${index + 1}_${row}`] = 1;
// });
// }
// emits('update:matrixAnswer', props.matrixAnswer);
// emits('update:rowRecord', props.rowRecord);
// };
const emitValue = () => { const emitValue = () => {
emit('update:element', element.value); emit('update:element', element.value);
}; };
@@ -124,4 +174,14 @@ input {
outline: 1px solid $theme-color; outline: 1px solid $theme-color;
} }
} }
.icon {
position: absolute;
top: 0;
right: -5px;
& > svg {
height: 15px;
}
}
</style> </style>