vue使用Split封裝通用拖拽滑動(dòng)分隔面板組件
前言
手動(dòng)封裝一個(gè)類似Iview中的Split組件,可將一片區(qū)域,分割為可以拖拽調(diào)整寬度或高度的兩部分區(qū)域,最終效果如下:


開(kāi)始
基礎(chǔ)布局
在vue工程中創(chuàng)建SplitPane組件,引入頁(yè)面使用。

<template>
<div class="page">
<SplitPane />
</div>
</template>
<script>
import SplitPane from './components/split-pane'
export default {
components: {
SplitPane
},
data() {
return {}
}
}
</script>
<style scoped lang="scss">
.page {
height: 100%;
padding: 10px;
background: #000;
}
</style>
// split-pane.vue
<template>
<div class="split-pane">
split
</div>
</template>
<script>
export default {
data() {
return {}
}
}
</script>
<style scoped lang="scss">
.split-pane {
background: palegreen;
height: 100%;
}
</style>

SplitPane組件由三部分組成:區(qū)域1,區(qū)域2,以及滑動(dòng)器。添加這三個(gè)元素,并分別添加class名,注意.pane為區(qū)域1和區(qū)域2共用。
<template> <div class="split-pane"> <div class="pane pane-one"></div> <div class="pane-trigger"></div> <div class="pane pane-two"></div> </div> </template>
將容器設(shè)置為flex布局,區(qū)域2的flex屬性設(shè)為1,則區(qū)域2會(huì)根據(jù)區(qū)域1的寬度變化自適應(yīng)。
<style scoped lang="scss">
.split-pane {
background: palegreen;
height: 100%;
display: flex;
.pane-one {
width: 50%;
background: palevioletred;
}
.pane-trigger {
width: 10px;
height: 100%;
background: palegoldenrod;
}
.pane-two {
flex: 1;
background: turquoise;
}
}
</style>

可以看到設(shè)置區(qū)域1的寬度變化就是實(shí)現(xiàn)該組件的核心點(diǎn)。
除了橫向還要支持縱向布局,所以給組件添加一個(gè)direction屬性,該屬性由外部傳入,值為row 或 column,與父元素的flex-direction屬性綁定。
<template>
<div class="split-pane" :style="{ flexDirection: direction }">
<div class="pane pane-one"></div>
<div class="pane-trigger"></div>
<div class="pane pane-two"></div>
</div>
</template>
<script>
export default {
props: {
direction: {
type: String,
default: 'row'
}
},
data() {
return {}
}
}
</script>
在橫向布局中,區(qū)域1設(shè)置width:50%,滑動(dòng)器設(shè)置width:10px,而變?yōu)榭v向布局后這兩個(gè)width應(yīng)該變?yōu)閔eight。所以刪除style中這兩個(gè)width設(shè)置,添加一個(gè)lengthType計(jì)算屬性,根據(jù)不同的direction在行內(nèi)樣式中給這兩個(gè)元素分別設(shè)置寬高。
<template>
<div class="split-pane" :style="{ flexDirection: direction }">
<div class="pane pane-one" :style="lengthType + ':50%'"></div>
<div class="pane-trigger" :style="lengthType + ':10px'"></div>
<div class="pane pane-two"></div>
</div>
</template>
computed: {
lengthType() {
return this.direction === 'row' ? 'width' : 'height'
}
}
同時(shí)在橫向布局中,區(qū)域1,區(qū)域2,滑動(dòng)器的height都為100%,在縱向布局下都應(yīng)該改為width: 100%。所以刪除原本的height設(shè)置,將direction綁定為容器的一個(gè)class,根據(jù)該class設(shè)置三個(gè)子元素兩種情況下100%的屬性。
<template>
<div class="split-pane" :class="direction" :style="{ flexDirection: direction }">
<div class="pane pane-one" :style="lengthType + ':50%'"></div>
<div class="pane-trigger" :style="lengthType + ':10px'"></div>
<div class="pane pane-two"></div>
</div>
</template>
<script>
export default {
props: {
direction: {
type: String,
default: 'row'
}
},
data() {
return {}
},
computed: {
lengthType() {
return this.direction === 'row' ? 'width' : 'height'
}
}
}
</script>
<style scoped lang="scss">
.split-pane {
background: palegreen;
height: 100%;
display: flex;
&.row {
.pane {
height: 100%;
}
.pane-trigger {
height: 100%;
}
}
&.column {
.pane {
width: 100%;
}
.pane-trigger {
width: 100%;
}
}
.pane-one {
background: palevioletred;
}
.pane-trigger {
background: palegoldenrod;
}
.pane-two {
flex: 1;
background: turquoise;
}
}
</style>
此時(shí)如果在頁(yè)面中給組件傳入direction="column",可以看到已經(jīng)變?yōu)榭v向
<template> <div class="page"> <SplitPane direction="column" /> </div> </template>
數(shù)據(jù)綁定
當(dāng)前區(qū)域1的寬(高)度和滑動(dòng)器的寬(高)度都是在樣式中寫(xiě)死的,需要變?yōu)樵趈s中綁定才能進(jìn)行操作,首先將能用于計(jì)算的數(shù)字放在data中
data() {
return {
paneLengthPercent: 50, // 區(qū)域1寬度 (%)
triggerLength: 10 // 滑動(dòng)器寬度 (px)
}
}
然后通過(guò)computed返回兩個(gè)樣式中需要的字符串,同時(shí)為了保證滑動(dòng)器在區(qū)域1和區(qū)域2的正中間,區(qū)域1的寬度應(yīng)該減去滑動(dòng)器寬度的一半。
computed: {
lengthType() {
return this.direction === 'row' ? 'width' : 'height'
},
paneLengthValue() {
return `calc(${this.paneLengthPercent}% - ${this.triggerLength / 2 + 'px'})`
},
triggerLengthValue() {
return this.triggerLength + 'px'
}
}
最后綁定在模板中
<template>
<div class="split-pane" :class="direction" :style="{ flexDirection: direction }">
<div class="pane pane-one" :style="lengthType + ':' + paneLengthValue"></div>
<div class="pane-trigger" :style="lengthType + ':' + triggerLengthValue"></div>
<div class="pane pane-two"></div>
</div>
</template>

事件綁定
想象一下拖拽滑動(dòng)器的過(guò)程,第一步是在滑動(dòng)器上按下鼠標(biāo),給滑動(dòng)器添加mousedown事件
<div class="pane-trigger" :style="lengthType + ':' + triggerLengthValue" @mousedown="handleMouseDown"></div>
按下鼠標(biāo)后開(kāi)始滑動(dòng),應(yīng)該監(jiān)聽(tīng)mousemove事件,但注意不是在滑動(dòng)器上,而是在整個(gè)文檔上監(jiān)聽(tīng),因?yàn)槭髽?biāo)有可能滑動(dòng)到頁(yè)面任何位置。當(dāng)用戶松開(kāi)鼠標(biāo)時(shí),應(yīng)該取消對(duì)整個(gè)文檔mousemove的監(jiān)聽(tīng),所以在鼠標(biāo)按下的那一刻,應(yīng)該對(duì)document添加兩個(gè)事件:鼠標(biāo)移動(dòng)和鼠標(biāo)松開(kāi)
methods: {
// 按下滑動(dòng)器
handleMouseDown(e) {
document.addEventListener('mousemove', this.handleMouseMove)
document.addEventListener('mouseup', this.handleMouseUp)
},
// 按下滑動(dòng)器后移動(dòng)鼠標(biāo)
handleMouseMove(e) {
console.log('拖動(dòng)中')
},
// 松開(kāi)滑動(dòng)器
handleMouseUp() {
document.removeEventListener('mousemove', this.handleMouseMove)
}
}

我們實(shí)際要控制的是區(qū)域1的寬度,讓區(qū)域1的寬度等于當(dāng)前鼠標(biāo)距容器左邊的距離,也就是如果鼠標(biāo)移動(dòng)到下圖的圓圈位置,讓區(qū)域1的寬度等于中間的長(zhǎng)度:

這個(gè)長(zhǎng)度可以根據(jù)當(dāng)前鼠標(biāo)距頁(yè)面最左邊的距離減去容器距頁(yè)面最左邊的距離算出,也就是綠色長(zhǎng)度等于紅色減藍(lán)色:

給容器添加ref為了獲取容器的dom信息
...
<div ref="splitPane" class="split-pane" :class="direction" :style="{ flexDirection: direction }">
...
如果打印ref的getBoundingClientRect()可以看到如下信息:
console.log(this.$refs.splitPane.getBoundingClientRect())

其中l(wèi)eft代表容器距離頁(yè)面左側(cè)的距離,width代表容器的寬度。
通過(guò)鼠標(biāo)事件對(duì)象event的pageX可以獲得當(dāng)前鼠標(biāo)距頁(yè)面左側(cè)的距離,則我們要求的鼠標(biāo)距容器左側(cè)距離就可以算出來(lái)了。
最后用這個(gè)距離除以容器寬度乘上100,就得到了這個(gè)距離的百分比數(shù)值,賦值給paneLengthPercent。
// 按下滑動(dòng)器后移動(dòng)鼠標(biāo)
handleMouseMove(e) {
const clientRect = this.$refs.splitPane.getBoundingClientRect()
const offset = e.pageX - clientRect.left
const paneLengthPercent = (offset / clientRect.width) * 100
this.paneLengthPercent = paneLengthPercent
},

兼容縱向布局。
// 按下滑動(dòng)器后移動(dòng)鼠標(biāo)
handleMouseMove(e) {
const clientRect = this.$refs.splitPane.getBoundingClientRect()
let paneLengthPercent = 0
if (this.direction === 'row') {
const offset = e.pageX - clientRect.left
paneLengthPercent = (offset / clientRect.width) * 100
} else {
const offset = e.pageY - clientRect.top
paneLengthPercent = (offset / clientRect.height) * 100
}
this.paneLengthPercent = paneLengthPercent
},
優(yōu)化
此時(shí)看上去需求已經(jīng)完成,但作為一個(gè)通用組件還有幾個(gè)要優(yōu)化的地方。
優(yōu)化一 抖動(dòng)問(wèn)題
把滑動(dòng)器寬度設(shè)置大一些后可以發(fā)現(xiàn)一個(gè)抖動(dòng)問(wèn)題如下:

在滑動(dòng)器兩側(cè)按下后輕輕移動(dòng)就會(huì)出現(xiàn)大幅偏移,因?yàn)楝F(xiàn)在的計(jì)算邏輯始終認(rèn)為鼠標(biāo)在滑動(dòng)器的正中間,沒(méi)有把滑動(dòng)器寬度考慮進(jìn)去。
在dota中定義一個(gè)當(dāng)前鼠標(biāo)距滑動(dòng)器左(頂)側(cè)偏移量
data() {
return {
paneLengthPercent: 50, // 區(qū)域1寬度 (%)
triggerLength: 100, // 滑動(dòng)器寬度 (px)
triggerLeftOffset: 0 // 鼠標(biāo)距滑動(dòng)器左(頂)側(cè)偏移量
}
}
這個(gè)值等于鼠標(biāo)距頁(yè)面左側(cè)距離減去滑動(dòng)器距頁(yè)面左側(cè)距離(通過(guò)e.srcElement.getBoundingClientRect()),在每次滑動(dòng)器被按下時(shí)進(jìn)行賦值,也要區(qū)分橫向/縱向布局:紅 - 藍(lán) = 綠

// 按下滑動(dòng)器
handleMouseDown(e) {
document.addEventListener('mousemove', this.handleMouseMove)
document.addEventListener('mouseup', this.handleMouseUp)
if (this.direction === 'row') {
this.triggerLeftOffset = e.pageX - e.srcElement.getBoundingClientRect().left
} else {
this.triggerLeftOffset = e.pageY - e.srcElement.getBoundingClientRect().top
}
},
有了這個(gè)triggerLeftOffset,設(shè)置區(qū)域1的寬度時(shí)就應(yīng)該變成:鼠標(biāo)距容器左側(cè)距離 減去 鼠標(biāo)距滑動(dòng)器左側(cè)的距離(triggerLeftOffset) 再加上滑動(dòng)器寬度的一半。
這樣就相當(dāng)于把鼠標(biāo)又定位回了滑動(dòng)器正中間。
// 按下滑動(dòng)器后移動(dòng)鼠標(biāo)
handleMouseMove(e) {
const clientRect = this.$refs.splitPane.getBoundingClientRect()
let paneLengthPercent = 0
if (this.direction === 'row') {
const offset = e.pageX - clientRect.left - this.triggerLeftOffset + this.triggerLength / 2
paneLengthPercent = (offset / clientRect.width) * 100
} else {
const offset = e.pageY - clientRect.top - this.triggerLeftOffset + this.triggerLength / 2
paneLengthPercent = (offset / clientRect.height) * 100
}
this.paneLengthPercent = paneLengthPercent
},
此時(shí)不再有抖動(dòng)問(wèn)題

優(yōu)化二 鼠標(biāo)樣式
鼠標(biāo)在滑動(dòng)器上經(jīng)過(guò)時(shí)應(yīng)該改變樣式告訴用戶可以拖動(dòng),分別在橫向布局與縱向布局的滑動(dòng)器css中添加鼠標(biāo)樣式變化。
<style scoped lang="scss">
.split-pane {
background: palegreen;
height: 100%;
display: flex;
&.row {
.pane {
height: 100%;
}
.pane-trigger {
height: 100%;
cursor: col-resize; // 這里
}
}
&.column {
.pane {
width: 100%;
}
.pane-trigger {
width: 100%;
cursor: row-resize; // 這里
}
}
.pane-one {
background: palevioletred;
}
.pane-trigger {
background: palegoldenrod;
}
.pane-two {
flex: 1;
background: turquoise;
}
}
</style>

優(yōu)化三 滑動(dòng)限制
作為一個(gè)通用組件,應(yīng)該向外部提供設(shè)置滑動(dòng)最小與最大距離的限制功能,接收min與max兩個(gè)props。
props: {
direction: {
type: String,
default: 'row'
},
min: {
type: Number,
default: 10
},
max: {
type: Number,
default: 90
}
},
在handleMouseMove加入判斷:
// 按下滑動(dòng)器后移動(dòng)鼠標(biāo)
handleMouseMove(e) {
const clientRect = this.$refs.splitPane.getBoundingClientRect()
let paneLengthPercent = 0
if (this.direction === 'row') {
const offset = e.pageX - clientRect.left - this.triggerLeftOffset + this.triggerLength / 2
paneLengthPercent = (offset / clientRect.width) * 100
} else {
const offset = e.pageY - clientRect.top - this.triggerLeftOffset + this.triggerLength / 2
paneLengthPercent = (offset / clientRect.height) * 100
}
if (paneLengthPercent < this.min) {
paneLengthPercent = this.min
}
if (paneLengthPercent > this.max) {
paneLengthPercent = this.max
}
this.paneLengthPercent = paneLengthPercent
}

優(yōu)化四 面板默認(rèn)寬度和滑動(dòng)器寬度
還是作為一個(gè)通用組件,面板初始化比例與滑動(dòng)器寬度應(yīng)該也由外部使用者決定。
將data中的paneLengthPercent 和 triggerLength轉(zhuǎn)移到props中,從外部接收。
props: {
direction: {
type: String,
default: 'row'
},
min: {
type: Number,
default: 10
},
max: {
type: Number,
default: 90
},
paneLengthPercent: {
type: Number,
default: 50
},
triggerLength: {
type: Number,
default: 10
}
},
data() {
return {
triggerLeftOffset: 0 // 鼠標(biāo)距滑動(dòng)器左(頂)側(cè)偏移量
}
},
在頁(yè)面中則需傳入paneLengthPercent,注意paneLengthPercent必須是一個(gè)定義在data中的數(shù)據(jù),并且要加上.sync修飾符,因?yàn)檫@個(gè)值要?jiǎng)討B(tài)修改。
// page.vue
<template>
<div class="page">
<SplitPane direction="row" :paneLengthPercent.sync="paneLengthPercent" />
</div>
</template>
...
data() {
return {
paneLengthPercent: 30
}
}
...
然后在組件中handleMouseMove中通過(guò)this.$emit觸發(fā)事件的方式修改paneLengthPercent值。
// 按下滑動(dòng)器后移動(dòng)鼠標(biāo)
handleMouseMove(e) {
const clientRect = this.$refs.splitPane.getBoundingClientRect()
let paneLengthPercent = 0
if (this.direction === 'row') {
const offset = e.pageX - clientRect.left - this.triggerLeftOffset + this.triggerLength / 2
paneLengthPercent = (offset / clientRect.width) * 100
} else {
const offset = e.pageY - clientRect.top - this.triggerLeftOffset + this.triggerLength / 2
paneLengthPercent = (offset / clientRect.height) * 100
}
if (paneLengthPercent < this.min) {
paneLengthPercent = this.min
}
if (paneLengthPercent > this.max) {
paneLengthPercent = this.max
}
this.$emit('update:paneLengthPercent', paneLengthPercent) // 這里
},
此時(shí)組件的要素信息都可以通過(guò)外部的props控制了。
優(yōu)化五 插槽
作為一個(gè)容器組件不能添加內(nèi)容不是等于白費(fèi),分別給兩個(gè)區(qū)域添加兩個(gè)具名插槽。
<template>
<div ref="splitPane" class="split-pane" :class="direction" :style="{ flexDirection: direction }">
<div class="pane pane-one" :style="lengthType + ':' + paneLengthValue">
<slot name="one"></slot>
</div>
<div
class="pane-trigger"
:style="lengthType + ':' + triggerLengthValue"
@mousedown="handleMouseDown">
</div>
<div class="pane pane-two">
<slot name="two"></slot>
</div>
</div>
</template>
優(yōu)化六 禁止選中
在拖動(dòng)過(guò)程中,如果區(qū)域中有文字內(nèi)容可能會(huì)出現(xiàn)選中文字的情況,給滑動(dòng)器添加禁止選中效果。
...
.pane-trigger {
user-select: none;
background: palegoldenrod;
}
...
結(jié)束
組件完整代碼
保留各背景色僅為了文章展示需要,實(shí)際使用中刪除
<template>
<div ref="splitPane" class="split-pane" :class="direction" :style="{ flexDirection: direction }">
<div class="pane pane-one" :style="lengthType + ':' + paneLengthValue">
<slot name="one"></slot>
</div>
<div
class="pane-trigger"
:style="lengthType + ':' + triggerLengthValue"
@mousedown="handleMouseDown"
></div>
<div class="pane pane-two">
<slot name="two"></slot>
</div>
</div>
</template>
<script>
export default {
props: {
direction: {
type: String,
default: 'row'
},
min: {
type: Number,
default: 10
},
max: {
type: Number,
default: 90
},
paneLengthPercent: {
type: Number,
default: 50
},
triggerLength: {
type: Number,
default: 10
}
},
data() {
return {
triggerLeftOffset: 0 // 鼠標(biāo)距滑動(dòng)器左(頂)側(cè)偏移量
}
},
computed: {
lengthType() {
return this.direction === 'row' ? 'width' : 'height'
},
paneLengthValue() {
return `calc(${this.paneLengthPercent}% - ${this.triggerLength / 2 + 'px'})`
},
triggerLengthValue() {
return this.triggerLength + 'px'
}
},
methods: {
// 按下滑動(dòng)器
handleMouseDown(e) {
document.addEventListener('mousemove', this.handleMouseMove)
document.addEventListener('mouseup', this.handleMouseUp)
if (this.direction === 'row') {
this.triggerLeftOffset = e.pageX - e.srcElement.getBoundingClientRect().left
} else {
this.triggerLeftOffset = e.pageY - e.srcElement.getBoundingClientRect().top
}
},
// 按下滑動(dòng)器后移動(dòng)鼠標(biāo)
handleMouseMove(e) {
const clientRect = this.$refs.splitPane.getBoundingClientRect()
let paneLengthPercent = 0
if (this.direction === 'row') {
const offset = e.pageX - clientRect.left - this.triggerLeftOffset + this.triggerLength / 2
paneLengthPercent = (offset / clientRect.width) * 100
} else {
const offset = e.pageY - clientRect.top - this.triggerLeftOffset + this.triggerLength / 2
paneLengthPercent = (offset / clientRect.height) * 100
}
if (paneLengthPercent < this.min) {
paneLengthPercent = this.min
}
if (paneLengthPercent > this.max) {
paneLengthPercent = this.max
}
this.$emit('update:paneLengthPercent', paneLengthPercent)
},
// 松開(kāi)滑動(dòng)器
handleMouseUp() {
document.removeEventListener('mousemove', this.handleMouseMove)
}
}
}
</script>
<style scoped lang="scss">
.split-pane {
background: palegreen;
height: 100%;
display: flex;
&.row {
.pane {
height: 100%;
}
.pane-trigger {
height: 100%;
cursor: col-resize;
}
}
&.column {
.pane {
width: 100%;
}
.pane-trigger {
width: 100%;
cursor: row-resize;
}
}
.pane-one {
background: palevioletred;
}
.pane-trigger {
user-select: none;
background: palegoldenrod;
}
.pane-two {
flex: 1;
background: turquoise;
}
}
</style>
組件使用示例
保留各背景色僅為了文章展示需要,實(shí)際使用中刪除
<template>
<div class="page">
<SplitPane
direction="column"
:min="20"
:max="80"
:triggerLength="20"
:paneLengthPercent.sync="paneLengthPercent"
>
<template v-slot:one>
<div>
區(qū)域一
</div>
</template>
<template v-slot:two>
<div>
區(qū)域二
</div>
</template>
</SplitPane>
</div>
</template>
<script>
import SplitPane from './components/split-pane'
export default {
components: {
SplitPane
},
data() {
return {
paneLengthPercent: 30
}
}
}
</script>
<style scoped lang="scss">
.page {
height: 100%;
padding: 10px;
background: #000;
}
</style>

到此這篇關(guān)于vue使用Split封裝通用拖拽滑動(dòng)分隔面板組件 的文章就介紹到這了,更多相關(guān)vue 拖拽滑動(dòng)分隔面板 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Vue 可拖拽組件Vue Smooth DnD的使用詳解
- vue拖拽組件 vuedraggable API options實(shí)現(xiàn)盒子之間相互拖拽排序
- Vue拖拽組件列表實(shí)現(xiàn)動(dòng)態(tài)頁(yè)面配置功能
- vue拖拽組件使用方法詳解
- Vue拖拽組件開(kāi)發(fā)實(shí)例詳解
- vue 實(shí)現(xiàn)拖拽動(dòng)態(tài)生成組件的需求
- vue開(kāi)發(fā)拖拽進(jìn)度條滑動(dòng)組件
- vue draggable resizable 實(shí)現(xiàn)可拖拽縮放的組件功能
- 利用Vue-draggable組件實(shí)現(xiàn)Vue項(xiàng)目中表格內(nèi)容的拖拽排序
- Vue組件Draggable實(shí)現(xiàn)拖拽功能
- Vue實(shí)現(xiàn)可拖拽組件的方法
相關(guān)文章
vue自定義權(quán)限標(biāo)簽詳細(xì)代碼示例
這篇文章主要給大家介紹了關(guān)于vue自定義權(quán)限標(biāo)簽的相關(guān)資料,在Vue中你可以通過(guò)創(chuàng)建自定義組件來(lái)實(shí)現(xiàn)自定義標(biāo)簽組件,文中給出了詳細(xì)的代碼示例,需要的朋友可以參考下2023-09-09
element-ui中this.$confirm提示文字中部分有顏色自定義方法
this.$confirm是一個(gè)Vue.js中的彈窗組件,其樣式可以通過(guò)CSS進(jìn)行自定義,下面這篇文章主要給大家介紹了關(guān)于element-ui中this.$confirm提示文字中部分有顏色的自定義方法,需要的朋友可以參考下2024-02-02
vue element-ui中table合計(jì)指定列求和實(shí)例
這篇文章主要介紹了vue element-ui中table合計(jì)指定列求和實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11
Vue3中Element Plus Table(表格)點(diǎn)擊獲取對(duì)應(yīng)id方式
這篇文章主要介紹了Vue3中Element Plus Table(表格)點(diǎn)擊獲取對(duì)應(yīng)id方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10
Vue.js實(shí)現(xiàn)微信過(guò)渡動(dòng)畫(huà)左右切換效果
這篇文章主要給大家介紹了利用Vue.js仿微信過(guò)渡動(dòng)畫(huà)左右切換效果的相關(guān)資料,需要用到的技術(shù)棧是Vue+Vuex。文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家具一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-06-06

