portal vue实现前端可视化portal(一)

灰太狼 2023-06-26 08:23 129阅读 0赞

项目需求: 组件分为内置组件 ,自定义组件,可以实现拖拽的形式实现一个门户页面的创建,当然也可以实现可视化大屏的创建,一开始内心是拒绝的,这玩意做出来,我是不是就要失业了。

一. 初版demo演示

在这里插入图片描述

二.项目结构

  • design 设计器
  • renderer 渲染器
  • 后台管理配置

2.1 设计器

就是demo展示的这个玩意,就是一个画布,可以让非开发人员去进行设计,分为内置组件自定义组件

  • 自定义组件
    就是设计器里面内置的组件,会单独放一个文件夹里面,后面打包发到npm上,给渲染器直接使用
  • 自定义插件
    内置组件不满足的,按照一定的规则,自己去写组件上传(设计器不支持配置),现在想的是两种方案,一种是webpack按照一定规则打包成js上传,然后渲染器,设计器通过script方式动态注入,这里就牵扯到new Vue的时机问题,因为只能事全局组件,就是牵扯到加载js导致UI界面卡一会的问题,第二种方案就是vue-cli3提供的单文件打包,异步script引入,但是样式这一块还需要解决,后面再考虑吧
用到的一些插件以及方案
  • 状态管理使用 provide inject 不使用vuex了。
  • vue-draggable 拖拽 https://github.com/SortableJS/Vue.Draggable
  • vue-draggable-resizable https://github.com/gorkys/vue-draggable-resizable-gorkys
  • echarts
    题外话: 可以用这两个插件去做自定义打印

组件json配置文件

  1. const patams = {
  2. mamDataType: 1, // 数据类型
  3. mamHidden: true, // 组件隐藏显示
  4. mamDataValue: '', // 数据值
  5. mamRefreshTime: 0, // 刷新时间
  6. mamDataProcessing: '', // 数据处理
  7. mamMethod: 'post', // 接口请求方式
  8. mamApi: 'http://' // 接口地址
  9. }
  10. export default [
  11. {
  12. name: '柱状图',
  13. key: '',
  14. icon: 'demo',
  15. type: 'k-histogram',
  16. options: {
  17. width: 300,
  18. height: 200,
  19. x: 10,
  20. y: 10,
  21. ...patams,
  22. mamCategory: true,
  23. title: {
  24. text: '',
  25. subtext: '',
  26. textAlign: 'left',
  27. textStyle: {
  28. color: '#333333',
  29. fontSize: 16
  30. },
  31. subtextStyle: {
  32. color: '#666666',
  33. fontSize: 12
  34. }
  35. },
  36. grid: {
  37. width: 'auto',
  38. height: 'auto'
  39. },
  40. backgroundColor: 'rgba(255, 255, 255, .6)',
  41. color: ['#3296fa'],
  42. xAxis: {
  43. type: 'category',
  44. data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']
  45. },
  46. yAxis: {
  47. type: 'value'
  48. },
  49. series: [{
  50. data: [120, 200, 150, 80, 70],
  51. type: 'bar'
  52. }]
  53. }
  54. }
  55. ]

设计器左侧组件列表

  1. <template>
  2. <div id="component-library">
  3. <!-- 内置组件 -->
  4. <div id="built-in" class="component-box">
  5. <div class="component-box-header">
  6. <span class="title">自定义组件</span>
  7. <i class="el-icon-caret-right" />
  8. </div>
  9. <draggable
  10. class="component-box-content"
  11. :list="componentsData"
  12. :group="{ name: 'component', pull: 'clone', put: false }"
  13. @start="handleEnd"
  14. >
  15. <component-item v-for="(item, index) in componentsData" :key="index" :icon="item.icon" :name="item.name" />
  16. </draggable>
  17. </div>
  18. <!-- 外置组件 -->
  19. <div id="external" class="component-box">
  20. <div class="component-box-header">
  21. <span class="title">外置组件</span>
  22. <i class="el-icon-caret-right" />
  23. </div>
  24. </div>
  25. </div>
  26. </template>
  27. <script>
  28. import componentsData from '@/config/components'
  29. import componentItem from '@/components/ComponentItem/index'
  30. import uuid from 'node-uuid'
  31. export default {
  32. name: 'ComponentLibrary',
  33. components: {
  34. componentItem
  35. },
  36. data() {
  37. return {
  38. componentsData
  39. }
  40. },
  41. methods: {
  42. handleEnd(evt) {
  43. // 通过key来保证组件的唯一性
  44. this.componentsData[evt.oldIndex].key = uuid.v1()
  45. }
  46. }
  47. }
  48. </script>

舞台画布

  1. <template>
  2. <div id="stage-canvas" @click="handleCanvas">
  3. <draggable
  4. class="draggable-box"
  5. :list="componentList"
  6. group="component"
  7. @change="handleChange"
  8. >
  9. <vue-draggable-resizable
  10. v-for="(item, index) in componentList"
  11. v-if="item.options.mamHidden"
  12. :key="index"
  13. eslint-disable-next-line
  14. vue
  15. no-use-v-if-with-v-for
  16. :grid="[20,20]"
  17. :x="10"
  18. :y="10"
  19. :w="item.options.width"
  20. :h="item.options.height"
  21. :snap="true"
  22. :snap-tolerance="10"
  23. :parent="true"
  24. :lock-aspect-ratio="true"
  25. :prevent-active-behavior="true"
  26. :parent-limitation="true"
  27. :is-active="true"
  28. :enable-native-drag="true"
  29. @refLineParams="getRefLineParams"
  30. @activated="activated(item)"
  31. @resizestop="resizestop"
  32. >
  33. <component :is="item.type" :options="item.options" :dom-id="'domId' + index" />
  34. </vue-draggable-resizable>
  35. <!--辅助线-->
  36. <span
  37. v-for="(item, index) in vLine"
  38. v-show="item.display"
  39. :key="'A' + index"
  40. class="ref-line v-line"
  41. :style="{ left: item.position, top: item.origin, height: item.lineLength}"
  42. />
  43. <span
  44. v-for="(item, index) in hLine"
  45. v-show="item.display"
  46. :key="'B' + index"
  47. class="ref-line h-line"
  48. :style="{ top: item.position, left: item.origin, width: item.lineLength}"
  49. />
  50. <!--辅助线END-->
  51. </draggable>
  52. </div>
  53. </template>
  54. <script>
  55. import draggable from './mixin/draggable'
  56. import resize from './mixin/resize'
  57. export default {
  58. name: 'StageCanvas',
  59. inject: ['app'],
  60. mixins: [draggable, resize],
  61. data() {
  62. return {
  63. componentList: [],
  64. vLine: [],
  65. hLine: [],
  66. enableNativeDrag: false,
  67. activeComponent: {} // 被激活的组件
  68. }
  69. }
  70. }
  71. </script>
  72. <style lang="scss">
  73. @import '../../styles/theme.scss';
  74. #stage-canvas{
  75. flex: 1;
  76. background: $stage-canvas-theme;
  77. border-left: 1px solid #eee;
  78. border-right: 1px solid #eee;
  79. position: relative;
  80. background: linear-gradient(-90deg, rgba(255,255,255, .3) 1px, transparent 1px) 10px 10px/20px 20px, linear-gradient(rgba(255,255,255, .3) 1px, transparent 1px) 0% 0%/20px 20px;
  81. .draggable-box{
  82. width: 100%;
  83. height: 100%;
  84. }
  85. }
  86. </style>

属性配置面板

  1. <template>
  2. <div id="attr-configuration">
  3. <!-- 画布操作 -->
  4. <p v-if="JSON.stringify(app.attrData.options) === '{}'">画布设置</p>
  5. <!-- 组件操作 -->
  6. <el-form v-else :data="app.attrData" label-width="100px" label-position="left">
  7. <el-tabs v-model="activeName">
  8. <el-tab-pane label="配置" name="first">
  9. <config-tab />
  10. </el-tab-pane>
  11. <el-tab-pane label="数据" name="second">
  12. <data-tab />
  13. </el-tab-pane>
  14. <el-tab-pane label="格式化" name="third">
  15. <format-tab />
  16. </el-tab-pane>
  17. <el-tab-pane label="参数" name="fourth">定时任务补偿</el-tab-pane>
  18. </el-tabs>
  19. </el-form>
  20. </div>
  21. </template>
  22. <script>
  23. import ConfigTab from './modules/ConfigTab'
  24. import DataTab from './modules/DataTab'
  25. import FormatTab from './modules/FormatTab'
  26. export default {
  27. name: 'AttrConfiguration',
  28. components: {
  29. ConfigTab,
  30. DataTab,
  31. FormatTab
  32. },
  33. inject: ['app'],
  34. data() {
  35. return {
  36. activeName: 'first'
  37. }
  38. }
  39. }
  40. </script>
  41. <style lang="scss">
  42. @import './AttrConfiguration.scss';
  43. </style>

柱状图组件

  1. <template>
  2. <div :id="domId" class="histogram" />
  3. </template>
  4. <script>
  5. export default {
  6. name: 'Histogram',
  7. props: {
  8. options: {
  9. type: Object,
  10. default: () => {}
  11. },
  12. domId: {
  13. type: String,
  14. default: ''
  15. }
  16. },
  17. data() {
  18. return {
  19. chart: null
  20. }
  21. },
  22. watch: {
  23. options: {
  24. handler(data) {
  25. let xAxis = {}
  26. let yAxis = {}
  27. if (data.mamCategory) {
  28. yAxis = { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'] }
  29. xAxis = { type: 'value' }
  30. } else {
  31. xAxis = { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'] }
  32. yAxis = { type: 'value' }
  33. }
  34. const options = {
  35. ...data,
  36. xAxis,
  37. yAxis
  38. }
  39. this.chart.resize()
  40. this.chart.setOption(options)
  41. },
  42. deep: true
  43. }
  44. },
  45. mounted() {
  46. this.$nextTick(() => {
  47. this.chart = window.echarts.init(document.getElementById(this.domId))
  48. this.chart.setOption(this.options)
  49. })
  50. }
  51. }
  52. </script>
  53. <style lang="scss">
  54. .histogram{
  55. min-width: 300px;
  56. min-height: 200px;
  57. width: 100%;
  58. height: 100%;
  59. }
  60. </style>

App.vue

  1. <template>
  2. <div id="app">
  3. <Layout />
  4. </div>
  5. </template>
  6. <script>
  7. import Layout from '@/components/layout/index'
  8. export default {
  9. name: 'App',
  10. provide() {
  11. return {
  12. app: this
  13. }
  14. },
  15. components: {
  16. Layout
  17. },
  18. data() {
  19. return {
  20. attrData: {
  21. options: {}
  22. }
  23. }
  24. },
  25. methods: {
  26. // 设置属性面板对象
  27. setAttrData(data) {
  28. this.attrData = Object.assign({}, data)
  29. }
  30. }
  31. }
  32. </script>
  33. <style lang="scss">
  34. html, body{
  35. width: 100%;
  36. height: 100%;
  37. }
  38. #app{
  39. width: 100%;
  40. height: 100%;
  41. }
  42. </style>

2.2 渲染器

一个npm包,谁使用谁引,开箱即用的那种

2.3 后台管理

用来管理portal模板,权限等一系列吧

具体功能还在探索当中,后续在更新。

发表评论

表情:
评论列表 (有 0 条评论,129人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Portal研究

    Portal Portal说明 一个portal应用可通过复杂的个性化配置用户提供定制的内容,而portal页面就是含有不同的portlet为不同的用户生成

    相关 前端测试

    背景 > 相信进行过前端开发的同学都知道,前端测试不仅仅涉及到功能的测试,而且也需要考虑到界面样式测试、多浏览器兼容性测试、性能测试。本文主要讨论分析目前前端测试的现状,

    相关 Angular cdk 学习之 Portals

           CDK里面Portals模块的功能就是将动态内容(这个内容可以是Component也可以是一个TemplateRef)呈现到应用程序中。更加直接点的解释就是把Po

    相关 bsp&&Portal Demo

    找朋友用3DMax做拉个简单的符合要求的场景,结果发现原来的程序在生成portal时有问题,现在已经修正拉这个问题。下一步要做的就是利用portal进行可见判断。现在有两种选择