1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        后臺管理系統(tǒng)可拖拽式組件的設計思路

        共 26470字,需瀏覽 53分鐘

         ·

        2022-07-23 21:12

        在后臺管理系統(tǒng)的項目中,因為是數(shù)據(jù)管理,大部分都是 CURD 的頁面。比如:

        image.png

        對于這類的頁面,我們完全可以設計一個組件,使用拖拽的方式,將組件一個個拖到指定區(qū)域,進行結構組裝,然后再寫一個對組裝數(shù)據(jù)的渲染組件,渲染成頁面即可。如下:

        image.png

        需要處理的問題

        • 數(shù)據(jù)結構的組裝
        • 組件列表的選擇
        • 組件的拖拽處理
        • 組件的配置信息配置
        • 請求的處理
        • 下拉選項數(shù)據(jù)的處理
        • table 組件的設計
        • 按鈕與彈窗的處理
        • 彈窗與表格數(shù)據(jù)的聯(lián)動
        • 自定義插槽

        下面的內(nèi)容只是做具體的設計思路分析,不做詳細的代碼展示,內(nèi)容太多了,沒法一一展示

        數(shù)據(jù)結構的組裝

        由于這種都是組件的組裝,所以我們要先定義具體組件的數(shù)據(jù)結構:

        class Component {
            typestring = 'componentName'
            properties: Record<stringany> = {}
            children: Record<stringany>[] = []
        }
        復制代碼
        • type:組件的名字
        • properties:組件的屬性
        • children:當前組件下的子組件,用于嵌套

        因為這種設計,整個頁面就是一個大組件,按照同樣的結構,所以我們最終的數(shù)據(jù)結構應該是這樣的:

        const pageConfig = {
            type'page',
            properties: {},
            search: {
                type'search',
                id: 'xxx',
                properties: {},
                children: [
                    {
                        type'input',
                        id: 'xxx',
                        properties: {}
                    }
                    // ...
                ]
            },
            table: {
                type'table'
                id: 'xxx',
                properties: {},
                children: [
                    {
                        type'column',
                        id: 'xxx',
                        properties: {}
                    },
                    {
                        type'column',
                        id: 'xxx',
                        properties: {},
                        children: [
                            {
                                type'button',
                                id: 'xxx',
                                properties: {}
                            }
                        ]
                    }
                    // ...
                ],
                buttons: [
                    {
                        type'button',
                        id: 'xxx',
                        properties: {}
                    }
                    // ...
                ]
            }
        }
        復制代碼

        上面的結構,對于第一層來說,因為場景的限制,search 組件和 table 組件是固定位置的,所以這里就直接定死了,如果想直接拖拽定位,直接在數(shù)據(jù)頂層加 children 字段即可,然后可以進行拖拽排序位置。對于內(nèi)部兄弟組件的排序功能,因為 vue 框架已經(jīng)提供了 transition-group 組件,直接使用即可。而 table 下面的 buttons 數(shù)組,是由于在一般的 table 組件的上方會有一排按鈕,用于新增,或者批量操作等。

        組件列表的選擇

        對于數(shù)據(jù)管理頁面,能夠用上的組件無外乎就是 input,select,date,checkbox,button 等常用的 form 組件,還有我們要在配置頁面重新封裝 search,table 等業(yè)務組件,梳理出所有要用的組件后,我們需要用一個文件來匯總所有組件的屬性:

        // 頁面結構
        class CommonType {
          title?: string
          code?: string
          filter?: string
          readVariable?: string
          writeVariable?: string
        }
        export class Common {
          hide = true
          type = 'common'
          properties = new CommonType
          dialogTemplate = []
        }

        // search
        class SearchType extends FormType {
          gutter = 20
          searchBtnText = '搜索'
          searchIcon = 'Search'
          resetBtnText = '重置'
          resetIcon = 'Refresh'
          round?: boolean
        }
        export class Search {
          bui = true
          type = 'search'
          properties = new SearchType
          children = []
        }
        // ...
        復制代碼

        組件的拖拽處理

        對于組件的拖拽處理,我們可以直接使用 H5 的 draggable[1],首先是左側的組件列表的每一個組件都是可以拖拽的,在拖動到中間展示區(qū)域的時候,我們需要獲取 drop 事件的目標元素,然后結合 dragstart 事件的信息,確定當前拖動組件的父級是誰,然后進行數(shù)據(jù)組裝,這里所有的數(shù)據(jù)組裝都由 drop 事件來完成,數(shù)據(jù)組裝完成之后,更新中間的渲染區(qū)域。

        組件的配置信息配置

        每一個組件的配置信息其實都是不一樣的,這些具體的屬性,除了像 prop,id 這樣通用的信息,都需要根據(jù)自己的情況來定,但是這些屬性是與組件的 properties 一一對應。由于組件的每一個屬性,有不同的類型,有的是輸入框,有的是下拉選擇,還有的是開關等,所以我們要對每一個屬性進行詳細的描述:

        const componentName = [
            {
                label: '占位提示文本',
                value: 'placeholder',
                type'input'
            },
            {
                label: '可清除',
                value: 'clearable',
                type'switch'
            },
            {
                label: '標簽位置',
                value: 'labelPosition',
                type'select',
                children: [
                    {
                        label: 'left',
                        value: 'left'
                    },
                    {
                        label: 'right',
                        value: 'right'
                    },
                    {
                        label: 'top',
                        value: 'top'
                    }
               ]
            }
            // ...
        ]
        復制代碼

        定義完基本信息之后,我們還需要處理兩種特殊情況:

        • 當組件中的一個屬性其實是依賴另一些屬性的具體值的處理
        • 組件處于不同的父級組件下,應用不同的屬性

        第一種情況,當一個屬性依賴另一個或者幾個的屬性的時候,我們可以設置一個規(guī)則數(shù)組,比如:

        [
            {
                label: '屬性1',
                value: 'type',
                type'select',
                children: [
                    {
                        label: 'url',
                        value: 'url'
                    },
                    {
                        label: 'other',
                        value: 'other'
                    }
               ]
            },
            {
                rules: [
                    {
                        originValue: 'type',
                        destValue: ['url']
                    }
                ],
                label: '屬性2',
                value: 'prop2',
                type'input'
            }
        ]
        復制代碼

        以上的規(guī)則,我們可以去解析屬性中的 rules 字段,當 type 的值為 url 時,我們就顯示屬性2,否則就不顯示。

        還有一種是同一個組件在不同的父級顯示不同的可操作屬性,比如,input 組件在 search 組件下不需要校驗字段,而在 form 表單是需要的,所以我們可以增加一個字段 use:

        const formItem = [
            {
                use: ['search''dialog'],
                label: '標簽',
                value: 'label',
                type'input'
            }
            // ...
        ]
        復制代碼

        以上信息表示,formItem 組件的標簽屬性是在 search 和 dialog 組件中使用的,其它的父級組件下不會顯示。

        當所有組件的配置信息配置完成后,我們在聚焦預覽區(qū)域的具體組件時,用程序篩選出可操作屬性即可。

        // 處理右側可操作屬性
        const getShowProperties = computed(() => {
          const properties = propertyTemplate[activeComponents.value.type]
          if (!properties) {
            return []
          }
          let props: Record<stringany> = []
          properties.forEach((item: Record<stringany>) => {
            if (
              (!item.use || item.use.includes(activeParent.value)) && 
              getConditionResult(item.rules)
            ) {
              props.push(item)
            }
          })
          return props
        })
        // 計算是否可操作屬性
        const getConditionResult = (rules: { originValue: string, destValue: string[] }[]) => {
          if (!rules) {
            return true
          }
          for (let i = 0; i < rules.length; i++) {
            const item = rules[i]
            if (
              item.destValue &&
              item.originValue &&
              !item.destValue.includes(activeComponents.value.properties[item.originValue])
            ) {
              return false
            }
          }
          return true
        }
        復制代碼

        最后使用循環(huán)渲染 getShowProperties 數(shù)據(jù)就可以完成。

        請求的處理

        在完全封裝的頁面內(nèi)部,大部分的動作都是配置出來的,請求的觸發(fā)除了初始化的,一般都是由點擊按鈕觸發(fā)請求,或者是組件的 change 事件中等,但是頁面內(nèi)部的請求依賴于項目的請求封裝,所以在內(nèi)部組件的屬性上面需要增加請求的相關信息。主要包括:url,type,params,在點擊按鈕觸發(fā)的請求的時候,去 properties 內(nèi)部拿到請求信息,由于請求方法依賴于項目,所以這個組件內(nèi)部不做請求封裝,由外部把封裝好的請求方法傳遞進去,組件內(nèi)外只做規(guī)范約定:

        // 外部通用的請求方法
        import HTTP from '@/http'

        export const commonRequest = (
          url: string
          params: Record<stringany> = {}, 
          type'post' | 'get' = 'get'
        ) => {
          return HTTP[`$${type}`](url, params)
        }
        復制代碼

        在遇到請求的 url 和 params,需要用到變量的情況下,我們可以約定變量格式,在內(nèi)部去解析且替換,如下:

        // 屬性
        const properties = {
            api: '/{type}/get-data',
            type'get',
            params: 'id={id}'
        }

        /**
         * 解析方法
         * url    需要解析的請求的路徑
         * params 需要解析的參數(shù)
         * parent 解析依賴的父級數(shù)據(jù)
         */

        const parseApiInfo = (url: string, params: string, parent: Record<stringany>) => {
          const depData = {
            // ...globalData // 全局數(shù)據(jù) 
            ..parant
          }
          const newUrl = url.replace(/\{(.*?)\}/g(a: string, b: string) => {
            return depData[b] || a
          })
          const newParams = params.replace(/\{(.*?)\}/g(a: string, b: string) => {
            return depData[b] || a
          })
          const obj: Record<stringstring> = {}
          newParams.replace(/([a-zA-Z0-9]+?)=(.+?)(&|$)/g(a: string, b: string, c: string) => {
            obj[b] = c
            return a
          })

          return {
            url: newUrl,
            params: obj
          }
        }
        復制代碼

        解析完 url 和 params 后,用 commonRequest 去執(zhí)行請求, 這樣基本完成對請求的處理。

        下拉選項數(shù)據(jù)的處理

        對于下拉選項數(shù)據(jù)的處理,可以大致分為兩種情況:

        • 靜態(tài)數(shù)據(jù)
        • 動態(tài)數(shù)據(jù)

        靜態(tài)數(shù)據(jù)

        靜態(tài)數(shù)據(jù)比較好處理,因為是不變的,所以我們可以直接在前端配置好,比如:

        const options = {
            optionsId: [
                {
                    label: '標簽',
                    value: 'val'
                }
                // ...
            ]
        }
        復制代碼

        動態(tài)數(shù)據(jù)

        動態(tài)數(shù)據(jù)會相對麻煩一點,因為需要后端配合,給出一個固定的接口,讓我們能一次性直接拿到整個頁面需要的所有的下拉數(shù)據(jù),格式如上。

        table 組件的設計

        table 組件是頁面內(nèi)主要的數(shù)據(jù)展示組件,因此功能上要考慮的較完善。

        table 組件相關的按鈕:

        • table 上方的按鈕,主要是上傳、新增、批量刪除、批量編輯等,這里的按鈕依賴的數(shù)據(jù)主要有搜索欄組件內(nèi)的數(shù)據(jù)和 table 多選框選中的數(shù)據(jù)
        • table 內(nèi) column 組件內(nèi)部的按鈕,因為是行內(nèi)按鈕,所以依賴的數(shù)據(jù)要把上方按鈕的選中的數(shù)據(jù)換成當前行的數(shù)據(jù)

        column 組件的設計:

        • column 組件的類型主要分為三種:selection(多選列)、default(默認)、operate(可操作列)
          • selection 是用于 table 第一列的多選列
          • default 為默認,不做其它配置
          • operate 為可操作列,該類型的列內(nèi)部可放子組件,比如 button,switch 等
        • 自定義文本,分為兩種情況:
          • 比較普通的狀態(tài)轉換文本,比如 0 -> 開啟;1 -> 關閉
          • 下拉選項的取值,這里我們需要一個具體下拉數(shù)據(jù)的 id,就是上方下拉數(shù)據(jù)的處理,然后用一個腳本程序去解析替換。

        按鈕與彈窗的處理

        在這種頁面內(nèi)部,按鈕組件應該是用的最多的組件,比如:彈窗、table、column、search等,都需要用上,并且按鈕在不同的位置,能處理的功能也不一樣,按鈕的功能主要分為以下幾種:

        • 確認提示框
        • 彈窗
        • 請求
        • 跳轉
        • 下載

        除了彈窗,其余的功能都可以通過自身的屬性字段來完成任務,但是彈窗是一個比較特殊且十分重要的功能,管理類系統(tǒng)的彈窗一般是需要新增或者編輯、查看等,所以彈窗組件的內(nèi)部需要將 form 組件的功能考慮進去。

        因為彈窗的內(nèi)容是自定義且內(nèi)容十分多,比如:彈窗內(nèi)部有 table,table 內(nèi)部有按鈕,按鈕還能打開彈窗等情況,所以我們需要將彈窗的內(nèi)容數(shù)據(jù)打平,否則會造成結構嵌套太深導致不好解析。

        const pageConfig = {
            type'page',
            properties: {},
            search: {},
            table: {},
            // 彈窗數(shù)據(jù)
            dialogTemplates: [
                {
                    id: 'xxx',
                    type'dialog',
                    properties: {},
                    // 彈窗內(nèi)部 form 表單組件
                    children: [
                        {
                            type'input',
                            id: 'xxx',
                            properties: {}
                        }
                        // ...
                    ],
                    // 彈窗底部按鈕
                    buttons: [
                        {
                            type'button',
                            id: 'xxx',
                            properties: {}
                        }
                        // ...
                    ]
                }
                // ...
            ]
        }
        復制代碼

        使用的話,我們在 button 組件上添加一個 dialogId 的字段,用來指向 dialogTemplates 數(shù)組內(nèi) id 為 dialogId 的彈窗數(shù)據(jù)即可。

        頁面的彈窗數(shù)量是不能做限制的,所以在彈窗的設計上,不能用普通的標簽去實現(xiàn),我們需要用服務方式去調(diào)用彈窗,如不了解 vue 服務方式的請看:使用服務方式來調(diào)用 vue 組件[2],這樣我們就實現(xiàn)了彈窗功能。

        彈窗與表格數(shù)據(jù)的聯(lián)動

        彈窗內(nèi)的新增和編輯大部分都會影響 table 列表數(shù)據(jù),還有就是在行內(nèi)的按鈕彈窗會默認攜帶行內(nèi)數(shù)據(jù)作為彈窗表單內(nèi)的初始數(shù)據(jù),所以我們在彈窗操作完成之后,要能刷新 table 數(shù)據(jù),所以我們要將頁面內(nèi)的按鈕功能統(tǒng)一的封裝起來,統(tǒng)一管理。如下:

        interface ButtonParams {
          params?: Record<stringany>
          callback?: () => void
        }

        export const btnClick = (btn: Record<stringany>, data: ButtonParams, pageId: string) => {
          if (!commonRequest) {
            commonRequest = globalMap.get('request')
          }
          return new Promise((res: (_v?: any) => void) => {
            if (btn.type === 'dialog') {  // dialog
              const dialogMap = globalMap.get(pageId).dialogMap
              if (dialogMap) {
                // 調(diào)用彈窗
                DpDialogService({ ...dialogMap.get(btn.dialogTemplateId), params: data.params, pageId, callback: data.callback })
              }
              res()
              return
            }
            const row = data.params && data.params._row
            if (data.params) {
              delete data.params._row
            }
            if (btn.type === 'confirm') { // confirm
              ElMessageBox.confirm(btn.message, btn.title, {
                type'warning',
                draggable: true
              }).then(() => {
                const { url, params } = parseApiInfo(btn.api, btn.requestParams, row, pageId)
                if (url) {
                  commonRequest(url, { ...data.params, ...params }, btn.requestType).then((ret: any = {}) => {
                    data.callback && data.callback()
                    res(ret.result)
                  })
                }
              })
            } else if (btn.type === 'link') { // link
              const route = parseApiInfo(btn.url, '', row, pageId)
              if (btn.api) {
                const { url, params } = parseApiInfo(btn.api, btn.requestParams, row, pageId)
                if (url) {
                  commonRequest(url, { ...data.params, ...params }, btn.requestType).then((ret: any = {}) => {
                    res(ret.result)
                    if (route.url) {
                      if (btn.externalLink) {
                        // 新窗口跳轉
                        openNewTab(route.url)
                      } else {
                        // 當前窗口跳轉
                        router.push(route.url)
                      }
                    }
                  })
                }
              } else {
                if (route.url) {
                  if (btn.externalLink) {
                    // 新窗口跳轉
                    openNewTab(route.url)
                  } else {
                    // 當前窗口跳轉
                    router.push(route.url)
                  }
                }
                res()
              }
            } else if (btn.type === 'none') { // none
              const { url, params } = parseApiInfo(btn.api, btn.requestParams, row, pageId)
              if (url) {
                commonRequest(url, { ...data.params, ...params }, btn.requestType).then((ret: any = {}) => {
                  data.callback && data.callback()
                  res(ret.result)
                })
              }
            } else if (btn.type === 'download') {
              const { url } = parseApiInfo(btn.api, '', row, pageId)
              if (url) {
                window.open(url)
              }
            }
          })
        }
        復制代碼

        上面按鈕的封裝,比如點擊彈窗,然后更新 table,我們就需要將更新 table 的方法放入回調(diào)函數(shù) callback 中, 在彈窗確認接口成功后,再執(zhí)行回調(diào)函數(shù)來刷新 table,對于依賴彈窗的功能都可以通過該方法去實現(xiàn)。

        自定義插槽

        對于有些特殊的表單功能通過配置無法實現(xiàn),我們需要開放兩個插槽,由開發(fā)者介入進行手動開發(fā)。

        • 第一個位置是 table 上方的按鈕位置區(qū)域
        • 第二個位置是 column 操作列的按鈕位置區(qū)域

        最后

        后臺管理系統(tǒng)可拖拽式組件,大體的設計思路就這樣。主要分為兩大塊:頁面配置和頁面渲染兩個組件。

        頁面配置組件:分為三個模塊(子組件列表、預覽區(qū)域、屬性配置區(qū)域)。配置組件思路比較容易,就是配置好各個組件之間的關系。

        頁面渲染組件:該組件就是拿到配置組件配置好的數(shù)據(jù)進行渲染,及業(yè)務邏輯的實現(xiàn)。

        整體功能不難,就是細節(jié)比較多,需要在各個組件、各個位置上都要想的要比較全面。如果想做好,最好還是得到后端的支持,該組件至少可以覆蓋管理系統(tǒng) 80% - 90% 的場景。

        寫的比較粗糙,有什么疑問或者更好的想法,歡迎留言指出

        關于本文:

        來自:對半

        https://juejin.cn/post/7073131582176886815

        瀏覽 112
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            懂色AV| 永久四色 | 成人性生交大片免费看黄103 | 奇米成人在线 | 成人久久生活片 | 免费观看又色又爽又黄的传媒 | 精品 码A片18 | 国产成人自拍在线观看 | 免费国产羞羞网站美图 | 免费观看黄色小视频 |