日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

html 表单 设计编辑器,可视化页面编辑器的架构设计

發布時間:2024/1/8 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 html 表单 设计编辑器,可视化页面编辑器的架构设计 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

前不久開發歷時半年的可視化搭建項目終于上線[手動撒花 🌹🌹🌹],產品功能上和市面上常見的可視化編輯器其實并沒有很大區別,功能細節處略有不同而已。本文主要是記錄開發過程中遇到的問題以及解決思路。

產品演示圖

需求分析

前期的準備工作還是比較重要的, 尤其是前端項目, 如果整個項目搭建好之后發現某個功能交互邏輯實現起來異常困難, 工作量大概率要成倍增加。哎,就不多說了,懂得都懂。

物料區, 目前支持 5 種組件, 要求可復用, 可支持擴展

可視化拖拽, 物料區拖拽至預覽區, 預覽區頁面內部拖拽排序,

預覽區,流式排版, 點擊可打開組件配置, 跟隨組件位置

實時預覽, 即配置改動需要立馬反映到預覽區

配置區, 大量調用業務相關的彈窗功能

配置區, 需要實現自定義校驗邏輯, 并支持單獨保存

技術棧

系統使用到技術棧如下

react typescript mobx scss antd

數據結構定義

第一步當然是和找后端小哥定義接口頁面存儲的數據結構, 這部分應該沒什么爭議。

interface Page {

id: number // 頁面id

siteName: string // 頁面名稱

description: string // 描述

createdAt: number // 創建時間

operatorName: string // 操作人名稱

modules: [

// 頁面組件配置

{

id: number // 組件id

name: string // 組件名稱

type: number // 組件類型

configuration: JSON.stringify({ // 序列化后的配置, 以內容列表為例

displayRowNum: 8,

subPageConfiguration: {...},

title: "暖心夜話",

contentType: 3,

columnId: 6747,

columnName: "99%的成年人都會患上的情緒綜合癥,你中槍了么",

}

status: number // 上架狀態

}

]

}

復制代碼

具體使用時只需要按順序解析 modules 字段中的 configration 配置展示即可, 編輯過后再按原有的數據結構回傳回后端。注意這里有很多組件配置字段僅存儲了索引關系, 具體展示信息仍需要運行時獲取。

目錄結構

├── @types # 聲明文件

├── store # 數據相關操作, 統一集中在這里

├── constant # 常量相關

├── service # 遠程服務

├── common # 調用的相關組件

├── Editor # 編輯器

│?? ├── BasicModules # 基礎組件區

│?? ├── Empty # 空數據

│?? ├── FormContainer # 配置區

│?? ├── PreviewComponent # 預覽組件

│?? ├── PreviewContainer # 預覽區

│?? ├── UIModules # 擴展組件區

│?? ├── index.tsx

├── Modules # 編輯器組件, 以List組件為例

│?? ├── List

│?? ├── ├── index.tsx # 渲染組件

│?? ├── ├── Form.tsx # 表單組件

│?? └── index.ts

復制代碼

組件

組價設計是這個系統中最重要的部分, 所有的操作都是通過組件解耦串聯到一起, 并且串聯到一起的

數據結構

下面是運行時需要用到的數據結構, 我們將后端給到的 configration 封裝在了 data 中, 并擴展了一些字段, 比如 UI 狀態和校驗屬性等。

interface CmsModule {

id: number // uuid

name: string // 組件名稱

component: any // 展示組件

form: any // 表單組件

type: number // 組件類型

selected: boolean // 是否選中

error: boolean // 是否有錯誤

untouched?: boolean // 是否是初始化狀態, 只有新增的組件會有此狀態

data: { id?: number } & Record // 組件的configration

}

復制代碼

初始化

初始化的操作統一在 store 中編寫, 下面是代碼示例, 解析服務端數據生成本地模型

import { BASIC_MODULE_LIST } from 'Modules'

// modules是后端傳入的數據結構

store.deserialize = (modules) => {

this.value = modules.map((module) => {

// 根據類型篩選出靜態屬性

const staticInfo = BASIC_MODULE_LIST.find(

(item) => item.type === module.type

)

const component: CmsModule = {

...staticInfo,

id: module.id,

type: module.type,

name: module.name,

selected: false,

error: false,

data: {

id: module.id,

...(() => {

try {

return JSON.parse(module.configuration)

} catch (e) {}

})(),

},

}

return component

})

}

復制代碼

組件注冊

上述代碼中的BASIC_MODULE_LIST 相當于一個組件的注冊列表, 通過 BASIC_MODULE_LIST 我們將組件的靜態屬性注入到運行時中, 同理新增一個組件也只需要添加如下條件即可。 當然如果你希望使用遠程組件也都是可以的

import Search from './Search'

import SearchForm from './Search/Form'

export const BASIC_MODULE_LIST = [

{

type: 20,

component: Search,

name: '搜索',

form: SearchForm,

},

]

復制代碼

// 加載遠程組件, 可采用 require.js 加載或者直接加載

init() {

const script = document.createElement('script')

script.src = 'https://demo.umd.component.js'

script.onload = () => {

BASIC_MODULE_LIST.push([

{

type: 31,

component: window.Search,

name: '遠程組件示例',

form: window.Search.Form,

},

])

}

document.body.appendChild(sciprt)

}

復制代碼

最后來看一下我們是如何使用組件的數據的

PreviewComponent.tsx

render() {

const Module = module.component

const Form = module.form

return

className={classnames(

style.preview,

module.selected && style.selected,

module.error && style.error

)}

onClick={this.handleSelect}>

{}

{data.selected &&

{module.name}

}

}

復制代碼

配置

組件的配置

先來聊聊組件的配置, 回顧一下需求, 組件的配置需要支持實時錯誤校驗, 調用業務資源相關的彈窗, 以及單獨保存。當然最重要的需要實現控制反轉, 也就是說配置文件只描述表單規則, 而實際的表單則需要由編輯器創建。本系統用到了 antd 的 Form 組件創建表單, 組件實現下面的接口即可

import { WrappedFormUtils } from 'antd/lib/form/Form'

interface ModuleFormProps {

form: WrappedFormUtils // antd 的 form 的實例, 由外部編輯器傳入

initialValue?: any // 表單默認值, 通常是是從 configration 獲取

layout?: {

// 布局配置

labelCol: { span: number }

wrapperCol: { span: number }

}

}

// Form組件簽名

type FormComponent =

| React.Component

| React.FC

// 示例表單組件

import SourceModal from '../common/SourceModal' // 引入業務相關的資源彈窗

const BannerForm: React.Component = (props) => {

return (

{getFieldDecorator('title', {

initialValue: this.props.initialValue?.title, // 默認值

rules: [{ required: true, message: '請輸入標題' }], // 校驗

})()}

)

}

復制代碼

同理, 上述組件如果需要從遠程調用, 只需要把 SourceModal 像 form 對象一樣將依賴注入, 簡單改造即可, 外部調用也比較簡單

FormContainer.tsx

import { Form as AntForm } from 'antd'

render() {

const { data, Form } = this.props

return (

form={this.props.form}

initialValue={data}

layout={...}

/>

)

}

復制代碼

配置同步

前面提到了我們創建了全局唯一的 store 用于統一處理數據, 原則上我們需要將所有的數據及修改數據的方法都封裝在 store 中, 以防萬一需要實現 undo/redo 棧。下面的代碼演示了如何將 Form 表單字段變更同步到 store 中

FormContainer.tsx

import { Form as AntForm } from 'antd'

import store from 'store'

export default Form.create({

onValuesChange: (props, changedFields, allValues) => {

store.updateComponent(this.props.data.id, allValues)

},

})(FormContainer)

復制代碼

在 react 中將 store 數據反應在 UI 上的方法有很多, 因為項目本身采用了 mobx, 故將PreviewComponent組件用 observer 包裹即可

錯誤處理

前面我們只定義了單個組件的表單錯誤校驗, 所以我們需要監控每一個組件的錯誤狀態, 否則當保存頁面時我們只能獲取當前組件的錯誤狀態。當前利用了Form組件的渲染鉤子函數,在切換選中時同步當前表單狀態

FormContainer.tsx

import store from 'store'

// 切換選中組件時, 上一個組件的 Form 的銷毀鉤子

componentWillUnmount() {

const { form, data } = this.props

form.validateFields((err, values) => {

store.updateComponent(data.id, values)

store.changeComponentError(data.id, Boolean(err))

})

}

復制代碼

同時CmsModule還有一個字段 untouched 用來標識組件是否被選中過(只有新增組件會有這個字段), untouched 為 true 時組件表單數據為空, 也無法保存

其他

拖拽

拖拽采用了知名的第三方庫 react-dnd, 具體使用方法可參考文檔, 這里就不贅述了

體驗上有幾處定制優化, 一是從左側物料區拖拽入預覽區有一個中間預覽狀態, 二是拖拽排序時會自動開啟頁面滾動, 在長頁面排序時會比較友好。

性能

渲染性能

因為采用了 mobx, 所以在列表數據量極大的情況下也可以做到精準更新, 不做優化的情況下也不會出現卡頓

數據獲取

前文提到很多組件只保存了資源索引 id, 只有在組件渲染時才會去請求接口數據, 想象一下如果配置了 100 個組件那么頁面初始化的時候就會同時發送 100 個請求。 類似于圖片懶加載, 組件的數據加載也可以優化

List/index.tsx

if (!window.IntersectionObserver) {

this.fetchData()

} else {

const observer = new IntersectionObserver(([entry]) => {

if (entry.isIntersecting) {

this.fetchData()

observer.unobserve(this.listRef.current)

}

})

observer.observe(this.listRef.current)

}

復制代碼

交互

推薦一個庫react-flip-move, 快速實現動態列表插入、刪除、排序動畫, 零配置接入, 代碼入侵也很小, 推薦使用。

規劃

更多物料組件實現

將組件替換為遠程組件

undo/redo

懶加載做到不依賴組件具體實現

總結

以上是生活随笔為你收集整理的html 表单 设计编辑器,可视化页面编辑器的架构设计的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。