一次用vue3簡單封裝table組件的實(shí)戰(zhàn)過程
前言:
對(duì)于一個(gè)業(yè)務(wù)前端來講,工作中用的最多的組件我覺得大概率是table組件了,只要是數(shù)據(jù)展示就不可避免地用到這個(gè)組件。用久了就有點(diǎn)熟悉,就來鍛煉自己的封裝能力,為了更好的搬磚,封裝table組件。
首先,我們要思考我們封裝一個(gè)怎樣的表格,怎樣的形式更加方便。先定一個(gè)大概目標(biāo),往前走,剩下慢慢去完善。一般來說,我們更傾向于通過配置來實(shí)現(xiàn)表格,那就是說利用json格式來配置。
實(shí)現(xiàn)基礎(chǔ)功能表格
el-table中用el-table-column的prop
屬性來對(duì)應(yīng)對(duì)象中的鍵名即可填入數(shù)據(jù),用 label
屬性來定義表格的列名。那么這兩個(gè)主要屬性可以通過動(dòng)態(tài)綁定來進(jìn)行賦值,然后使用v-for
來進(jìn)行循環(huán)。
//app.vue <script setup> import TableVue from './components/Table.vue'; import {ref,reactive} from 'vue' const tableData = [ { date: '2016-05-03', name: 'Tom', address: 'No. 189, Grove St, Los Angeles', }, ] const options = reactive({ column:[ { prop:'date', label:'日期', width:180 }, { prop:'name', label:'名字', width:120 }, { prop:'address', label:'地址', } ] }) </script> <template> <TableVue :table-data="tableData" :options="options"></TableVue> </template> <style scoped></style>
//table.vue <script setup> import { ref,defineProps } from 'vue' const props= defineProps({ options:Object, tableData:Array }) const {column} = props.options </script> <template> <el-table :data="tableData" style="width: 100vw;"> <el-table-column v-for="(item,index) in column" :prop="item.prop" :label="item.label" :width="item.width??''" /> </el-table> </template> <style scoped></style>
好了,我們完成了通過json格式的最簡單配置展示了數(shù)據(jù),僅僅只有這樣是遠(yuǎn)遠(yuǎn)不夠的,連最基本的增刪改查都沒有。那么我們接下來就來實(shí)現(xiàn)增刪改功能。對(duì)于增刪改的方法基本上就是父子組件之間的方法調(diào)用,值的傳遞使用,以及多了一個(gè)對(duì)話框來進(jìn)行值的交互。這里也不難,下面是關(guān)鍵代碼
//app.vue //options里面新增以下屬性 index:true,//是否有序號(hào) boolean indexWidth:50,//序號(hào)列表寬度 number indexFixed:true,//序號(hào)是否為固定列 boolean menu:true, //是否有操作欄 boolean menuWidth:140,//操作欄寬度 number menuTitle:'操作2',//操作欄標(biāo)題 string menuFixed:true,//操作欄是否為固定列 boolean menuType:'text',//操作欄按鈕樣式 button/text //新增以下方法 //刪除 const rowDel = (index,row)=>{ console.log('del',index,row) } //編輯 const rowEdit=(type,row)=>{ console.log(type,row) } <TableVue :table-data="tableData" :options="options" @row-del="rowDel" @row-edit="rowEdit"></TableVue>
//table.vue新增以下方法 <script setup> const emits = defineEmits(["rowDel", "rowEdit"]) //把屬性解構(gòu)出來減少代碼量 const { options: op } = props const { column } = props.options //獲取子組件實(shí)例 const edit = ref('edit') //行數(shù)據(jù)刪除時(shí)觸發(fā)該事件 const rowDel = (index, row) => { emits("rowDel", index, row) } //更新數(shù)據(jù)后確定觸發(fā)該事件 const editBefore = (row,type) => { //將行內(nèi)屬性轉(zhuǎn)為普通對(duì)象傳遞 edit.value.openDialog(type,row,toRaw(column)) } const rowEdit=(type,form)=>{ emits("rowEdit",type,form) } </script> <template> <div class="menuOp"> <el-button type="danger" :icon="Plus" :size="op.size??'small'" @click="editBefore(_,'add')">新增</el-button> </div> <el-table :data="tableData" style="width: 100vw;" :size="op.size??'small'"> <el-table-column v-if="op.index" type="index" :width="op.indexWidth ?? 50" /> <el-table-column v-for="(item, index) in column" :prop="item.prop" :label="item.label" :width="item.width ?? ''" /> <el-table-column :fixed="op.menuFixed ? 'right' : ''" :label="op.menuTitle" :width="op.menuWidth" v-if="op.menu"> <template #default="scope"> <el-button :type="op.menuType ?? 'primary'" size="small" @click="editBefore(scope.row,'edit')">編輯</el-button> <el-button :type="op.menuType ?? 'primary'" size="small" @click="rowDel(scope.$index, scope.row,'del')">刪除</el-button> </template> </el-table-column> </el-table> <!-- 對(duì)話框 --> <editDialog ref="edit" @edit-submit="rowEdit"></editDialog> </template> <style scoped> .menuOp{ text-align: left; } </style>
進(jìn)一步定制化
雖然看著我們可以進(jìn)行簡單的增刪改,但是如果編輯有下拉框或者其他類型呢。表格行內(nèi)數(shù)據(jù)需要多樣性展示,而不是單純的純文本又該怎么辦呢。這時(shí)候我們需要用到vue里面的slot(插槽)
來解決這個(gè)問題了。
插槽(slot)
是 vue 為組件的封裝者提供的能力。允許開發(fā)者在封裝組件時(shí),把不確定的、希望由用戶指定的部分定義為插槽。
關(guān)鍵代碼截圖:
對(duì)話框我們利用插槽,那么同理,表格行內(nèi)的數(shù)據(jù)展示我們也可以進(jìn)行修改,來支持插槽
修改的關(guān)鍵代碼為:
增刪改基本功能到這里其實(shí)差不多,剩下就是一些細(xì)節(jié)性的判斷,接下來我們就來完成查詢部分的封裝。方法也是一樣,一般都是input框,其他有需求我們就利用插槽來實(shí)現(xiàn)。
//Search.vue <script setup> import { defineProps, onMounted, ref, defineEmits, toRaw, useSlots } from "vue"; const emits = defineEmits(["handleQuery", "handleReset"]); const search = ref({}); const slots = useSlots(); const handleQuery = () => { emits("handleQuery", search.value); }; const handleReset = () => { search.value = {}; emits("handleReset"); }; const props = defineProps({ row: { type: Object, default: () => {}, }, options: { type: Object, default: () => {}, }, search:{ type:Object, default:()=>{} } }); const column = toRaw(props.options.column); onMounted(() => { }); </script> <template> <div style="text-align: left; margin-bottom: 20px"> <el-form :inline="true" :model="search" class="demo-form-inline"> <template v-for="(item, index) in props.row"> <el-form-item :label="item.label" :label-width="`${item.searchLabel ?? options.searchLabel ?? 120}px`" > <slot v-if="slots.hasOwnProperty(`${item?.prop}Search`)" :name="`${item.prop}Search`" > <el-input v-model="search[item.prop]" :style="{ width: item.searchWidth ?? options.searchWidth + 'px' }" :placeholder="`請(qǐng)輸入${item.label}`" /> </slot> <el-input v-else v-model="search[item.prop]" :style="{ width: item.searchWidth ?? options.searchWidth + 'px' }" :placeholder="`請(qǐng)輸入${item.label}`" /> </el-form-item> </template> </el-form> <div> <el-button type="primary" size="small" @click="handleQuery" >查詢</el-button > <el-button type="primary" size="small" plain @click="handleReset" >重置</el-button > </div> </div> </template>
//Table.vue <SearchVue :options="op" :row="slotCloumn" @handleReset="handleReset" @handleQuery="handleQuery" :search="search" > <template v-for="(item, index) in slotCloumn" #[item?.prop+`Search`]> <slot :name="`${item?.prop}Search`"></slot> </template> </SearchVue>
就暫時(shí)先寫到這里了,最開始是想通過封裝一個(gè)簡單的組件,來鞏固自己的vue3語法熟悉程度。后來發(fā)現(xiàn)因?yàn)樗接邢藜由鲜亲约邯?dú)立思考,有很多地方其實(shí)卡住了。比如值的傳遞是用provide(inject)
比較好還是直接props
來方便。值的改動(dòng)需不需要用到computed
或者watch
。插槽的寫法能否更加簡便,不需要一級(jí)一級(jí)傳遞的那種,越想越多問題。自己技術(shù)能力不夠這是最大的問題,想法過于宏大,能力卻不行,打算日后精進(jìn)自己的技術(shù),然后約上幾個(gè)伙伴繼續(xù)去完善,只有思維碰撞才能產(chǎn)出好的代碼。
最后,把全部代碼附上
//App.vue <script setup> import TableVue from "./components/Table.vue"; import { ref, reactive,toRaw } from "vue"; const tableData = [ { date: "2016-05-03", name: "Tom", address: "No. 189, Grove St, Los Angeles", }, ]; const options = reactive({ index: true, //是否有序號(hào) boolean indexWidth: 50, //序號(hào)列表寬度 number indexFixed: true, //序號(hào)是否為固定列 boolean menu: true, //是否有操作欄 boolean menuWidth: 180, //操作欄寬度 number menuTitle: "操作2", //操作欄標(biāo)題 string menuFixed: true, //操作欄是否為固定列 boolean menuType: "text", //操作欄按鈕樣式 button/text searchLabel:150,//查詢框label的寬度 searchWidth:200,//查詢框組件的寬度 column: [ { prop: "date", label: "日期", width: 180, searchWidth:220, searchLabel:100,//行內(nèi)的設(shè)置優(yōu)先級(jí)高于全局 }, { prop: "name", label: "名字", width: 120, searchWidth:180 }, { prop: "address", label: "地址", //是否在表單彈窗中顯示 editDisplay: false, searchWidth:200 }, ], }); const form = ref({}) const search = ref({}) //刪除 const rowDel = (index, row) => { console.log("del", index, row); }; //編輯 const rowEdit = (type, row) => { // console.log(type, row); //這里因?yàn)闆]有思考明白到底如何利用v-model屬性進(jìn)行所有組件綁定傳遞,就使用這種蹩腳方法 console.log(Object.assign(row.value,form.value)) }; const tip=(row)=>{ console.log(row) } const handleReset=()=>{ console.log('reset') } const handleQuery=(param)=>{ let params = Object.assign(search.value,param) console.log(params) } </script> <template> <TableVue :table-data="tableData" :options="options" @row-del="rowDel" @row-edit="rowEdit" v-model="form" @handleQuery="handleQuery" @handleReset="handleReset" :search="search" > <!-- 查詢框插槽 --> <template #dateSearch> <el-date-picker v-model="search.date" type="datetime" placeholder="Select date and time" /> </template> <!-- 表格內(nèi)的插槽,插槽名為字段名 --> <template #date="{scope}"> <el-tag>{{scope.row.date}}</el-tag> </template> <!-- 操作欄插槽 --> <template #menu="{scope}" > <el-button icon="el-icon-check" @click="tip(scope.row)">自定義菜單按鈕</el-button> </template> <!-- 對(duì)話框插槽,插槽名字為對(duì)應(yīng)的字段名加上Form --> <template #dateForm> <el-date-picker v-model="form.date" type="datetime" placeholder="Select date and time" /> </template> </TableVue> </template> <style scoped></style>
//Search.vue <script setup> import { defineProps, onMounted, ref, defineEmits, toRaw, useSlots } from "vue"; const emits = defineEmits(["handleQuery", "handleReset"]); const search = ref({}); const slots = useSlots(); const handleQuery = () => { emits("handleQuery", search.value); }; const handleReset = () => { search.value = {}; emits("handleReset"); }; const props = defineProps({ row: { type: Object, default: () => {}, }, options: { type: Object, default: () => {}, }, search:{ type:Object, default:()=>{} } }); const column = toRaw(props.options.column); onMounted(() => { }); </script> <template> <div style="text-align: left; margin-bottom: 20px"> <el-form :inline="true" :model="search" class="demo-form-inline"> <template v-for="(item, index) in props.row"> <el-form-item :label="item.label" :label-width="`${item.searchLabel ?? options.searchLabel ?? 120}px`" > <slot v-if="slots.hasOwnProperty(`${item?.prop}Search`)" :name="`${item.prop}Search`" > <el-input v-model="search[item.prop]" :style="{ width: item.searchWidth ?? options.searchWidth + 'px' }" :placeholder="`請(qǐng)輸入${item.label}`" /> </slot> <el-input v-else v-model="search[item.prop]" :style="{ width: item.searchWidth ?? options.searchWidth + 'px' }" :placeholder="`請(qǐng)輸入${item.label}`" /> </el-form-item> </template> </el-form> <div> <el-button type="primary" size="small" @click="handleQuery" >查詢</el-button > <el-button type="primary" size="small" plain @click="handleReset" >重置</el-button > </div> </div> </template>
//table.vue <script setup> import { ref, defineProps, defineEmits, toRaw, onMounted, useSlots } from "vue"; import { Delete, Plus } from "@element-plus/icons-vue"; import editDialog from "./Dialog.vue"; import SearchVue from "./Search.vue"; const props = defineProps({ options: Object, tableData: Array, modelValue: { type: Object, default: () => {}, }, }); const emits = defineEmits([ "rowDel", "rowEdit", "update:modelValue", "handleQuery", ]); //把屬性解構(gòu)出來減少代碼量 const { options: op } = props; const { column } = props.options; //獲取編輯對(duì)話框里面所有屬性,用于動(dòng)態(tài)生成插槽 const slotCloumn = toRaw(column); //獲取子組件實(shí)例 const edit = ref("edit"); //獲取插槽實(shí)例 const slots = useSlots(); //行數(shù)據(jù)刪除時(shí)觸發(fā)該事件 const rowDel = (index, row) => { emits("rowDel", index, row); }; //更新數(shù)據(jù)后確定觸發(fā)該事件 const editBefore = (row, type) => { //將行內(nèi)屬性轉(zhuǎn)為普通對(duì)象傳遞 edit.value.openDialog(type, row, toRaw(column)); }; const rowEdit = (type, form) => { emits("rowEdit", type, form); emits("update:modelValue", form); }; const handleQuery = (search) => { emits("handleQuery", search); }; const handleReset = () => { emits("handleReset"); }; onMounted(() => { console.log("slots", slots); }); </script> <template> <div> <SearchVue :options="op" :row="slotCloumn" @handleReset="handleReset" @handleQuery="handleQuery" :search="search" > <template v-for="(item, index) in slotCloumn" #[item?.prop+`Search`]> <slot :name="`${item?.prop}Search`"></slot> </template> </SearchVue> </div> <div class="menuOp"> <el-button type="danger" :icon="Plus" :size="op.size ?? 'small'" @click="editBefore(_, 'add')" >新增</el-button > </div> <el-table :data="tableData" style="width: 100vw" :size="op.size ?? 'small'"> <el-table-column v-if="op.index" type="index" :width="op.indexWidth ?? 50" /> <template v-for="(item, index) in column"> <el-table-column :label="item.label" v-if="slots.hasOwnProperty(item?.prop)" > <template #default="scope"> <slot :name="item.prop" :scope="scope"></slot> </template> </el-table-column> <el-table-column v-else :prop="item.prop" :label="item.label" :width="item.width ?? ''" /> </template> <el-table-column :fixed="op.menuFixed ? 'right' : ''" :label="op.menuTitle" :width="op.menuWidth" v-if="op.menu" > <template #default="scope"> <el-button :type="op.menuType ?? 'primary'" size="small" @click="editBefore(scope.row, 'edit')" >編輯</el-button > <el-button :type="op.menuType ?? 'primary'" size="small" @click="rowDel(scope.$index, scope.row, 'del')" >刪除</el-button > <!-- 利用作用域插槽將數(shù)據(jù)傳遞過去 --> <slot name="menu" :scope="scope"></slot> </template> </el-table-column> </el-table> <!-- 對(duì)話框 --> <editDialog ref="edit" @edit-submit="rowEdit"> <template v-for="(item, index) in slotCloumn" #[item?.prop+`Form`]="scope"> <slot :name="`${item?.prop}Form`" v-bind="scope"></slot> </template> </editDialog> </template> <style scoped> .menuOp { text-align: left; } </style>
//Dialog.vue <script setup> import { ref, defineExpose, defineEmits, useSlots, onMounted } from "vue"; const emits = defineEmits(["editSubmit"]); const dialogFormVisible = ref(false); const form = ref({}); const tp = ref(""); let columns = []; //獲取當(dāng)前已實(shí)例化的插槽,然后去判斷插槽是否使用了 const slots = useSlots(); const openDialog = (type, row, column) => { dialogFormVisible.value = true; columns = column; tp.value = type; if (type === "edit") { //如果編輯框設(shè)置了editDisplay為false,則刪除該屬性 columns.map((x) => { if (x.editDisplay === false) { delete row[x.prop]; } }); form.value = JSON.parse(JSON.stringify(row)); }else{ form.value={} } }; const handleSubmit = () => { emits("editSubmit", tp, form); }; onMounted(() => { console.log("===", slots); }); defineExpose({ openDialog, }); </script> <template> <el-dialog v-model="dialogFormVisible" append-to-body :title="tp==='add'?'新增':'編輯'"> <el-form :model="form"> <template v-for="(item, index) in columns"> <el-form-item v-if="tp==='add'?item.addDisplay??true:item.editDisplay ?? true" :key="index" :label="item.label" label-width="120px" > <slot :name="`${item?.prop}Form`" v-if="slots.hasOwnProperty(`${item?.prop}Form`)" > <!-- 因?yàn)樵趖able組件已經(jīng)開始生成所有關(guān)于編輯的插槽,所以全部都有實(shí)例,需要給個(gè)默認(rèn)顯示,否則會(huì)空白 --> <el-input v-model="form[item?.prop]" /> </slot> <el-input v-model="form[item?.prop]" v-else /> </el-form-item> </template> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="dialogFormVisible = false">取消</el-button> <el-button type="primary" @click="handleSubmit"> 確認(rèn) </el-button> </span> </template> </el-dialog> </template>
總結(jié)
到此這篇關(guān)于用vue3簡單封裝table組件的文章就介紹到這了,更多相關(guān)vue3封裝table組件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue官方推薦AJAX組件axios.js使用方法詳解與API
axios是Vue官方推薦AJAX組件,下面為大家介紹axios.js庫的詳細(xì)使用方法與API介紹2018-10-10vue如何實(shí)現(xiàn)路由跳轉(zhuǎn)到外部鏈接界面
這篇文章主要介紹了vue如何實(shí)現(xiàn)路由跳轉(zhuǎn)到外部鏈接界面,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。2022-10-10Vue.js分頁組件實(shí)現(xiàn):diVuePagination的使用詳解
這篇文章主要介紹了Vue.js分頁組件實(shí)現(xiàn):diVuePagination的使用詳解,需要的朋友可以參考下2018-01-01