123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- <template>
- <div class="content-table-layer">
- <table class="content-table" ref="tableRef">
- <tr class="header">
- <th
- class="sel-th"
- v-for="(col, colIndex) in rows[0]"
- @click="select = { col: colIndex }"
- :class="{ active: colIndex === select.col }"
- >
- <el-button type="primary" plain size="small" @click.stop="delColumn(colIndex)">
- <el-icon><Minus /></el-icon>
- </el-button>
- </th>
- <td></td>
- </tr>
- <tr v-for="(row, rowIndex) in rows" class="row" :key="rowIndex">
- <td
- v-for="(col, colIndex) in row"
- class="col"
- :key="colIndex"
- @click="inputPos = [rowIndex, colIndex]"
- >
- <input
- :ref="(dom: any) => inputRef = dom"
- @blur="inputPos = null"
- v-if="inputPos && rowIndex === inputPos[0] && colIndex === inputPos[1]"
- :value="col"
- :style="getBound(rowIndex, colIndex)"
- @change="ev => setColValue(rowIndex, colIndex, (ev.target as any).value)"
- />
- <span v-else :style="getBound(rowIndex, colIndex)">{{ col || "" }} </span>
- </td>
- <th
- class="sel-th del-col"
- @click="select = { row: rowIndex }"
- :class="{ active: rowIndex === select.row }"
- >
- <div style="width: 100px"></div>
- <el-button type="primary" plain size="small" @click="delRow(rowIndex)">
- <el-icon><Minus /></el-icon>
- </el-button>
- </th>
- </tr>
- </table>
- </div>
- <div class="setter" v-if="selectValue">
- <el-form-item :label="selectValue.label">
- <el-input-number
- :modelValue="selectValue.value"
- @update:modelValue="changeSelectValue"
- :min="24"
- :max="1000"
- size="small"
- controls-position="right"
- />
- </el-form-item>
- </div>
- <div class="add-row-layout">
- <el-button type="primary" @click="addRow">
- <el-icon><Plus /></el-icon> 行
- </el-button>
- <el-button type="primary" @click="addCloumn">
- <el-icon><Plus /></el-icon> 列
- </el-button>
- </div>
- </template>
- <script setup lang="ts">
- import { ElMessage } from "element-plus";
- import { computed, nextTick, ref, watchEffect } from "vue";
- import { QuiskExpose } from "@/helper/mount";
- export type EshapeTableContent = {
- width: number;
- height: number;
- value: string;
- colIndex: number;
- rowIndex: number;
- }[];
- const props = defineProps<{ content: EshapeTableContent; track?: boolean }>();
- const bindContent = ref(props.content.map((item) => ({ ...item })));
- const inputPos = ref<[number, number] | null>(null);
- const inputRef = ref<HTMLInputElement>();
- const select = ref<{ col?: number; row?: number }>({});
- const selectValue = computed(() => {
- if (Object.keys(select.value).length === 0) return;
- const isCol = "col" in select.value;
- const item = bindContent.value.find((item) =>
- isCol ? item.colIndex === select.value.col : item.rowIndex === select.value.row
- )!;
- if (item) {
- return isCol
- ? { label: "列宽", value: item.width }
- : { label: "行高", value: item.height };
- }
- });
- const changeSelectValue = (val: number) => {
- const isCol = "col" in select.value;
- bindContent.value
- .filter((item) =>
- isCol ? item.colIndex === select.value.col : item.rowIndex === select.value.row
- )
- .forEach((item) => {
- if (isCol) {
- item.width = val;
- } else {
- item.height = val;
- }
- });
- };
- const getBound = (rowIndex, colIndex) => {
- const item = bindContent.value.find(
- (item) => item.rowIndex === rowIndex && item.colIndex === colIndex
- );
- const bound = [170, 25 - 2];
- if (item && item.width && item.height) {
- bound[0] = item.width;
- bound[1] = item.height;
- } else if (colIndex === 0) {
- bound[0] = 90;
- }
- return {
- width: bound[0] + "px",
- "min-height": bound[1] + "px",
- };
- };
- watchEffect(
- () => {
- if (inputRef.value) {
- inputRef.value.focus();
- }
- },
- { flush: "post" }
- );
- const delRow = (rowIndex: number) => {
- if (rows.value.length === 1) {
- ElMessage.error("表格最少需要保留一行!");
- } else {
- bindContent.value = bindContent.value
- .filter((item) => item.rowIndex !== rowIndex)
- .map((item) => {
- if (item.rowIndex > rowIndex) {
- return { ...item, rowIndex: item.rowIndex - 1 };
- } else {
- return item;
- }
- });
- }
- };
- const delColumn = (columnIndex: number) => {
- if (rows.value[0].length === 1) {
- ElMessage.error("表格最少需要保留一列!");
- } else {
- console.log(columnIndex, bindContent.value);
- bindContent.value = bindContent.value
- .filter((item) => item.colIndex !== columnIndex)
- .map((item) => {
- if (item.colIndex > columnIndex) {
- return { ...item, colIndex: item.colIndex - 1 };
- } else {
- return item;
- }
- });
- console.log(columnIndex, bindContent.value);
- }
- };
- const addRow = () => {
- const colSize = rows.value[0].length;
- const rowSize = rows.value.length;
- for (let i = 0; i < colSize; i++) {
- bindContent.value.push({
- width: 0,
- height: 0,
- colIndex: i,
- rowIndex: rowSize,
- value: "",
- });
- }
- nextTick(updateTableContent);
- };
- const addCloumn = () => {
- const colSize = rows.value[0].length;
- const rowSize = rows.value.length;
- for (let i = 0; i < rowSize; i++) {
- bindContent.value.push({
- width: 0,
- height: 0,
- colIndex: colSize,
- rowIndex: i,
- value: "",
- });
- }
- nextTick(updateTableContent);
- // for (let i = 0; i < colSize; i++) {
- // bindContent.value.push({
- // width: 0,
- // height: 0,
- // colIndex: i,
- // rowIndex: rowSize,
- // value: "",
- // });
- // }
- };
- const setColValue = (rowIndex: number, colIndex: number, val: string) => {
- const item = bindContent.value.find(
- (item) => item.rowIndex === rowIndex && item.colIndex === colIndex
- )!;
- item.value = val;
- };
- const rows = computed(() => {
- const rows: string[][] = [];
- bindContent.value.forEach((item) => {
- rows[item.rowIndex] = rows[item.rowIndex] || [];
- rows[item.rowIndex][item.colIndex] = item.value;
- });
- return rows;
- });
- const tableRef = ref<HTMLTableElement>();
- const updateTableContent = () => {
- const dom = tableRef.value!;
- const rows = Array.from(dom.querySelectorAll(".row"));
- for (let i = 0; i < rows.length; i++) {
- const cols = Array.from(rows[i].querySelectorAll(".col"));
- for (let j = 0; j < cols.length; j++) {
- const col = cols[j] as HTMLElement;
- const item = bindContent.value.find(
- (item) => item.rowIndex === i && item.colIndex === j
- )!;
- item.width = col.offsetWidth;
- item.height = col.offsetHeight;
- }
- }
- };
- defineExpose<QuiskExpose>({
- submit() {
- updateTableContent();
- return bindContent.value;
- },
- });
- </script>
- <style lang="scss" scoped>
- .content-table {
- margin: 0 auto;
- border-collapse: collapse;
- --border-color: #f0f2f5;
- margin-bottom: 10px;
- table-layout: fixed;
- th {
- background: #fafafb;
- }
- tr:first-child {
- th,
- td {
- border-top: 1px solid var(--border-color);
- }
- }
- th:first-child,
- td:first-child {
- border-left: 1px solid var(--border-color);
- }
- th,
- td {
- border-right: 1px solid var(--border-color);
- border-bottom: 1px solid var(--border-color);
- text-align: center;
- }
- .col {
- cursor: pointer;
- &:hover {
- background-color: rgba(133, 194, 255, 0.1);
- }
- span,
- input {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- font-size: 12px;
- width: 100%;
- height: 100%;
- border: none;
- text-align: center;
- line-height: 24px;
- }
- span {
- word-break: break-all;
- }
- }
- .col:hover {
- background-color: rgba(133, 194, 255, 0.1);
- }
- .sel-th {
- cursor: pointer;
- }
- .active {
- box-shadow: 0 0 0 1px var(--el-color-primary) inset;
- }
- }
- .add-row-layout {
- text-align: center;
- }
- .content-table-layer {
- overflow: auto;
- text-align: center;
- }
- .setter {
- text-align: center;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- </style>
|