vue3新擬態(tài)組件庫開發(fā)流程之table組件源碼分析
基礎(chǔ)表格
首先開發(fā)table組件之前,先想好要用什么樣式的api,因?yàn)楣P者在生產(chǎn)工作中用的都是element,所以前面幾個(gè)組件風(fēng)格和element類似,但是這次不打算用element的風(fēng)格了,打算換一種,直接展示:
我們期望用戶這樣使用:
<script setup> const dataList = [ { id: 1, name: '《JavaEE企業(yè)應(yīng)用實(shí)戰(zhàn)》', author: 'dev1ce', price: '10.22', desc: '書中最后講解的項(xiàng)目案例,涵蓋從前期設(shè)計(jì)到最終實(shí)施的整個(gè)過程,對全書知識(shí)點(diǎn)進(jìn)行串聯(lián)和鞏固,使讀者融會(huì)貫通,掌握J(rèn)ava Web開發(fā)的精髓。' }, { id: 2, name: '《代碼整潔之道》', author: 'R0bert', price: '10.22', desc: '整潔代碼并非遵循一組規(guī)則編寫的。不可能因?yàn)閷W(xué)習(xí)一套金規(guī)玉律就成為軟件大師。專業(yè)精神和手工藝來自于推動(dòng)規(guī)則形成的價(jià)值。' }, { id: 3, name: '《ECMAScript 6 入門教程》', author: 'y1feng', price: '10.22', desc: '本書是一本開源的 JavaScript 語言教程,全面介紹 ECMAScript 6 新引入的語法特性。' }, ] const columnsList = [ { title: '書名', key: 'name' }, { title: '作者', key: 'author' }, { title: '價(jià)格', key: 'price' }, { title: '簡介', key: 'desc' } ] </script> <template> <sanorin-table :columns="columnsList" :data="dataList"/> </template>
依照這個(gè)寫出以下代碼
<script setup> import { ref, computed } from 'vue' import { useProp, useNeumorphism } from '../mixin/neumorphism' const props = defineProps({ ...useProp, ...{ data: { // dataList type: Array, default: () => [], }, columns: { // columnsList type: Array, default: () => [], }, } }) const { baseStyleObject } = useNeumorphism(props) let styleObject = computed(() => ({ // '--font-size': `${props.size-26}px`, '--line-height': `${props.size-20}px`, '--limit-size': `${props.size-28}px` })) </script> <template> <div :style="{...baseStyleObject,...styleObject}"> <table style=""> <thead> <tr class="neumorphism"> <!-- 表頭循環(huán) --> <th v-for="col in columns" :key="col.key">{{col.title}}</th> </tr> </thead> <tbody> <!-- 表體循環(huán) --> <tr class="neumorphism" v-for="row in data" :key="row.id"> <td v-for="col in columns" :key="col.key"> <span> {{row[col.key]}} </span> </td> </tr> </tbody> </table> </div> </template> <script> export default { name: 'sanorin-table', } </script> <style scoped> @import "../../style/index.css"; @import "../../style/neumorphism.css"; table { width: 100%; /* border-collapse: collapse; */ empty-cells: show; border-collapse:separate; border-spacing:0px 10px; } table td, table th { color: var(--text-color); padding: 8px 16px 8px 16px; text-align: left; } table th { color: var(--text-back-color) !important; font-weight: 600; white-space: nowrap; } table tr{ margin-top: 20px; } </style>
最后出來的效果就是:
然后實(shí)現(xiàn)了這個(gè)后我們開始做后面的,先從固定表頭開始。
固定表頭
固定表頭有三種方法,詳見我的另一篇文章http://www.dbjr.com.cn/article/91143.htm
這里先采用第一種,以后不能滿足需求了再改成后面的方法。
效果和代碼如下:
<script setup> import { ref, computed } from 'vue' import { useProp, useNeumorphism } from '../mixin/neumorphism' const props = defineProps({ ...useProp, ...{ data: { // dataList type: Array, default: () => [], }, columns: { // columnsList type: Array, default: () => [], }, } }) const { baseStyleObject } = useNeumorphism(props) let styleObject = computed(() => ({ })) </script> <template> <div class="san-table scrollbar" :style="{...baseStyleObject,...styleObject}"> <table> <thead> <tr class="neumorphism"> <!-- 表頭循環(huán) --> <th v-for="col in columns" :key="col.key">{{col.title}}</th> </tr> </thead> <tbody> <!-- 表體循環(huán) --> <tr class="neumorphism" v-for="row in data" :key="row.id"> <td v-for="col in columns" :key="col.key"> <span> {{row[col.key]}} </span> </td> </tr> </tbody> </table> </div> </template> <script> export default { name: 'sanorin-table', } </script> <style scoped> @import "../../style/index.css"; @import "../../style/neumorphism.css"; .san-table{ padding: 0px 20px 20px 20px; height: 200px; position: relative; width: 700px; overflow: auto; } table { width: 100%; table-layout: fixed; empty-cells: show; border-collapse:separate; border-spacing:0px 10px; } thead { position: sticky; top: 10px; } thead:before{ position: absolute; content: ''; width: calc(100% + var(--shadow-blur) * 2); transform: translate(calc(var(--shadow-blur) * -1) , -10px); height: 20px; background-color: var(--main-color); z-index: -1; } table td, table th { color: var(--text-color); padding: 8px 16px 8px 16px; text-align: left; } table th { color: var(--text-back-color) !important; font-weight: 600; white-space: nowrap; } </style>
高度/流體高度
可以為 Table 設(shè)置一個(gè)高度。(height)
當(dāng)數(shù)據(jù)量動(dòng)態(tài)變化時(shí),可以為 Table 設(shè)置一個(gè)最大高度。(maxHeight) 通過設(shè)置max-height屬性為 Table 指定最大高度。此時(shí)若表格所需的高度大于最大高度,則會(huì)顯示一個(gè)滾動(dòng)條。
只要在sanorin-table元素中定義了height或者maxHeight屬性,即可實(shí)現(xiàn)固定表頭的表格,而不需要額外的代碼。
代碼如下:
<script setup> import { ref, computed, reactive } from 'vue' import { useProp, useNeumorphism } from '../mixin/neumorphism' const props = defineProps({ ...useProp, ...{ data: { // dataList type: Array, default: () => [], }, columns: { // columnsList type: Array, default: () => [], }, height: { type: Number, }, maxHeight: { type: Number, } } }) // 高度設(shè)置 let tableHeightStyleObj = computed(() => { let styleObj = ((e) => { if (e.maxHeight) return { maxHeight: e.maxHeight + 'px' } if (e.height) return { height: e.height + 'px' } return {} })({...props}) return styleObj }) const { baseStyleObject } = useNeumorphism(props) let styleObject = computed(() => ({ ...tableHeightStyleObj.value })) </script> <template> <div class="san-table scrollbar" :style="{...baseStyleObject,...styleObject}"> <table> <colgroup> <col v-for="(col, index) in columns" :key="index"> </colgroup> <thead> <tr class="neumorphism"> <th v-for="col in columns" :key="col.key"> {{col.title}} </th> </tr> </thead> <tbody> <tr class="neumorphism" v-for="row in data" :key="row.id"> <td v-for="col in columns" :key="col.key"> {{row[col.key]}} </td> </tr> </tbody> </table> </div> </template> <script> export default { name: 'sanorin-table', } </script> <style scoped> @import "../../style/index.css"; @import "../../style/neumorphism.css"; .san-table{ padding: 0px 20px 20px 20px; position: relative; width: 700px; overflow: auto; } table { width: 100%; table-layout: fixed; empty-cells: show; border-collapse:separate; border-spacing:0px 10px; } thead { position: sticky; top: 10px; } thead:before{ position: absolute; content: ''; width: calc(100% + var(--shadow-blur) * 2); transform: translate(calc(var(--shadow-blur) * -1) , -10px); height: 20px; background-color: var(--main-color); z-index: -1; } table td, table th { color: var(--text-color); padding: 8px 16px 8px 16px; text-align: left; } table th { color: var(--text-back-color) !important; font-weight: 600; white-space: nowrap; } </style>
自定義列寬
接下來加入寬度控制,希望在columns 傳入的數(shù)組對象內(nèi)加入寬度,示例如下:
const columnsList = [ { title: '書名', key: 'name', width: 100, }, { title: '作者', key: 'author', width: 100, }, { title: '價(jià)格', key: 'price', width: 100, }, { title: '簡介', key: 'desc', minWidth: 350, } ]
希望達(dá)到以下效果
1、含有width的列,寬度固定,不隨瀏覽器寬度變化而變化
2、含有minWidth的列,在大于設(shè)定值時(shí),自動(dòng)填充 table 剩余寬度,小于設(shè)定值時(shí),固定該寬度
3、不包含width和minWidth的列,自動(dòng)填充 table 剩余寬度
根據(jù)我們的需求,我們需要單獨(dú)控制每一列的寬度展示,并在瀏覽器寬度變化時(shí)實(shí)時(shí)的重新計(jì)算并且重新渲染列。
首先定義出一個(gè)方法,用來計(jì)算每一列在當(dāng)前情況下所要的寬度,再綁定要dom上。然后,每次表格變化/瀏覽器寬度變化時(shí)候就能實(shí)時(shí)響應(yīng)改變Table的寬度了。
const initColumns = () => { // 計(jì)算每一列在當(dāng)前情況下所要的寬度 } watch(() => props.columns, () => { initColumns() }); onMounted(() => { nextTick(() => { initColumns(); on(window, 'resize', throttle(() => initColumns(), 400)); }); }); onBeforeUnmount(() => off(window, 'resize', () => initColumns()));
全部代碼:
<script setup> import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from 'vue' import { on, off } from '../../utils/listener' import { throttle } from "../../utils/debounce&throttle" import { useProp, useNeumorphism } from '../mixin/neumorphism' const table = ref() // 與html中ref=""對應(yīng),定位dom元素 const props = defineProps({ ...useProp, ...{ data: { // dataList type: Array, default: () => [], }, columns: { // columnsList type: Array, default: () => [], }, height: { // height type: Number, }, maxHeight: { // 流體高度 type: Number, }, minUnsetWidth: { // 未設(shè)置寬度時(shí)最小寬度 type: Number, default: 80 } } }) // 高度設(shè)置 let tableHeightStyleObj = computed(() => { let styleObj = ((e) => { if (e.maxHeight) return { maxHeight: e.maxHeight + 'px' } if (e.height) return { height: e.height + 'px' } return {} })({...props}) return styleObj }) // 列寬設(shè)置 let col = ref([]) const { columns, minUnsetWidth } = props const _min_column_width = minUnsetWidth // 未設(shè)置寬度時(shí)最小寬度 const initColumns = () => { col.value = (() => { let _total_width = table.value.offsetWidth // 表格dom元素總寬度 let _needed_minWidth = columns.reduce((t, v) => { // 需要的最小寬度 t += v.width || v.minWidth || _min_column_width return t }, 0) // 需要的最小寬度比總寬度大,則取minWidth即可 if (_needed_minWidth >= _total_width) return columns.reduce((t, v) => { let n = v.width || v.minWidth || _min_column_width t = [...t, n] return t }, []) // 需要的最小寬度比總寬度大,則要把minWidth加權(quán),權(quán)重為(未分配的寬度 / minWidth之和) let _unassigned_width = columns.reduce((t, v) => { t += v.minWidth || 0 return t }, 0) let _assigned_width = _needed_minWidth - _unassigned_width let _width_power = (_total_width - _assigned_width) / _unassigned_width return columns.reduce((t, v) => { let n = v.width || (v.minWidth ? (_width_power * v.minWidth).toFixed(2) : _min_column_width) t = [...t, n] return t }, []) })() } watch(() => props.columns, () => { initColumns() }) const throttleInitColumns = () => throttle(() => initColumns(), 400) onMounted(() => { nextTick(() => { initColumns() on(window, 'resize', throttleInitColumns) }) }) onBeforeUnmount(() => off(window, 'resize', throttleInitColumns)) const { baseStyleObject } = useNeumorphism(props) let styleObject = computed(() => ({ ...tableHeightStyleObj.value })) </script> <template> <div class="san-table scrollbar" :style="{...baseStyleObject,...styleObject}"> <table ref="table"> <colgroup> <col v-for="(item, index) in col" :key="index" :width="`${item}px`"> </colgroup> <thead> <tr class="neumorphism"> <th v-for="col in columns" :key="col.key"> {{col.title}} </th> </tr> </thead> <tbody> <tr class="neumorphism" v-for="row in data" :key="row.id"> <td v-for="col in columns" :key="col.key"> {{row[col.key]}} </td> </tr> </tbody> </table> </div> </template> <script> export default { name: 'sanorin-table', } </script> <style scoped> @import "../../style/index.css"; @import "../../style/neumorphism.css"; .san-table{ padding: 0px 20px 20px 20px; position: relative; overflow: auto; } table { width: 100%; table-layout: fixed; empty-cells: show; border-collapse:separate; border-spacing:0px 10px; } thead { position: sticky; top: 10px; } thead:before{ position: absolute; content: ''; width: calc(100% + var(--shadow-blur) * 2); transform: translate(calc(var(--shadow-blur) * -1) , -10px); height: 20px; background-color: var(--main-color); z-index: -1; } table td, table th { color: var(--text-color); padding: 8px 16px 8px 16px; text-align: left; word-break:break-all; } table th { color: var(--text-back-color) !important; font-weight: 600; white-space: nowrap; } </style>
其中用到的兩個(gè)js,防抖節(jié)流和注冊監(jiān)聽這里也放下吧
/* 防抖節(jié)流函數(shù) */ let timeout = null // 創(chuàng)建一個(gè)標(biāo)記用來存放定時(shí)器的返回值 let count = 0; export function debounce(fn, wait = 1000, immediate = false) { return function () { const args = arguments; if (immediate) { if (count == 0) { fn.apply(this, arguments) count++; } else { if (timeout) { clearTimeout(timeout) // 每當(dāng)用戶輸入的時(shí)候把前一個(gè) setTimeout clear 掉 } timeout = setTimeout(() => { fn.apply(this, arguments) }, wait) } } else { if (timeout) { clearTimeout(timeout) // 每當(dāng)用戶輸入的時(shí)候把前一個(gè) setTimeout clear 掉 } timeout = setTimeout(() => { fn.apply(this, arguments) }, wait) } }() } let canRun = true; let count1 = 0; export function throttle(fn, wait = 1000, immediate = true) { return function () { if (immediate) { if (count1 == 0) { fn.apply(this, arguments); count1++; } else { if (canRun) { canRun = false setTimeout(function () { fn.apply(this, arguments) canRun = true }, wait); } } } else { if (!canRun) return canRun = false setTimeout(function () { fn.apply(this, arguments) canRun = true }, wait); } }() }
/** * 綁定事件 on(element, event, handler) */ export const on = (element, event, handler) => { if (document.addEventListener) { if (element && event && handler) { element.addEventListener(event, handler, false) } } } /** * 解綁事件 off(element, event, handler) */ export const off = (element, event, handler) => { if (document.removeEventListener) { if (element && event) { element.removeEventListener(event, handler, false) } } }
序號
自定義內(nèi)容(slot)
固定列
全選
展開
到此這篇關(guān)于vue3新擬態(tài)組件庫開發(fā)流程——table組件源碼的文章就介紹到這了,更多相關(guān)vue3 table組件源碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue Object.defineProperty及ProxyVue實(shí)現(xiàn)雙向數(shù)據(jù)綁定
這篇文章主要介紹了Vue Object.defineProperty及ProxyVue實(shí)現(xiàn)雙向數(shù)據(jù)綁定,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09nuxt.js寫項(xiàng)目時(shí)增加錯(cuò)誤提示頁面操作
這篇文章主要介紹了nuxt.js寫項(xiàng)目時(shí)增加錯(cuò)誤提示頁面操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11vue下載excel的實(shí)現(xiàn)代碼后臺(tái)用post方法
這篇文章主要介紹了vue下載excel的實(shí)現(xiàn)代碼,后臺(tái)用post方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下2019-05-05