后台管理系统可拖拽式组件的设计思路(拖拽前端组件化开发)-ag凯发k8国际

在后台管理系统的项目中,因为是数据管理,大部分都是 curd 的页面。比如:

image.png

对于这类的页面,我们完全可以设计一个组件,使用拖拽的方式,将组件一个个拖到指定区域,进行结构组装,然后再写一个对组装数据的渲染组件,渲染成页面即可。如下:

image.png

需要处理的问题

  • 数据结构的组装
  • 组件列表的选择
  • 组件的拖拽处理
  • 组件的配置信息配置
  • 请求的处理
  • 下拉选项数据的处理
  • table 组件的设计
  • 按钮与弹窗的处理
  • 弹窗与表格数据的联动
  • 自定义插槽

下面的内容只是做具体的设计思路分析,不做详细的代码展示,内容太多了,没法一一展示

数据结构的组装

由于这种都是组件的组装,所以我们要先定义具体组件的数据结构:

class component {    type: string = 'componentname'    properties: record = {}    children: record[] = []}复制代码

  • type:组件的名字
  • properties:组件的属性
  • children:当前组件下的子组件,用于嵌套

因为这种设计,整个页面就是一个大组件,按照同样的结构,所以我们最终的数据结构应该是这样的:

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 组件是固定位置的,所以这里就直接定死了,如果想直接拖拽定位,直接在数据顶层加 children 字段即可,然后可以进行拖拽排序位置。对于内部兄弟组件的排序功能,因为 vue 框架已经提供了 transition-group 组件,直接使用即可。而 table 下面的 buttons 数组,是由于在一般的 table 组件的上方会有一排按钮,用于新增,或者批量操作等。

组件列表的选择

对于数据管理页面,能够用上的组件无外乎就是 input,select,date,checkbox,button 等常用的 form 组件,还有我们要在配置页面重新封装 search,table 等业务组件,梳理出所有要用的组件后,我们需要用一个文件来汇总所有组件的属性:

// 页面结构class commontype {  title?: string  code?: string  filter?: string  readvariable?: string  writevariable?: string}export class common {  hide = true  type = 'common'  properties = new commontype  dialogtemplate = []}// searchclass 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],首先是左侧的组件列表的每一个组件都是可以拖拽的,在拖动到中间展示区域的时候,我们需要获取 drop 事件的目标元素,然后结合 dragstart 事件的信息,确定当前拖动组件的父级是谁,然后进行数据组装,这里所有的数据组装都由 drop 事件来完成,数据组装完成之后,更新中间的渲染区域。

组件的配置信息配置

每一个组件的配置信息其实都是不一样的,这些具体的属性,除了像 prop,id 这样通用的信息,都需要根据自己的情况来定,但是这些属性是与组件的 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'            }       ]    }    // ...]复制代码

定义完基本信息之后,我们还需要处理两种特殊情况:

  • 当组件中的一个属性其实是依赖另一些属性的具体值的处理
  • 组件处于不同的父级组件下,应用不同的属性

第一种情况,当一个属性依赖另一个或者几个的属性的时候,我们可以设置一个规则数组,比如:

[    {        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'    }]复制代码

以上的规则,我们可以去解析属性中的 rules 字段,当 type 的值为 url 时,我们就显示属性2,否则就不显示。

还有一种是同一个组件在不同的父级显示不同的可操作属性,比如,input 组件在 search 组件下不需要校验字段,而在 form 表单是需要的,所以我们可以增加一个字段 use:

const formitem = [    {        use: ['search', 'dialog'],        label: '标签',        value: 'label',        type: 'input'    }    // ...]复制代码

以上信息表示,formitem 组件的标签属性是在 search 和 dialog 组件中使用的,其它的父级组件下不会显示。

当所有组件的配置信息配置完成后,我们在聚焦预览区域的具体组件时,用程序筛选出可操作属性即可。

// 处理右侧可操作属性const getshowproperties = computed(() => {  const properties = propertytemplate[activecomponents.value.type]  if (!properties) {    return []  }  let props: record = []  properties.foreach((item: record) => {    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}复制代码

最后使用循环渲染 getshowproperties 数据就可以完成。

请求的处理

在完全封装的页面内部,大部分的动作都是配置出来的,请求的触发除了初始化的,一般都是由点击按钮触发请求,或者是组件的 change 事件中等,但是页面内部的请求依赖于项目的请求封装,所以在内部组件的属性上面需要增加请求的相关信息。主要包括:url,type,params,在点击按钮触发的请求的时候,去 properties 内部拿到请求信息,由于请求方法依赖于项目,所以这个组件内部不做请求封装,由外部把封装好的请求方法传递进去,组件内外只做规范约定:

// 外部通用的请求方法import http from '@/http'export const commonrequest = (  url: string,   params: record = {},   type: 'post' | 'get' = 'get') => {  return http[`$${type}`](url, params)}复制代码

在遇到请求的 url 和 params,需要用到变量的情况下,我们可以约定变量格式,在内部去解析且替换,如下:

// 属性const properties = {    api: '/{type}/get-data',    type: 'get',    params: 'id={id}'}/** * 解析方法 * url    需要解析的请求的路径 * params 需要解析的参数 * parent 解析依赖的父级数据 */const parseapiinfo = (url: string, params: string, parent: record) => {  const depdata = {    // ...globaldata // 全局数据     ..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 = {}  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 去执行请求, 这样基本完成对请求的处理。

下拉选项数据的处理

对于下拉选项数据的处理,可以大致分为两种情况:

  • 静态数据
  • 动态数据

静态数据

静态数据比较好处理,因为是不变的,所以我们可以直接在前端配置好,比如:

const options = {    optionsid: [        {            label: '标签',            value: 'val'        }        // ...    ]}复制代码

动态数据

动态数据会相对麻烦一点,因为需要后端配合,给出一个固定的接口,让我们能一次性直接拿到整个页面需要的所有的下拉数据,格式如上。

table 组件的设计

table 组件是页面内主要的数据展示组件,因此功能上要考虑的较完善。

table 组件相关的按钮:

  • table 上方的按钮,主要是上传、新增、批量删除、批量编辑等,这里的按钮依赖的数据主要有搜索栏组件内的数据和 table 多选框选中的数据
  • table 内 column 组件内部的按钮,因为是行内按钮,所以依赖的数据要把上方按钮的选中的数据换成当前行的数据

column 组件的设计:

  • column 组件的类型主要分为三种:selection(多选列)、default(默认)、operate(可操作列)
    • selection 是用于 table 第一列的多选列
    • default 为默认,不做其它配置
    • operate 为可操作列,该类型的列内部可放子组件,比如 button,switch 等
  • 自定义文本,分为两种情况:
    • 比较普通的状态转换文本,比如 0 -> 开启;1 -> 关闭
    • 下拉选项的取值,这里我们需要一个具体下拉数据的 id,就是上方下拉数据的处理,然后用一个脚本程序去解析替换。

按钮与弹窗的处理

在这种页面内部,按钮组件应该是用的最多的组件,比如:弹窗、table、column、search等,都需要用上,并且按钮在不同的位置,能处理的功能也不一样,按钮的功能主要分为以下几种:

  • 确认提示框
  • 弹窗
  • 请求
  • 跳转
  • 下载

除了弹窗,其余的功能都可以通过自身的属性字段来完成任务,但是弹窗是一个比较特殊且十分重要的功能,管理类系统的弹窗一般是需要新增或者编辑、查看等,所以弹窗组件的内部需要将 form 组件的功能考虑进去。

因为弹窗的内容是自定义且内容十分多,比如:弹窗内部有 table,table 内部有按钮,按钮还能打开弹窗等情况,所以我们需要将弹窗的内容数据打平,否则会造成结构嵌套太深导致不好解析。

const pageconfig = {    type: 'page',    properties: {},    search: {},    table: {},    // 弹窗数据    dialogtemplates: [        {            id: 'xxx',            type: 'dialog',            properties: {},            // 弹窗内部 form 表单组件            children: [                {                    type: 'input',                    id: 'xxx',                    properties: {}                }                // ...            ],            // 弹窗底部按钮            buttons: [                {                    type: 'button',                    id: 'xxx',                    properties: {}                }                // ...            ]        }        // ...    ]}复制代码

使用的话,我们在 button 组件上添加一个 dialogid 的字段,用来指向 dialogtemplates 数组内 id 为 dialogid 的弹窗数据即可。

页面的弹窗数量是不能做限制的,所以在弹窗的设计上,不能用普通的标签去实现,我们需要用服务方式去调用弹窗,如不了解 vue 服务方式的请看:使用服务方式来调用 vue 组件[2],这样我们就实现了弹窗功能。

弹窗与表格数据的联动

弹窗内的新增和编辑大部分都会影响 table 列表数据,还有就是在行内的按钮弹窗会默认携带行内数据作为弹窗表单内的初始数据,所以我们在弹窗操作完成之后,要能刷新 table 数据,所以我们要将页面内的按钮功能统一的封装起来,统一管理。如下:

interface buttonparams {  params?: record  callback?: () => void}export const btnclick = (btn: record, 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) {        // 调用弹窗        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 的方法放入回调函数 callback 中, 在弹窗确认接口成功后,再执行回调函数来刷新 table,对于依赖弹窗的功能都可以通过该方法去实现。

自定义插槽

对于有些特殊的表单功能通过配置无法实现,我们需要开放两个插槽,由开发者介入进行手动开发。

  • 第一个位置是 table 上方的按钮位置区域
  • 第二个位置是 column 操作列的按钮位置区域

最后

后台管理系统可拖拽式组件,大体的设计思路就这样。主要分为两大块:页面配置和页面渲染两个组件。

页面配置组件:分为三个模块(子组件列表、预览区域、属性配置区域)。配置组件思路比较容易,就是配置好各个组件之间的关系。

页面渲染组件:该组件就是拿到配置组件配置好的数据进行渲染,及业务逻辑的实现。

整体功能不难,就是细节比较多,需要在各个组件、各个位置上都要想的要比较全面。如果想做好,最好还是得到后端的支持,该组件至少可以覆盖管理系统 80% – 90% 的场景。

写的比较粗糙,有什么疑问或者更好的想法,欢迎留言指出

关于本文:

来自:对半

https://juejin.cn/post/7073131582176886815

ag凯发k8国际的版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

(0)
上一篇 2022年6月15日 上午8:38
下一篇 2022年6月15日 上午8:40

相关推荐

  • 前言 配置管理作为研发项目中一项重要的支持类活动,主要目的是通过配置管理相关活动来建立并维护研发项目中的工作产品的完整性。这些活动包括:配置管理计划、识别配置项、配置控制、配置状态…

    2022年6月30日
    128
  •   多年来,三台县高度重视动物防疫,在切实做好每年春秋两次农村动物集中防疫的基础上,始终坚持做好“布病”筛查、接产过程个人防护、养殖环境消毒和养成良好个人卫…

    科研百科 2022年5月20日
    118
  •   9月17日~18日,农业部畜牧业司司长马有祥带领调研组莅漯,调研生态畜牧业发展情况。qop   省畜牧局副局长王全周、漯河市副市长张锦印陪同。qop   在17日召开的座谈会上…

    科研百科 2022年6月5日
    152
  • 徐工机械全称是徐工集团工程机械股份有限公司,成立于1993年,是徐工集团目前唯一上市公司,主要从事起重机械、路面机械、桩工机械、其他工程机械及备件的研发、制造、销售和服务。1996…

    2022年7月20日
    237
  • 记者 张园园 通讯员 薄燕 4月22日上午,垦利区党建引领深化农村集体产权制度改革工作推进会议召开,动员全区上下进一步提高思想认识,分类有序推进,持续提升改革工作质量,推动全区党建…

    科研百科 2023年1月17日
    64
  • 党建和业务工作融合发展,是高校院系党组织建设和作用发挥的重要内容。近年来,山东建筑大学信息与电气工程学院强化党建引领,通过建立“四大机制”,探索构建党建业务融合发展体系,实现学院各…

    科研百科 4天前
    27
  • 来源:人民网-江西频道 “假如我还能生存,那我生存一天就要为中国呼喊一天……”在石城县税务局横江税务分局党支部开展的“诵读红色经典”主题党日活动中,年轻党员正深情朗诵方志敏《可爱的…

    科研百科 2023年1月2日
    91
  • 钢筋翻样在造价工作中非常普遍,把钢筋计算思路完全吃透,做起来其实不算难,但是扛不住数据多、工作量大,用手算效率又低,往往需要造价员加班加点才能完成工作! 钢筋翻样真的不用动手算,自…

    科研百科 2023年5月13日
    34
  •    7月8日,在肇东市大庄园肉业二期进口羊产品加工车间内,流水线上的工人们工装整洁,围绕市场需求,忙着对进口羊产品进行加工。据大庄园肉业加工技术主任杜艳辉介绍,该分厂在…

    科研百科 2022年5月25日
    125
  • 为加强政府合同管理,有效防范签约履约风险,西海岸新区于近日修订实施了《政府合同管理办法》(以下简称《办法》),从政府合同的磋商、起草、审查、签订、履行、变更、解除、争议处理、档案保…

    科研百科 2022年11月28日
    467
网站地图