如何巧用Vue.extend繼承組件實現el-table雙擊可編輯(不使用v-if、v-else)
問題描述
有一個簡單的表格,產品要求實現雙擊可編輯
看瞭一下網上的帖子,大多數都是搞兩部分dom,一塊是輸入框,用於編輯狀態填寫;另一塊是普通標簽,用於在不編輯顯示狀態下呈現單元格文字內容。再加上一個flag標識搭配v-if和v-else去控制編輯狀態、還是顯示狀態。大致代碼如下:
<el-table-column align="center" label="姓名" > <template slot-scope="scope"> <!--isClick就是標識狀態,狀態處於編輯時候,顯示輸入框,狀態屬於呈現狀態就顯示文本內容--> <el-input v-if="scope.row.isClick" v-model="scope.row.name" @blur="blurFn(scope.row)"></el-input> <span @click="clickCell(scope.row)" v-else>{{scope.row.name}}</span> </template> </el-table-column>
這種方式有其適用場景,但是得每個el-table-column列中都加上el-input和span以及v-if和v-else。我們嘗試一下動態添加el-input,就是點擊那個單元格,給那個單元格添加el-input讓其處於可編輯狀態,然後適時移除即可。這樣的話,很多列的時候,就不用加很多個v-if和v-else啦。我們先看一下效果圖
效果圖
代碼思路
- 第1步:給el-table綁定雙擊事件
@cell-dblclick='dblclick'
,再雙擊事件的回調函數中,可以得知點擊的是哪一行、那一列、那個單元格dom,以及點擊事件。dblclick(row, column, cell, event) {...}
,這個是餓瞭麼官方提供的,沒啥好說的 - 第2步:重點來嘍
- 第2.1步:單元格雙擊事件以後,我們首先創建一個el-input標簽,然後把點擊的這個單元格的值,作為參數props讓這個el-input接收,這樣的話el-input就會顯示這個單元格的值瞭,就可以編輯瞭。問題一:如何創建一個el-input標簽? ,客官稍等,下方會解答
- 第2.2步:把創建好的el-input標簽替換掉原來的單元格span標簽,這樣的話,就可以看到單元格變成瞭可輸入的輸入框瞭。問題二:如何把新創建的el-input標簽,替換原有的span標簽 ,客官稍等,下方會解答
- 第2.3步,當用戶編輯完瞭點擊別處時候,即輸入框失去焦點的時候,再把el-input輸入框標簽移除掉,恢復默認的span標簽(當然失去焦點的時候,就要發請求修改數據瞭)問題三:如何移除el-input標簽,並恢復原有的span標簽,客官稍等,下方會解答
- 這樣的話,每次雙擊搞一個input標簽用於修改,每次改完瞭失去焦點,就恢復默認單元格展示狀態瞭,功能就實現瞭
代碼思路中的三個問題解答
問題一:如何創建一個el-input標簽?
我們知道,如果是創建原生的input標簽並指定一個值,比較簡單,直接:
let input = document.createElement('input') // 創建一個input標簽 input.value = '孫悟空' // 給input標簽賦值 document.body.appendChild(input) // 把input標簽追加到文檔body中
不過el-input標簽不能通過上述方式創建,因為document.createElement()方法雖然可以創建出來el-input標簽,但是dom並不認識這個el-input標簽,所以頁面沒有變化。畢竟餓瞭麼的el-input也是把input標簽做一個二次封裝的
所以,這裡我們可以使用Vue.extend()方法去繼承一個組件並暴露出去,而繼承的這個組件中又有一個input標簽,所以那個需要使用,那裡就可以引入並new出來一個el-input瞭。關於Vue.extend()的定義啥的,這裡不贅述,詳情看官方文檔。筆者之前也寫過一篇Vue.extend文章,傳送門:https://www.jb51.net/article/251484.htm
首先搞一個.vue文件,用於繼承
// input.vue文件 <template> <div class="cell"> <el-input ref="elInputRef" size="mini" v-model.trim="cellValue" ></el-input> </div> </template> props: { cellValue: { type: String | Number, default: "", }, }
然後定義一個data.js文件,繼承input.vue文件,並暴露
// data.js import Vue from "vue"; import definedInput from "./input.vue"; // vue繼承這個input組件,就相當於一個構造函數瞭 const inputC = Vue.extend(definedInput); // 暴露出去,哪裡需要哪裡引入 export default { inputC, }
頁面中引入並使用
// page.vue import extendComponents from "./threeC/data"; // 1. 引入 new extendComponents.inputC({ // 2. 實例化 propsData: { // 使用propsData對象傳遞參數,子組件在props中可以接收到 cellValue: cellValue, // 傳遞單元格的值 }, }).$mount(cell.children[0]);// 3. 掛載
propsData對象用於給繼承的組件傳遞參數,也可以傳遞一個函數,從而繼承組件通過這個函數通知外部使用組件,詳情見後續完整代碼
問題二三:el-input標簽和span標簽的來回替換恢復
使用$mount
方法去做來回替換,$mount
可以把一個子dom元素追加到父dom元素內部,相當於appendChild
然後這裡需要有一個替換的時機,就是實例化的組件中的el-input失去焦點的時候,去通知外部使用的組件,所以可以在外部使用是,在propsData中傳遞一個函數到繼承的組件,如:
// 外部組件傳遞 new extendComponents.inputC({ propsData: { cellValue: cellValue, // 傳遞單元格的值 saveRowData: this.saveRowData, // 傳遞回調函數用於通知,繼承組件中可以觸發之 }, }).$mount(cell.children[0]); saveRowData(params){ console.log('收到繼承組件消息通知啦參數為:',params) }
// 內部組件失去焦點時候通知 <el-input ref="elInputRef" size="mini" v-model.trim="cellValue" @blur="blurFn" ></el-input> props: { cellValue: { type: String | Number, default: "", }, saveRowData: Function, // 外部,傳遞進來一個函數,當這個el-input失去焦點的時候,通過此函數通知外部 } blurFn() { // 失去焦點,再拋出去,通知外部 this.saveRowData({ cellValue: this.cellValue, // 其他參數 }); },
所以當內層失去焦點的時候,就可以通知外層去做一個替換瞭,就是把單元格dom重新做一個$mount
掛載,就把el-input替換成瞭span瞭,為瞭進一步理解,這裡的span我們也可以使用繼承的方式,是new實例化使用,詳情見下方完整代碼
完整代碼
目錄結構
threeC
— data.js
— input.vue
— span.vue
three.vue
用於繼承的el-input組件
input.vue
<template> <div class="cell"> <el-input ref="elInputRef" size="mini" v-model.trim="cellValue" @blur="blurFn" ></el-input> </div> </template> <script> export default { props: { cellValue: { type: String | Number, default: "", }, saveRowData: Function, // 外部,傳遞進來一個函數,當這個el-input失去焦點的時候,通過此函數通知外部 cellDom: Node, // 單元格dom row: Object, // 單元格所在行數據 property: String, // 單元格的key }, mounted() { // 用戶雙擊後,讓其處於獲取焦點的狀態 this.$refs.elInputRef.focus(); }, methods: { blurFn() { // 失去焦點,再拋出去,通知外部 this.saveRowData({ cellValue: this.cellValue, cellDom: this.cellDom, row: this.row, property: this.property, }); }, }, }; </script> <style> .cell { width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; box-sizing: border-box; padding: 0 8px; } </style>
用於繼承的span組件
span.vue
<template> <span class="cell">{{ cellValue }}</span> </template> <script> export default { props: { cellValue: { type: String | Number, default: "", }, }, }; </script>
統一繼承並暴露data.js文件
import Vue from "vue"; import definedInput from "./input.vue"; import definedSpan from "./span.vue"; const inputC = Vue.extend(definedInput); const spanC = Vue.extend(definedSpan); export default { inputC, spanC, }
使用繼承的three.vue組件
<template> <div id="app"> <el-table @cell-dblclick="dblclick" :cell-class-name="cellClassName" height="480" :data="tableData" border > <el-table-column align="center" type="index" label="序號" width="50"> </el-table-column> <el-table-column align="center" prop="name" label="姓名" width="100"> </el-table-column> <el-table-column align="center" prop="age" label="年齡" width="100"> </el-table-column> <el-table-column align="center" prop="home" label="傢鄉"> </el-table-column> </el-table> </div> </template> <script> // 引入繼承組件對象,可取其身上的inputC構造函數、或spanC構造函數生成組件dom import extendComponents from "./threeC/data"; export default { data() { return { tableData: [ { name: "孫悟空", age: 500, home: "花果山水簾洞", }, { name: "豬八戒", age: 88, home: "高老莊", }, { name: "沙和尚", age: 1000, home: "通天河", }, ], /** * 存一份舊的值,用於校驗是否發生變化,是否修改 * */ oldCellValue: null, }; }, methods: { cellClassName({ row, column, rowIndex, columnIndex }) { row.index = rowIndex; // 自定義指定一個索引,下方能夠用到 }, dblclick(row, column, cell, event) { // 1. 序號列單元格不允許編輯,別的列單元格可以編輯 if (column.label == "序號") { this.$message({ type: "warning", message: "序號列不允許編輯", }); return; } // 2. 存一份舊的單元格的值 this.oldCellValue = row[column.property]; // 3. 然後把單元格的值,作為參數傳遞給實例化的input組件 let cellValue = row[column.property]; // 4. 實例化組件以後,帶著參數,再掛載到對應位置 new extendComponents.inputC({ propsData: { // 使用propsData對象傳遞參數,子組件在props中可以接收到 cellValue: cellValue, // 傳遞單元格的值 saveRowData: this.saveRowData, // 傳遞回調函數用於保存行數據,組件中可以觸發之 cellDom: cell, // 傳遞這個dom元素 row: row, // 傳遞雙擊的行的數據 property: column.property, // 傳遞雙擊的是哪個字段 }, }).$mount(cell.children[0]); // 5. $mount方法,用於將某個dom掛載到某個dom上 }, /** * 失去焦點的時候有以下操作 * 1. 校驗新值是否等於原有值,若等於,說明用戶未修改,就不發請求。若不等於就發請求,然後更新tableData數據 * 2. 然後使用$mount方法,掛載一個新的span標簽dom在頁面上,即恢復原樣,而span標簽也是實例化的哦 * */ saveRowData(params) { console.log("繼承的子組件傳遞過來的數據", params); // 1. 看看用戶是否修改瞭 if (params.cellValue == this.oldCellValue) { console.log("未修改數據,不用發請求"); } else { params.row[params.property] = params.cellValue; // 這裡模擬一下發瞭請求,得到最新表體數據以後,更新tableData setTimeout(() => { // 給那個數組的 第幾項 修改為什麼值 this.$set(this.tableData, params.row.index, params.row); }, 300); } // 2. 恢復dom節點成為原來的樣子,有下面兩種方式 /** * 方式一:使用官方推薦的$mount去掛載到某個節點上,上方也是 * */ new extendComponents.spanC({ propsData: { cellValue: params.cellValue, }, }).$mount(params.cellDom.children[0]); /** * 方式二:使用原生js去清空原節點內容,同時再添加子元素 * */ // let span = document.createElement("span"); // 創建一個span標簽 // span.innerHTML = params.cellValue; // 指定span標簽的內容的值 // span.classList.add("cell"); // 給span標簽添加class為cell // params.cellDom.innerHTML = ""; // 清空剛操作的input標簽的內容 // params.cellDom.appendChild(span); // 再把span標簽給追加上去,恢復原樣 }, }, }; </script> <style lang="less" scoped> #app { width: 100%; height: 100vh; box-sizing: border-box; padding: 50px; } </style>
總結
使用Vue.extend()
方法,可以繼承一些組件,甚至繼承一些復雜的組件,在實際業務場景中會有巧妙的使用。具體業務場景具體分析。
此外,上述代碼中是el-input的繼承
,其實,我們也可以做el-select的繼承
,思路和上方類似,這樣就可以在表格中雙擊單元格,選擇並更改對應的下拉框更改el-table的單元值瞭,比如如果有性別這一列,那是下拉框的形式的。道友們可以按照這個思路發散哦…
到此這篇關於如何巧用Vue.extend繼承組件實現el-table雙擊可編輯(不使用v-if、v-else)的文章就介紹到這瞭,更多相關Vue.extend實現el-table雙擊可編輯內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- vue 中使用 vxe-table 制作可編輯表格的使用過程
- 如何在vue中使用jsx語法
- Vue extend使用示例深入分析
- vue在自定義組件上使用v-model和.sync的方法實例
- vue+el-table實現合並單元格