• Vue2 + Element UI 封装 Table 递归多层级列表头动态


     注:此功能改为独立模块直接拿代码就可以使用。

    1、在 components 中创建 HeaderTable 文件夹,在创建 ColumnItem.vue 和 index.vue。

    如下:

    2、index.vue 代码内容,如下:

    1. <script>
    2. import { EventBus } from "../../utils/event-bus.js";
    3. import * as XLSX from "xlsx";
    4. import ColumnItem from './ColumnItem.vue';
    5. import { apiEditHeaderTable, apiGetList, apiGetTreeselect, listTableReport, listYearReport, listMonthReport, listDayReport } from "@/api/headerTable/index.js";
    6. export default {
    7. name: 'CustomElTable',
    8. components: {
    9. ColumnItem
    10. },
    11. props: {
    12. // 区分哪个页面下的列表
    13. tablekey: {
    14. type: String,
    15. required: true
    16. },
    17. // 限制新增最大列
    18. maxLength: {
    19. type: Number,
    20. required: false
    21. },
    22. },
    23. data() {
    24. let today = new Date();
    25. let todayString = `${today.getFullYear()}-${
    26. today.getMonth() + 1
    27. }-${today.getDate()}`;
    28. return {
    29. dataHeaders: [], // 表头数据
    30. dataTableData: [], // 表格列表数据
    31. // 查询参数
    32. queryParams: {
    33. dataTime: todayString,
    34. ids: [],
    35. },
    36. vLoading: false,
    37. showAddBut: false, // 控制按钮状态
    38. flag: true, // 为了更新子组件的状态
    39. drawer: false,
    40. loading: false,
    41. column: {
    42. label: "",
    43. width: "",
    44. align: "center",
    45. prop: "",
    46. propId: "",
    47. },
    48. optionProps: {
    49. expandTrigger: 'click',
    50. value: "id",
    51. label: "label",
    52. children: "children",
    53. },
    54. copyCol: {},
    55. rules: {
    56. label: [{ required: true, message: "请输入名称", trigger: "blur" }],
    57. align: [{ required: true, message: "请选择对齐方式", trigger: "blur" }],
    58. prop: [{ required: true, message: "请选择映射变量1", trigger: ["blur","change"] }],
    59. propId: [{ required: true, message: "请选择映射变量2", trigger: ["blur","change"] }],
    60. },
    61. title: '新增列',
    62. submitText: '新增',
    63. options: [],
    64. optionsId: [],
    65. }
    66. },
    67. methods: {
    68. // 立即执行获取数据 搜索
    69. getTableList() {
    70. this.vLoading = true;
    71. this.showTable = false;
    72. listTableReport(this.tablekey).then(res => {
    73. if(res.code == 200) {
    74. let tableJson = res.data.tableJson ? JSON.parse(res.data.tableJson) : [];
    75. console.log("tableJson",tableJson);
    76. this.getForRecursion(tableJson);
    77. this.dataHeaders = tableJson;
    78. this.getList();
    79. }
    80. })
    81. },
    82. // 获取列表数据
    83. async getList() {
    84. console.log("this.queryParams",this.queryParams);
    85. if(this.queryParams.ids) {
    86. this.vLoading = true;
    87. let data = {
    88. dataTime: this.queryParams.dataTime,
    89. ids: this.queryParams.ids.length == 0 ? [] : this.queryParams.ids
    90. }
    91. let res = null;
    92. if(this.tablekey === 'year') {
    93. res = await listYearReport(data);
    94. }else if(this.tablekey === 'month') {
    95. res = await listMonthReport(data);
    96. }else {
    97. res = await listDayReport(data);
    98. }
    99. this.dataTableData = res.data;
    100. this.showTable = true;
    101. this.vLoading = false;
    102. }
    103. },
    104. // 递归遍历出 propId
    105. getForRecursion(list) {
    106. console.log("list",list);
    107. if(list?.length > 0) {
    108. list.map(item => {
    109. if (item.children && item.children.length > 0) {
    110. this.getForRecursion(item.children);
    111. }else {
    112. this.queryParams.ids.push(item.propId);
    113. }
    114. })
    115. }else {
    116. this.queryParams.ids = [];
    117. }
    118. },
    119. // 请求接口,保存表头数据
    120. handleSave() {
    121. console.log("保存");
    122. let data = {
    123. tableKey: this.tablekey,
    124. tableJson: JSON.stringify(this.dataHeaders)
    125. }
    126. apiEditHeaderTable(data).then(() => {
    127. this.loading = false;
    128. this.drawer = false;
    129. this.$message({
    130. message: '编辑成功',
    131. type: 'success'
    132. });
    133. this.getTableList();
    134. }).catch(() => {
    135. this.loading = false;
    136. this.drawer = false;
    137. this.$message({
    138. message: '编辑失败',
    139. type: 'error'
    140. });
    141. })
    142. },
    143. // 重置
    144. resetSearchForm() {
    145. let today = new Date();
    146. let todayString = `${today.getFullYear()}-${
    147. today.getMonth() + 1
    148. }-${today.getDate()}`;
    149. this.queryParams.dataTime = todayString;
    150. this.getList();
    151. },
    152. // 操作列表头
    153. handleEditBut(val) {
    154. this.showAddBut = !this.showAddBut;
    155. if(val === 'preserve') {
    156. this.handleSave();
    157. }else if(val === 'quit') {
    158. this.getTableList();
    159. }
    160. },
    161. // 删除
    162. getDelete(col) {
    163. this.$confirm('是否删除?', '提示', {
    164. confirmButtonText: '确定',
    165. cancelButtonText: '取消',
    166. type: 'warning'
    167. }).then(_ => {
    168. let newHeaders = this.getRecursion(this.dataHeaders,col);
    169. console.log("处理完数据的", newHeaders);
    170. this.dataHeaders = newHeaders;
    171. // 数据更新,更新子组件状态
    172. this.flag = false;
    173. this.$nextTick(() => {
    174. this.flag = true;
    175. })
    176. // EventBus.$emit("getHeaders",this.headers)
    177. }).catch(() => {});
    178. },
    179. // 递归删除
    180. getRecursion(arr,col) {
    181. arr.map((item,index) => {
    182. if(item.label === col.label && item.deep === col.deep) {
    183. arr.splice(index,1);
    184. return;
    185. }
    186. if(item.children && item.children.length > 0) {
    187. this.getRecursion(item.children,col)
    188. }
    189. })
    190. return arr;
    191. },
    192. // 新增
    193. getAdd(col) {
    194. this.drawer = true;
    195. this.title = '新增列';
    196. this.submitText = '新增';
    197. this.column.label = '新增列';
    198. this.copyCol = col;
    199. this.emptyColumn();
    200. },
    201. // 编辑
    202. getEdit(col) {
    203. console.log("col",col);
    204. this.drawer = true;
    205. this.title = '编辑列',
    206. this.submitText = '保存',
    207. this.column.label = col.label;
    208. this.column.width = col.width;
    209. this.column.align = col.align;
    210. this.column.prop = col.prop.split(",").map(item => Number(item));
    211. if(col?.prop) {
    212. this.handleChange(this.column.prop);
    213. }
    214. console.log("this.column.prop",this.column.prop);
    215. this.column.propId = col.propId;
    216. this.copyCol = col;
    217. },
    218. // 编辑,新增里的取消
    219. handleClose() {
    220. this.$confirm('内容未保存,确认关闭?','提示',{
    221. confirmButtonText: '确定',
    222. cancelButtonText: '取消',
    223. type: 'warning'
    224. })
    225. .then(( )=> {
    226. this.emptyColumn();
    227. this.drawer = false;
    228. })
    229. .catch(() => {});
    230. },
    231. // 编辑,新增里的保存
    232. handleSubmit() {
    233. this.$refs['ruleFormRef'].validate((valid) => {
    234. if(!valid) {
    235. return false;
    236. }
    237. console.log("this.column",this.column);
    238. let col = this.copyCol
    239. this.loading = true;
    240. let item = {
    241. label: this.column.label,
    242. prop: this.column.prop.toString(),
    243. propId: this.column.propId,
    244. width: this.column.width,
    245. align: this.column.align,
    246. deep: 1,
    247. selected: false,
    248. children: []
    249. }
    250. if(this.title === '新增列') {
    251. if(!Object.keys(col).length == 0){
    252. item.deep = col.deep + 1;
    253. if(!col.children) {
    254. col.children = [];
    255. }
    256. col.children.push(item);
    257. }else {
    258. this.dataHeaders.push(item);
    259. }
    260. }else {
    261. this.flag = false;
    262. this.$nextTick(() => {
    263. this.flag = true;
    264. col.label = this.column.label;
    265. col.width = this.column.width;
    266. col.align = this.column.align;
    267. col.prop = this.column.prop.toString();
    268. col.propId = this.column.propId;
    269. })
    270. }
    271. this.loading = false;
    272. this.drawer = false;
    273. })
    274. },
    275. // 清空 column 的数据
    276. emptyColumn() {
    277. this.column.width = "";
    278. this.column.align = "center";
    279. this.column.prop = "";
    280. this.column.propId = "";
    281. },
    282. // 映射变量更改获取数据 ID
    283. handleChange(val) {
    284. let aa = val[val.length-1]
    285. apiGetList({
    286. deptId: aa
    287. }).then(res => {
    288. if(res.code == 200) {
    289. this.optionsId = res.rows;
    290. }
    291. })
    292. },
    293. // 映射变量下拉数据
    294. handleList() {
    295. let data = {}
    296. apiGetTreeselect(data).then((res) => {
    297. this.options = res.data;
    298. })
    299. },
    300. // 导出表格
    301. exportToExcel() {
    302. const table = this.$refs.table.$el;
    303. const workbook = XLSX.utils.table_to_book(table, { raw: true });
    304. const wbout = XLSX.write(workbook, { bookType: "xlsx", type: "array" });
    305. let tableName = '';
    306. if(this.tablekey === 'year') {
    307. tableName = '年报表';
    308. }else if(this.tablekey === 'month') {
    309. tableName = '月报表';
    310. }else {
    311. tableName = '日报表';
    312. }
    313. tableName ? tableName + ".xlsx" : "表格.xlsx";
    314. this.downloadExcel(wbout, tableName);
    315. },
    316. downloadExcel(buffer, filename) {
    317. const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
    318. const link = document.createElement("a");
    319. const url = URL.createObjectURL(blob);
    320. link.href = url;
    321. link.setAttribute("download", filename);
    322. document.body.appendChild(link);
    323. link.click();
    324. document.body.removeChild(link);
    325. URL.revokeObjectURL(url);
    326. },
    327. },
    328. mounted() {
    329. console.log("this.tablekey",this.tablekey);
    330. this.getTableList();
    331. this.handleList();
    332. // 接收子组件传递过来的事件
    333. EventBus.$on("getDelete",(col) => {
    334. this.getDelete(col) // 删除
    335. });
    336. EventBus.$on("getAdd",(col) => {
    337. this.getAdd(col) // 新增
    338. });
    339. EventBus.$on("getEdit",(col) => {
    340. this.getEdit(col) // 编辑
    341. });
    342. }
    343. }
    344. script>
    345. <style lang="scss" scoped>
    346. .searchBar {
    347. padding: 15px;
    348. padding-bottom: 0;
    349. }
    350. .scope_p {
    351. margin: 0;
    352. }
    353. .i_margin {
    354. margin: 0 10px;
    355. }
    356. .content-main {
    357. .table-title {
    358. display: flex;
    359. justify-content: space-around;
    360. margin-bottom: 10px;
    361. > p {
    362. flex: 1;
    363. font-weight: bold;
    364. font-size: 18px;
    365. }
    366. }
    367. .el-input {
    368. width: 250px;
    369. }
    370. .el-select {
    371. width: 280px;
    372. }
    373. }
    374. .form-padding {
    375. padding: 0 20px;
    376. ::v-deep .el-form-item__label {
    377. width: 88px;
    378. }
    379. }
    380. .drawer-footer {
    381. display: flex;
    382. align-items: center;
    383. justify-content: center;
    384. }
    385. .header-btn {
    386. display: flex;
    387. align-items: center;
    388. justify-content: center;
    389. .el-icon-plus {
    390. margin-left: 10px;
    391. cursor: pointer;
    392. }
    393. .el-icon-edit {
    394. margin-left: 10px;
    395. cursor: pointer;
    396. }
    397. .el-icon-delete {
    398. margin-left: 10px;
    399. cursor: pointer;
    400. }
    401. }
    402. .selected {
    403. color: #409eff;
    404. }
    405. .header-scroll::v-deep {
    406. .el-table__header-wrapper {
    407. overflow-x: auto;
    408. }
    409. /* 修改滚动条样式 */
    410. ::-webkit-scrollbar {
    411. height: 8px; /* 设置滚动条宽度 */
    412. }
    413. ::-webkit-scrollbar-track {
    414. background-color: #ffffff; /* 设置滚动条轨道背景色 */
    415. }
    416. ::-webkit-scrollbar-thumb {
    417. background-color: #ececec; /* 设置滚动条滑块颜色 */
    418. border-radius: 4px; /* 设置滚动条滑块圆角 */
    419. }
    420. ::-webkit-scrollbar-thumb:hover {
    421. background-color: #e2e2e2; /* 设置滚动条滑块鼠标悬停时颜色 */
    422. }
    423. }
    424. .error_label {
    425. color: #f56c6c !important;
    426. }
    427. style>

    3、ColumnItem.vue 代码内容,如下:

    1. <script>
    2. import { EventBus } from "../../utils/event-bus.js";
    3. export default {
    4. name: "ColumnItem",
    5. props: {
    6. col: {
    7. type: Object,
    8. required: true
    9. },
    10. maxLength: {
    11. type: Number,
    12. required: false
    13. },
    14. showAddBut: {
    15. type: Boolean,
    16. },
    17. },
    18. methods: {
    19. Add(col) {
    20. EventBus.$emit("getAdd",col)
    21. },
    22. Edit(col) {
    23. EventBus.$emit("getEdit",col)
    24. },
    25. Delete(col) {
    26. console.log(col);
    27. EventBus.$emit("getDelete",col)
    28. }
    29. },
    30. mounted() {
    31. },
    32. }
    33. script>
    34. <style lang="scss" scoped>
    35. .scope_p {
    36. margin: 0;
    37. }
    38. i {
    39. cursor: pointer;
    40. }
    41. .i_margin {
    42. margin: 0 10px;
    43. }
    44. style>

    4、在 .vue 文件中使用和数据,如下: 

    <HeaderTable tablekey="year" :maxLength="4" />

    5、效果图,如下:

     

    6、引入事件总线 EventBus 

    1. // event-bus.js
    2. import Vue from 'vue'
    3. export const EventBus = new Vue()
  • 相关阅读:
    【翻译】Raft 共识算法:集群成员变更
    网站使用谷歌登录 oauth java nuxt auth
    ArcGIS中ArcMap栅格遥感影像的监督分类
    【RocketMQ系列十】RocketMQ的核心概念说明
    C# HTML
    redis-基于docker搭建redis集群
    20.1CubeMx配置FMC控制SDRAM【W9825G6KH-6】
    机器学习_10、集成学习-AdaBoost
    Maven的常用命令
    BigDecimal的精度与刻度
  • 原文地址:https://blog.csdn.net/FitnessSnail/article/details/139317292