editEshapeTable.vue 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. <template>
  2. <div class="content-table-layer">
  3. <table class="content-table" ref="tableRef">
  4. <tr class="header">
  5. <th
  6. class="sel-th"
  7. v-for="(col, colIndex) in rows[0]"
  8. @click="select = { col: colIndex }"
  9. :class="{ active: colIndex === select.col }"
  10. >
  11. <el-button type="primary" plain size="small" @click.stop="delColumn(colIndex)">
  12. <el-icon><Minus /></el-icon>
  13. </el-button>
  14. </th>
  15. <td></td>
  16. </tr>
  17. <tr v-for="(row, rowIndex) in rows" class="row" :key="rowIndex">
  18. <td
  19. v-for="(col, colIndex) in row"
  20. class="col"
  21. :key="colIndex"
  22. @click="inputPos = [rowIndex, colIndex]"
  23. >
  24. <input
  25. :ref="(dom: any) => inputRef = dom"
  26. @blur="inputPos = null"
  27. v-if="inputPos && rowIndex === inputPos[0] && colIndex === inputPos[1]"
  28. :value="col"
  29. :style="getBound(rowIndex, colIndex)"
  30. @change="ev => setColValue(rowIndex, colIndex, (ev.target as any).value)"
  31. />
  32. <span v-else :style="getBound(rowIndex, colIndex)">{{ col || "" }}&nbsp;</span>
  33. </td>
  34. <th
  35. class="sel-th del-col"
  36. @click="select = { row: rowIndex }"
  37. :class="{ active: rowIndex === select.row }"
  38. >
  39. <div style="width: 100px"></div>
  40. <el-button type="primary" plain size="small" @click="delRow(rowIndex)">
  41. <el-icon><Minus /></el-icon>
  42. </el-button>
  43. </th>
  44. </tr>
  45. </table>
  46. </div>
  47. <div class="setter" v-if="selectValue">
  48. <el-form-item :label="selectValue.label">
  49. <el-input-number
  50. :modelValue="selectValue.value"
  51. @update:modelValue="changeSelectValue"
  52. :min="24"
  53. :max="1000"
  54. size="small"
  55. controls-position="right"
  56. />
  57. </el-form-item>
  58. </div>
  59. <div class="add-row-layout">
  60. <el-button type="primary" @click="addRow">
  61. <el-icon><Plus /></el-icon> 行
  62. </el-button>
  63. <el-button type="primary" @click="addCloumn">
  64. <el-icon><Plus /></el-icon> 列
  65. </el-button>
  66. </div>
  67. </template>
  68. <script setup lang="ts">
  69. import { ElMessage } from "element-plus";
  70. import { computed, nextTick, ref, watchEffect } from "vue";
  71. import { QuiskExpose } from "@/helper/mount";
  72. export type EshapeTableContent = {
  73. width: number;
  74. height: number;
  75. value: string;
  76. colIndex: number;
  77. rowIndex: number;
  78. }[];
  79. const props = defineProps<{ content: EshapeTableContent; track?: boolean }>();
  80. const bindContent = ref(props.content.map((item) => ({ ...item })));
  81. const inputPos = ref<[number, number] | null>(null);
  82. const inputRef = ref<HTMLInputElement>();
  83. const select = ref<{ col?: number; row?: number }>({});
  84. const selectValue = computed(() => {
  85. if (Object.keys(select.value).length === 0) return;
  86. const isCol = "col" in select.value;
  87. const item = bindContent.value.find((item) =>
  88. isCol ? item.colIndex === select.value.col : item.rowIndex === select.value.row
  89. )!;
  90. if (item) {
  91. return isCol
  92. ? { label: "列宽", value: item.width }
  93. : { label: "行高", value: item.height };
  94. }
  95. });
  96. const changeSelectValue = (val: number) => {
  97. const isCol = "col" in select.value;
  98. bindContent.value
  99. .filter((item) =>
  100. isCol ? item.colIndex === select.value.col : item.rowIndex === select.value.row
  101. )
  102. .forEach((item) => {
  103. if (isCol) {
  104. item.width = val;
  105. } else {
  106. item.height = val;
  107. }
  108. });
  109. };
  110. const getBound = (rowIndex, colIndex) => {
  111. const item = bindContent.value.find(
  112. (item) => item.rowIndex === rowIndex && item.colIndex === colIndex
  113. );
  114. const bound = [170, 25 - 2];
  115. if (item && item.width && item.height) {
  116. bound[0] = item.width;
  117. bound[1] = item.height;
  118. } else if (colIndex === 0) {
  119. bound[0] = 90;
  120. }
  121. return {
  122. width: bound[0] + "px",
  123. "min-height": bound[1] + "px",
  124. };
  125. };
  126. watchEffect(
  127. () => {
  128. if (inputRef.value) {
  129. inputRef.value.focus();
  130. }
  131. },
  132. { flush: "post" }
  133. );
  134. const delRow = (rowIndex: number) => {
  135. if (rows.value.length === 1) {
  136. ElMessage.error("表格最少需要保留一行!");
  137. } else {
  138. bindContent.value = bindContent.value
  139. .filter((item) => item.rowIndex !== rowIndex)
  140. .map((item) => {
  141. if (item.rowIndex > rowIndex) {
  142. return { ...item, rowIndex: item.rowIndex - 1 };
  143. } else {
  144. return item;
  145. }
  146. });
  147. }
  148. };
  149. const delColumn = (columnIndex: number) => {
  150. if (rows.value[0].length === 1) {
  151. ElMessage.error("表格最少需要保留一列!");
  152. } else {
  153. console.log(columnIndex, bindContent.value);
  154. bindContent.value = bindContent.value
  155. .filter((item) => item.colIndex !== columnIndex)
  156. .map((item) => {
  157. if (item.colIndex > columnIndex) {
  158. return { ...item, colIndex: item.colIndex - 1 };
  159. } else {
  160. return item;
  161. }
  162. });
  163. console.log(columnIndex, bindContent.value);
  164. }
  165. };
  166. const addRow = () => {
  167. const colSize = rows.value[0].length;
  168. const rowSize = rows.value.length;
  169. for (let i = 0; i < colSize; i++) {
  170. bindContent.value.push({
  171. width: 0,
  172. height: 0,
  173. colIndex: i,
  174. rowIndex: rowSize,
  175. value: "",
  176. });
  177. }
  178. nextTick(updateTableContent);
  179. };
  180. const addCloumn = () => {
  181. const colSize = rows.value[0].length;
  182. const rowSize = rows.value.length;
  183. for (let i = 0; i < rowSize; i++) {
  184. bindContent.value.push({
  185. width: 0,
  186. height: 0,
  187. colIndex: colSize,
  188. rowIndex: i,
  189. value: "",
  190. });
  191. }
  192. nextTick(updateTableContent);
  193. // for (let i = 0; i < colSize; i++) {
  194. // bindContent.value.push({
  195. // width: 0,
  196. // height: 0,
  197. // colIndex: i,
  198. // rowIndex: rowSize,
  199. // value: "",
  200. // });
  201. // }
  202. };
  203. const setColValue = (rowIndex: number, colIndex: number, val: string) => {
  204. const item = bindContent.value.find(
  205. (item) => item.rowIndex === rowIndex && item.colIndex === colIndex
  206. )!;
  207. item.value = val;
  208. };
  209. const rows = computed(() => {
  210. const rows: string[][] = [];
  211. bindContent.value.forEach((item) => {
  212. rows[item.rowIndex] = rows[item.rowIndex] || [];
  213. rows[item.rowIndex][item.colIndex] = item.value;
  214. });
  215. return rows;
  216. });
  217. const tableRef = ref<HTMLTableElement>();
  218. const updateTableContent = () => {
  219. const dom = tableRef.value!;
  220. const rows = Array.from(dom.querySelectorAll(".row"));
  221. for (let i = 0; i < rows.length; i++) {
  222. const cols = Array.from(rows[i].querySelectorAll(".col"));
  223. for (let j = 0; j < cols.length; j++) {
  224. const col = cols[j] as HTMLElement;
  225. const item = bindContent.value.find(
  226. (item) => item.rowIndex === i && item.colIndex === j
  227. )!;
  228. item.width = col.offsetWidth;
  229. item.height = col.offsetHeight;
  230. }
  231. }
  232. };
  233. defineExpose<QuiskExpose>({
  234. submit() {
  235. updateTableContent();
  236. return bindContent.value;
  237. },
  238. });
  239. </script>
  240. <style lang="scss" scoped>
  241. .content-table {
  242. margin: 0 auto;
  243. border-collapse: collapse;
  244. --border-color: #f0f2f5;
  245. margin-bottom: 10px;
  246. table-layout: fixed;
  247. th {
  248. background: #fafafb;
  249. }
  250. tr:first-child {
  251. th,
  252. td {
  253. border-top: 1px solid var(--border-color);
  254. }
  255. }
  256. th:first-child,
  257. td:first-child {
  258. border-left: 1px solid var(--border-color);
  259. }
  260. th,
  261. td {
  262. border-right: 1px solid var(--border-color);
  263. border-bottom: 1px solid var(--border-color);
  264. text-align: center;
  265. }
  266. .col {
  267. cursor: pointer;
  268. &:hover {
  269. background-color: rgba(133, 194, 255, 0.1);
  270. }
  271. span,
  272. input {
  273. display: inline-flex;
  274. align-items: center;
  275. justify-content: center;
  276. font-size: 12px;
  277. width: 100%;
  278. height: 100%;
  279. border: none;
  280. text-align: center;
  281. line-height: 24px;
  282. }
  283. span {
  284. word-break: break-all;
  285. }
  286. }
  287. .col:hover {
  288. background-color: rgba(133, 194, 255, 0.1);
  289. }
  290. .sel-th {
  291. cursor: pointer;
  292. }
  293. .active {
  294. box-shadow: 0 0 0 1px var(--el-color-primary) inset;
  295. }
  296. }
  297. .add-row-layout {
  298. text-align: center;
  299. }
  300. .content-table-layer {
  301. overflow: auto;
  302. text-align: center;
  303. }
  304. .setter {
  305. text-align: center;
  306. display: flex;
  307. align-items: center;
  308. justify-content: center;
  309. }
  310. </style>