OpenDataV計劃採用子庫的方式新增子元件,即每一個元件都當做一個子庫,子庫有自己的依賴,而專案本身的依賴只針對框架,因此每一個元件我們都當做一個子庫來開發。下面我帶著大家一步步詳細的開發一個數位展示元件。
所有的可拖拽元件都存放在src/resource/components
目錄下
cd src/resource/components
預設元件目錄是以元件的名稱命名,當然也可以根據自己的要求命名,元件可以放在components
目錄下,也可以放在其子目錄的目錄下。
mkdir DigitalText
每個元件必須的檔案有vue檔案xxx.vue
、組態檔config.ts
、匯出檔案index.ts
,每個檔案有自己的用處,vue檔案不用說了是元件渲染的主體,匯出檔名稱固定為index.ts
,主要是匯出元件的資訊給外部參照,組態檔主要是在編輯頁面右側展示的設定項,這個我們後面再詳述。所以這裡我們需要建立三個檔案:DigitalText.vue、config.ts、index.ts
以上我們就建立好了元件所需的檔案,下面就需要做元件的初始化了
因為我們的元件都是以子庫的方式引入,所以需要進行包的初始化,執行以下命令
cd src/resource/components/Text/DigitalText
npm init
這裡使用npm
初始化包會讓我們選擇填寫部分資料。
下面我們先初始化一下元件檔案DigitalText.vue
,先初始化最簡單的元件資料
<template>
<div>數位展示</div>
</template>
<script lang="ts" setup></script>
<style lang="less" scoped></style>
然後我們要初始化元件的組態檔config.ts
import { ComponentGroup, FormType } from '@/enum'
import type { PropsType } from '@/types/component'
import { BaseComponent } from '@/resource/models'
export const componentName = 'Digital'
class DigitalComponent extends BaseComponent {
constructor(id?: string, name?: string, icon?: string) {
super({
component: componentName,
group: ComponentGroup.TEXT,
name: name ? name : '數位文字',
id,
width: 100,
height: 30,
icon
})
}
}
export default DigitalComponent
這裡要說明的點:componentName
是元件在專案中的註冊名稱,因此必須保證唯一,group
是給元件分組,這裡的分組主要是展示在元件拖拽頁面,其型別的ComponentGroup
是固定好的,可以自己增加,展示位置如下:
name
是元件在拖拽頁面上顯示的名稱,width
和height
是元件拖拽到畫布上顯示的初始大小
設定完元件後資料後,就可以設定匯出檔案index.ts
了,主要是匯出元件名、元件物件和設定項。
import DigitalTextComponent, { componentName } from './config'
export default {
componentName,
component: () => import('./DigitalText.vue'),
config: DigitalTextComponent
}
初始化到這一步,我們的元件已經可以在編輯頁面正常使用了,看一下效果:
在右邊的編輯頁面我們看到有樣式
和屬性
,所有的元件都包含基礎樣式位置大小
,包括元件的上下邊距和寬高,屬性包含公共屬性,其中元件
和元件ID
無法修改,主要是展示來看的,名稱
可以修改,名稱屬性主要是展示在圖層上,修改名稱後圖層上會響應的顯示修改後的名稱。
這是最基礎的元件,只能展示固定資料,不能進行任何設定,下面我們要做元件的設定項。
作為文字顯示元件,最基礎的字型相關屬性設定應該要有,比如字型、字型顏色、字型大小、字型寬度
,設定項依然是在組態檔中新增,繼承自基礎元件類的私有屬性_style
class DigitalTextComponent extends BaseComponent {
constructor(id?: string, name?: string, icon?: string) {......}
_style: PropsType[] = [
{
label: '字型設定',
prop: 'font',
children: [
{
prop: 'color',
label: '顏色',
type: FormType.COLOR,
componentOptions: {
defaultValue: 'skyblue'
}
},
{
prop: 'fontSize',
label: '字型大小',
type: FormType.NUMBER,
componentOptions: {
defaultValue: 20
}
},
{
prop: 'fontWeight',
label: '字型寬度',
type: FormType.FONT_WEIGHT,
componentOptions: {
defaultValue: 200
}
},
{
prop: 'fontFamily',
label: '字型',
type: FormType.FONT_STYLE,
componentOptions: {
defaultValue: 'Arial'
}
}
]
}
]
}
樣式設定的格式已經定義好了,其中需要注意的是所有children
下的子項中prop
必須是html
元素的css
屬性,具體的css
屬性名稱並不是我們在style
檔案中填寫的,而是在js
中對應的名稱,這個可以在網上搜尋:css3 中文手冊
,類似如下:
我們再詳述一下各設定項的意義:
css
屬性值FormType
中src/types/component.d.ts
中檢視定義,所有設定都有defaultValue
設定,作為屬性初始化時的預設值設定完樣式後,我們在編輯頁面看一下效果:
搞清楚了樣式的設定,下面我們再說說屬性的設定,屬性設定與樣式設定格式一致,有一些小細節需要注意。
屬性設定是繼承私有變數_prop
,設定格式與樣式相同,我們這裡設定一個範例屬性:
class DigitalTextComponent extends BaseComponent {
constructor(id?: string, name?: string, icon?: string) {......}
_prop: PropsType[] = [
{
label: '資料設定',
prop: 'dataconfig',
children: [
{
prop: 'data',
label: '資料',
type: FormType.NUMBER,
componentOptions: {
defaultValue: 100000,
max: 99999999,
min: 0
}
}
]
}
]
}
格式這裡就不解釋了,這裡我們用到了數值型別,因此可以設定最大最小值。
接下來就是要在vue
檔案中使用屬性設定了,屬性不像樣式,樣式是html
元素本身就支援的,因此只要我們設定好,就可以生效了,但是屬性是元件專有的,什麼屬性要產生什麼效果全憑我們自己書寫邏輯,因此設定好屬性我們只會在編輯頁面看到屬性展示和設定,但是實際設定後是沒有任何效果的,具體效果我們在vue
中實現。
首先我們要新增一個型別定義檔案,因為ts
最基礎的優勢就是型別提示,而我們封裝的元件基礎類別是通用的,因此需要在每個元件中使用自己的屬性型別定義,定義如下:
// DigitalText/type.ts
export interface DigitalType {
dataconfig: {
data: number
}
}
為了準確的進行提示,型別定義必須和屬性設定一直,具體來講就是children
下面的prop
作為屬性值,children
外面的prop
作為屬性鍵,可以對比一下type.ts
中的設定和_prop
的設定。
元件的設定資訊是通過外部傳入的,所以所有元件都必須接收外部資料,我們已經定義好了固定的格式
const props = defineProps<{
component: DigitalTextComponent
}>()
元件相關的所有資訊都將通過component
傳入進來,為了監聽屬性變化和型別提示,我們封裝了一個hook,減少每個元件中通用的處理,useProp
的使用如下:
const propChange = (prop: string, key: string, value: number) => {
console.log(prop, key, value)
}
const { propValue } = useProp<DigitalType>(props.component, propChange)
useProp
接收三個引數,一個是component
,主要是為了新增型別提示,所以這裡也傳入了一個泛型定義,就是我們在type.ts
中定義的型別,另外兩個引數是屬性變化回撥函數和樣式變化回撥函數。一般情況下我們只需要處理屬性變化回撥,樣式變化是自動生效的,所以基本上不用處理,如果有特殊需求才需要。屬性變化回撥函數中有三個引數,prop
對應的是屬性設定中外層的prop
值,key
對應的是屬性設定中children
中的prop
值,而value
就是屬性變化的值。
最終我們的屬性處理結果如下:
<template>
<div>{{ data }}</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import DigitalTextComponent from './config'
import { useProp } from '@/resource/hooks'
import { DigitalType } from './type'
const props = defineProps<{
component: DigitalTextComponent
}>()
const propChange = (prop: string, key: string, value: number) => {
if (prop === 'dataconfig' && key === 'data') {
data.value = value
}
}
const { propValue } = useProp<DigitalType>(props.component, propChange)
const data = ref<number>(propValue.dataconfig.data)
</script>
<style lang="less" scoped></style>
看一下頁面上的效果:
上面我們用了屬性回撥去處理值變化響應,實際上還有其他的方式可以處理,我們要明白屬性回撥的根本需求是什麼?主要就是為了編輯了對應的屬性後,我們在元件內能監測到變化反饋到顯示上。相同這一點,可用的方法就多了。
props
傳遞的屬性值在template
中渲染資料<template>
<div>{{ propValue.dataconfig.data }}</div>
</template>
<script lang="ts" setup>
import DigitalTextComponent from './config'
import { useProp } from '@/resource/hooks'
import { DigitalType } from './type'
const props = defineProps<{
component: DigitalTextComponent
}>()
const { propValue } = useProp<DigitalType>(props.component)
因為vue
響應式的原因,props
中的資料是可以響應變化的,那麼我們直接在template
中使用即可,不需要做任何監測。
computed
或者watch
監聽屬性變化這裡和上面是一樣的道理,vue
會自動幫我們處理響應式資料,只要用vue
的計算屬性或者watch
也可以監聽到屬性變化。
<template>
<div>{{ data }}</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import DigitalTextComponent from './config'
import { useProp } from '@/resource/hooks'
import { DigitalType } from './type'
const props = defineProps<{
component: DigitalTextComponent
}>()
const { propValue } = useProp<DigitalType>(props.component)
const data = computed<number>(() => {
return propValue.dataconfig.data
})
</script>
<style lang="less" scoped></style>
<template>
<div>{{ data }}</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import DigitalTextComponent from './config'
import { useProp } from '@/resource/hooks'
import { DigitalType } from './type'
const props = defineProps<{
component: DigitalTextComponent
}>()
const propChange = () => {
data.value = propValue.dataconfig.data
}
const { propValue } = useProp<DigitalType>(props.component, propChange)
const data = ref<number>(propValue.dataconfig.data)
</script>
<style lang="less" scoped></style>
屬性變化回撥接收任何引數,我們可以選擇接收引數,也可以不接收引數,在一些設定項比較多的元件中,我們不想在屬性回撥中去一個一個判斷變化的屬性,那麼就可以使用這種方式,在這種方式中我們只是把屬性回撥作為一個通知,即通知我們屬性發生變化了,而我們不關心哪一個屬性發生了變化,把所有的屬性都修改一遍即可,雖然聽起來比較麻煩,但是在一些複雜元件中確實很有作用。在這裡我們要明白,只要屬性發生了變化,那麼prop
中的資料也必定發生了變化,所以我們隨時取prop
中的資料它都是最新的。
到這裡,一個元件的整個新增過程就講完了,根據目前的開發進度來看,基本上所有的部分都講到了,如果有人在使用過程中發現了什麼問題或者有哪些地方不夠清楚的,可以在專案的issue中提,也可以通過其他方式反饋。
新增微信公眾號瞭解更多資訊: