• 【珍惜时间】iReport


    项目很点意思,感觉很高超的样子
    先放下项目的github地址:https://github.com/tctangyanan/iReport
    感谢各位伟大的程序员无私的分享自己的技术
    老规矩,我们会运行项目
    页面效果为

    先逐行分析代码
    先看main.js,引入了我们需要的一些全局插件

    //main.js
    import Vue from 'vue'
    import 'normalize.css/normalize.css'// A modern alternative to CSS resets
    import App from './App'
    import router from './router'
    // import routes from './router/routes'
    import store from './vuex/store'
    import { sync } from 'vuex-router-sync'
    import ElementUI from 'element-ui'
    import 'element-ui/lib/theme-chalk/index.css'
    import './styles/index.scss' // global css
    // 进度条
    import NProgress from 'nprogress' // progress bar
    import 'nprogress/nprogress.css'// progress bar style
    import UtilsPlugin from './assets/utils'
    import HttpPlugin from './http/index'
    import VueParticles from 'vue-particles' // 粒子酷炫效果
    import formatStyle from './filters/formatStyle.js'
    // import { Tag } from '../src/model/index'
    import './mock' // mock
    import VCharts from 'v-charts'
    Vue.config.productionTip = false
    Vue.use(ElementUI)
    // plugins
    Vue.use(UtilsPlugin)
    Vue.use(HttpPlugin)
    Vue.use(VCharts)
    Vue.use(VueParticles)
    Vue.filter('formatStyle', formatStyle)
    // const dispatch = store.dispatch
    router.beforeEach((to, from, next) => {
      NProgress.start() // start progress bar
      if (to.path === '/' || to.path === '/login' || to.path === '/404' || to.path === '/401') {
        setTimeout(next, 0)
      } else {
        setTimeout(next, 20)
      }
    })
    router.afterEach((to) => {
      NProgress.done() // finish progress bar
    })
    sync(store, router)
    /* eslint-disable no-new */
    // global
    window.$globalHub = new Vue({
      store,
      router,
      render: h => h(App)
    }).$mount('#app')
    
    
    //App.vue
    <template>
      <div id="app" style="height:100%;">
        <!--default slot-->
        <router-view :key="$route.path"></router-view>
      </div>
    </template>
    
    <script>
    import { mapState } from 'vuex'
    
    export default {
      components: {},
      data () {
        return {}
      },
      mounted () {
        // 禁用整个页面的右击事件
        // document.oncontextmenu = () => {
        //   return false
        // }
      },
      computed: {
        ...mapState({})
      },
      methods: {}
    }
    </script>
    
    <style lang="less" rel="stylesheet/less">
    #nprogress .spinner {
      display: none;
    }
    
    @import "styles/icon.css";
    </style>
    

    接下来看路由页面

    //router.js
    /* eslint-disable no-undef */
    import NotFind from '../pages/errors/404.vue'
    import VChars from '../pages/test/VChars.vue'
    // 生产/测试环境,使用路由懒加载
    const _import = process.env.NODE_ENV === 'development'
      ? file => require(`@/pages/${file}.vue`).default
      : file => () => System.import(`@/pages/${file}.vue`).then(m => m.default)
    export default [
      { path: '/', component: _import('login/Index') },
      { path: '/login', component: _import('login/Index') },
      { path: '/VChars', component: VChars },
      {
        path: '/main',
        component: resolve => require(['../layout/Layout'], resolve),
        children: [
          {
            path: '/dashboard',
            name: '首页',
            component: _import('dashboard/Index')
          }, {
            path: '/table/list',
            name: '表格',
            component: _import('table/Index')
          }, {
            path: '/icons',
            name: '图标',
            component: _import('icons/Index')
          }, {
            path: '/role/list',
            name: '角色管理',
            component: _import('roles/Index')
          }, {
            path: '/user/list',
            name: '用户管理',
            component: _import('users/Index')
          }, {
            path: '/menus/list',
            name: '菜单设置',
            component: _import('menus/Index')
          }, {
            path: '/smsCode/list',
            name: '短信码',
            component: _import('smsCode/Index')
          }, {
            path: '/errorCode/list',
            name: '错误码',
            component: _import('errorCode/Index')
          }, {
            path: '/404',
            name: '404',
            component: NotFind
          }, {
            path: '/401',
            name: '401',
            component: _import('errors/401')
          }
        ]
      },
      { path: '/editor', component: _import('editor/Index') }
    ]
    
    //index.js
    /* eslint-disable no-undef */
    import Vue from 'vue'
    import Router from 'vue-router'
    import config from '../../config/index'
    import routes from './routes'
    
    Vue.use(Router)
    export default new Router({
      mode: 'history', // 后端支持可开
      base: config.build.assetsPublicPath,
      routes
    })
    
    

    接下来我们根据页面效果看代码

    //srcpagesloginIndex.vue
    <template>
      <!--region 粒子效果的登录背景-->
      <div class="login-page">
        <vue-particles color="#fff" :particleOpacity="0.7" :particlesNumber="60" shapeType="circle" :particleSize="4"
                       linesColor="#fff" :linesWidth="1" :lineLinked="true" :lineOpacity="0.4" :linesDistance="150"
                       :moveSpeed="2" :hoverEffect="true" hoverMode="grab" :clickEffect="true" clickMode="push"
                       class="bg-lizi">
        </vue-particles>
        <!--region 登录表单-->
        <el-form :model="loginForm" class="login-form" label-position="left" size="large">
          <el-form-item>
            <el-input type="text" v-model="loginForm.phone" auto-complete="off" placeholder="注册时的手机号">
              <span v-html="'&#xe851;'" class="axon-icon" slot="prefix"></span>
            </el-input>
          </el-form-item>
          <el-form-item>
            <el-input :type="passwordType" v-model="loginForm.password" size="large" auto-complete="off" placeholder="登录密码">
              <span v-html="'&#xe62a;'" class="axon-icon" slot="prefix"></span>
              <span v-html="passwordType === 'password' ? '&#xe901;' : '&#xe6e0;'" class="axon-icon" slot="suffix"
                    @click="showPwd()"></span>
            </el-input>
          </el-form-item>
          <el-button type="primary" @click="submitLogin" @enter="submitLogin()" :loading="loading">登&nbsp;&nbsp;&nbsp;&nbsp;录</el-button>
        </el-form>
        <!--endregion-->
      </div>
      <!--endregion-->
    </template>
    <script>
    import BLL from './Index.js'
    import menusLisit from '../../../static/json/limit.json'
    
    export default {
      components: {},
      data () {
        return {
          loginForm: {
            phone: '18951871658',
            password: '123456'
          },
          loading: false,
          menus: menusLisit,
          passwordType: 'password' // 密码控件的类型
        }
      },
      created () {
        this.BLL = new BLL(this)
      },
      mounted () {
      },
      methods: {
        // 登录
        submitLogin () {
          this.BLL.login()
        },
        showPwd () {
          this.passwordType === 'password' ? this.passwordType = 'text' : this.passwordType = 'password'
        }
      }
    }
    </script>
    
    <style lang="scss">
      @import "./Index.scss";
    </style>
    
    //srcpagesloginIndex.js
    import Base from '../../base/index'
    
    export default class extends Base {
      /**
       * 用户登录
       */
      login () {
        if (!this.vm.loginForm.phone) {
          this.vm.$message.warning('请填写登录账号!')
          return false
        }
        if (!this.vm.$utils.Validate.chkFormat(this.vm.loginForm.phone, 'phone')) {
          this.vm.$message.warning('登录手机号的格式不正确!')
          return false
        }
        if (!this.vm.loginForm.password) {
          this.vm.$message.warning('请填写登录密码!')
          return false
        }
        if (this.vm.loginForm.phone !== '18951871658' || this.vm.loginForm.password !== '123456') {
          this.vm.$message.warning('账号或密码不匹配!')
          return false
        }
        this.vm.loading = true
        // 取用户的平台权限信息,并且持久化
        this.vm.$store.dispatch('init_sidebar_data', this.vm.menus)
        // 清空tab标签
        this.vm.$store.dispatch('del_all_tags')
        // 登录成功
        this.vm.$notify.success({
          title: '登录成功',
          message: '欢迎小主回来!'
        })
        this.vm.$router.push({path: '/dashboard'})
      }
    }
    

    //srcpagesdashboardIndex.vue
    <template>
      <div class="dashboard-page">
        <!--region 栏目-->
        <el-row class="panel-group" :gutter="40">
          <template v-for="(panel, index) in panelList">
            <el-col :key="index" :xs="12" :sm="12" :lg="6" class="card-panel-col">
              <div class='card-panel' @click="handleSetLineChartData(panel.id)">
                <div class="card-panel-icon-wrapper icon-people">
                  <div :class="`card-panel-icon panel-${panel.color}`">
                    <span class="axon-icon" v-html="panel.icon"></span>
                  </div>
                </div>
                <div class="card-panel-description">
                  <div class="card-panel-text">{{ panel.label }}</div>
                  <count-to class="card-panel-num" :startVal="0" :endVal="panel.value" :decimals=2 :duration="2600"></count-to>
                </div>
              </div>
            </el-col>
          </template>
        </el-row>
        <!--endregion-->
        <!--region 创建新场景-->
        <div class="add-new-scene">
          <el-button type="primary" icon="el-icon-plus" plain round @click.native="creatNewScene">添加新场景</el-button>
        </div>
        <!--endregion-->
      </div>
    </template>
    <script>
    const CountTo = () => import('vue-count-to')
    const lineChartData = {
      newVisitis: {
        expectedData: [100, 120, 161, 134, 105, 160, 165],
        actualData: [120, 82, 91, 154, 162, 140, 145]
      },
      messages: {
        expectedData: [200, 192, 120, 144, 160, 130, 140],
        actualData: [180, 160, 151, 106, 145, 150, 130]
      },
      purchases: {
        expectedData: [80, 100, 121, 104, 105, 90, 100],
        actualData: [120, 90, 100, 138, 142, 130, 130]
      },
      shoppings: {
        expectedData: [130, 140, 141, 142, 145, 150, 160],
        actualData: [120, 82, 91, 154, 162, 140, 130]
      }
    }
    export default {
      components: { CountTo },
      data () {
        return {
          panelList: [
            {
              id: 'newVisitis',
              label: 'Demo1',
              value: 102400,
              icon: '&#xe63e;',
              color: 1// '#40c9c6'
            },
            {
              id: 'messages',
              label: 'Demo2',
              value: 81212,
              icon: '&#xe72d;',
              color: 2 // '#36a3f7'
            },
            {
              id: 'purchases',
              label: 'Demo3',
              value: 9280.3,
              icon: '&#xe736;',
              color: 3 // '#f4516c'
            },
            {
              id: 'shoppings',
              label: 'Demo4',
              value: 13600.6,
              icon: '&#xe61b;',
              color: 4 // '#34bfa3'
            }
          ],
          lineChartData: lineChartData.newVisitis
        }
      },
      mounted () {
      },
      methods: {
        handleSetLineChartData (panel) {
          this.lineChartData = lineChartData[panel]
        },
        /**
         * 创建新场景
         */
        creatNewScene () {
          this.$router.push({ path: '/editor' })
        }
      }
    }
    </script>
    
    <style lang="scss" scoped>
    @import "../../styles/font.scss";
    
    .dashboard-page {
      background-color: #f0f2f5;
      padding: 32px;
      overflow-y: auto;
      .panel-group {
        padding: 40px 0;
        .card-panel-col {
          margin-top: 10px;
          .card-panel {
            height: 108px;
            cursor: pointer;
            font-size: $font-size-s;
            position: relative;
            overflow: hidden;
            color: #666;
            background: #fff;
            -webkit-box-shadow: 4px 4px 40px rgba(0, 0, 0, 0.05);
            box-shadow: 4px 4px 40px rgba(0, 0, 0, 0.05);
            border-color: rgba(0, 0, 0, 0.05);
            display: flex;
            padding: 0 20px;
    
            &:hover {
              .card-panel-icon-wrapper {
                .card-panel-icon {
                  border-radius: 4px;
                  transition: all 0.38s ease-out;
                  &.panel-1 {
                    background-color: #40c9c6;
                    .axon-icon {
                      color: #ffffff;
                    }
                  }
                  &.panel-2 {
                    background-color: #36a3f7;
                    .axon-icon {
                      color: #ffffff;
                    }
                  }
                  &.panel-3 {
                    background-color: #f4516c;
                    .axon-icon {
                      color: #ffffff;
                    }
                  }
                  &.panel-4 {
                    background-color: #34bfa3;
                    .axon-icon {
                      color: #ffffff;
                    }
                  }
                }
              }
            }
            .card-panel-icon-wrapper {
              flex: 0 0 50%;
              .card-panel-icon {
                 60px;
                height: 60px;
                margin-top: 29px;
                text-align: center;
                line-height: 60px;
                &.panel-1 {
                  .axon-icon {
                    color: #40c9c6;
                  }
                }
                &.panel-2 {
                  .axon-icon {
                    color: #36a3f7;
                  }
                }
                &.panel-3 {
                  .axon-icon {
                    color: #f4516c;
                  }
                }
                &.panel-4 {
                  .axon-icon {
                    color: #34bfa3;
                  }
                }
                .axon-icon {
                  font-size: $font-size-large;
                }
              }
            }
            .card-panel-description {
              flex: 0 0 50%;
              text-align: right;
              margin: 26px 0;
              .card-panel-text {
                font-size: $font-size-xxl;
                font-weight: 600;
                line-height: 32px;
                color: rgba(0, 0, 0, 0.45);
              }
              .card-panel-num {
                font-weight: 600;
                font-size: $font-size-xxxl;
              }
            }
          }
        }
      }
      .add-new-scene {
        text-align: center;
      }
    }
    </style>
    

    这个里面写的css的方式处理的非常优雅,包括在css里面的定义,很值得学习
    接下来的页面是,点击添加新场景


    我们先看Index.vue中引入的组件

    //srcpageseditorIndex.vue
    <template>
      <div class="editor-index">
        <!--顶部各种组件列表-->
        <the-tools></the-tools>
        <!--region 中间区域分为三个区间-->
        <div class="index-center" ref="centerBox">
          <!--左边在线模板列表-->
          <the-template-list></the-template-list>
          <!--内容面板 编辑-->
          <div class="editor-box">
            <the-editor-container ref="editorContainer"></the-editor-container>
          </div>
          <!--右侧页面列表-->
          <the-page-list></the-page-list>
          <!--组件属性设置面板-->
          <the-comp-props-config></the-comp-props-config>
          <!--组件右击图层设置面板-->
          <the-comp-layer-manager></the-comp-layer-manager>
        </div>
        <!--endregion-->
      </div>
    </template>
    
    <script>
    // 右侧页面列表
    import ThePageList from './components/ThePageList'
    // 左边在线模板列表
    import TheTemplateList from './components/TheTemplateList'
    // 顶部各种组件列表
    import TheTools from './components/TheTools'
    // 面板
    import TheEditorContainer from './components/TheEditorContainer'
    // 组件属性设置面板
    import TheCompPropsConfig from './components/TheCompPropsConfig'
    // 组件右击图层设置面板
    import TheCompLayerManager from './components/TheCompLayerManager'
    
    export default {
      // 引入组件
      components: {
        ThePageList,
        TheTemplateList,
        TheTools,
        TheEditorContainer,
        TheCompPropsConfig,
        TheCompLayerManager
      },
      data () {
        return {
          toolJson: {},
          panelWidthAndHeight: {
             200,
            height: 400
          }
        }
      },
      created () {
        // 初始化
        this.$store.dispatch('initPageEditor')
      },
      mounted () {
      },
      computed: {},
      methods: {}
    }
    </script>
    
    <style lang="scss">
    @import "../../styles/variables";
    @import "../../styles/mixin";
    
    .editor-index {
      background-color: #fff;
       100%;
      height: 100%;
      display: flex;
      flex-direction: column;
      .index-center {
        box-sizing: border-box;
        flex: 0 0 calc(#{"100% - "+ $tool-header-height +""});
        display: flex;
        flex-direction: row;
        position: relative;
        @include scrollBar;
        .editor-box {
          box-sizing: border-box;
           100%;
          overflow-y: auto;
          padding: 40px 0;
          background-color: #eee;
        }
      }
    }
    </style>
    

    接下来我们一个一个分析

    //srccomponentsEChars
    eportComponent.js
    
    export default {
      // 园区客流检测 数据
      reportComponentList: [
        {
          componentId: 87001,
          componentName: '柱状图',
          sourceRemark: '项王故里为节日期间游客量最多的,三台山森林公园和洪泽湖湿地公园分列二三名。',
          tempHtml: '<i-LBarFour :chartsId="dataObj.chartsId" :title="dataObj.title" :xAxis="dataObj.xAxis" :yAxis="dataObj.yAxis" :unit="dataObj.unit"></i-LBarFour >',
          dataObj: {
            chartsId: `circularCharsID`,
            title: '接待省外访客量TOP10景区排名',
            xAxis: ['项王故里', '三台山森林公园', '洪泽湖湿地公园', '湖滨公园', '洋河酒厂', '雪枫公园', '龙王庙行宫', '中国杨树博物馆'],
            yAxis: [38056, 33824, 22672, 18616, 13112, 7016, 6864, 5792],
            unit: '人次'
          }
        },
        {
          componentId: 87002,
          componentName: '折线图',
          sourceRemark: '中秋期间,9月23日接待访客量最多,9月24日接待访客量最少',
          tempHtml: '<i-Line :chartsId="dataObj.chartsId" :title="dataObj.title" :xAxis="dataObj.xAxis" :yAxis="dataObj.yAxis" :unit="dataObj.unit"></i-Line >',
          dataObj: {
            chartsId: `lineCharsID`,
            title: '分日客流量变化',
            xAxis: ['9月22日', '9月23日', '9月23日'],
            yAxis: [59.28, 63.86, 33.86],
            unit: '万人'
          }
        },
        {
          componentId: 87003,
          componentName: '饼状图',
          sourceRemark: '节假日期间,年龄在22岁及以下、23-35岁和36-45岁的访客占访客总量的60%',
          tempHtml: '<i-HollowPie :chartsId="dataObj.chartsId" :title="dataObj.title" :xAxis="dataObj.xAxis" :yAxis="dataObj.yAxis" :unit="dataObj.unit"></i-HollowPie>',
          dataObj: {
            chartsId: `hollowPieCharsID`,
            title: '访客年龄段分布',
            xAxis: ['22岁及以下', '23-35岁', '36-45岁', '46-55岁', '56岁及以上'],
            yAxis: [25145, 108532, 83761, 91837, 53111],
            unit: '%'
          }
        }
      ]
    }
    

    //srccomponentsiDialogImgIndex.vue
    <!--region 封装的图片列表 卡片-->
    <template>
      <div class="custom-dialog-wrapper" v-if="dialogVisible">
        <div class="custom-dialog-container" :style="{ '60%',marginTop: '15vh'}">
          <el-container>
            <el-aside :style="{height: '75vh','200px'}">
              <div class="custom-aside__wrapper">
                <span class="custom-aside__title">图片库</span>
                <el-upload
                  class="upload-demo"
                  drag
                  action="https://jsonplaceholder.typicode.com/posts/"
                  multiple>
                  <i class="el-icon-upload"></i>
                  <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
                  <div class="el-upload__tip" slot="tip">只能上传jpg/png文件,且不超过500kb</div>
                </el-upload>
              </div>
            </el-aside>
            <el-container>
              <el-header>
                <div class="custom-dialog__header">
                  <span class="custom-dialog__title">{{title}}</span>
                  <a class="custom-dialog__headerbtn" @click="closeDialog">
                    <i class="el-dialog__close el-icon el-icon-close"></i>
                  </a>
                </div>
              </el-header>
              <el-main>
                <ul v-if="operationFlag==='BackgroundImage'">
                  背景图片
                  <template v-for="item in backgroundImageList">
                    <li :key="item.id" @click="selectImage(item)" style="height: 30vh;">
                      <div class="item-bg-images" :style="{backgroundImage: 'url(' + item.url + ')'}">
                        <div class="item-active"
                             v-if="imageSelectItem && imageSelectItem.id === item.id">
                          <span class="axon-icon" v-html="'&#xe61e;'"></span>
                        </div>
                      </div>
                    </li>
                  </template>
                </ul>
                <ul v-if="operationFlag==='Image' || operationFlag==='UpdateImages'">
                  <template v-for="item in iconList">
                    <li :key="item.id" @click="selectImage(item)" style="height: 24vh;border: 1px solid #c8c9ca">
                      <div class="item-bg-images" :style="{backgroundImage: 'url(' + item.url + ')'}">
                        <div class="item-active"
                             v-if="imageSelectItem && imageSelectItem.id === item.id">
                          <span class="axon-icon" v-html="'&#xe61e;'"></span>
                        </div>
                      </div>
                    </li>
                  </template>
                </ul>
              </el-main>
              <el-footer>
                <el-button @click="cancelSelectImage" round>&nbsp;&nbsp;&nbsp;&nbsp;取消&nbsp;&nbsp;&nbsp;&nbsp;</el-button>
                <el-button type="success" @click="confirmSelectImage" round>&nbsp;&nbsp;&nbsp;&nbsp;确定&nbsp;&nbsp;&nbsp;&nbsp;</el-button>
              </el-footer>
            </el-container>
          </el-container>
        </div>
      </div>
    </template>
    <!--endregion-->
    <script>
    export default {
      name: 'iDialogImg',
      data () {
        return {
          dialogVisible: '',
          title: '',
          operationFlag: '',
          backgroundImageList: [
            { id: 1, url: require('../../assets/images/bgi/1521186133451071.jpg') },
            { id: 2, url: require('../../assets/images/bgi/1522660019106280.png') },
            { id: 3, url: require('../../assets/images/bgi/1521186133451071.jpg') },
            { id: 4, url: require('../../assets/images/bgi/1522660019106280.png') },
            { id: 5, url: require('../../assets/images/bgi/1521186133451071.jpg') },
            { id: 6, url: require('../../assets/images/bgi/1522660019106280.png') },
            { id: 7, url: require('../../assets/images/bgi/1521186133451071.jpg') },
            { id: 8, url: require('../../assets/images/bgi/1522660019106280.png') },
            { id: 9, url: require('../../assets/images/bgi/1521186133451071.jpg') },
            { id: 10, url: require('../../assets/images/bgi/1522660019106280.png') },
            { id: 11, url: require('../../assets/images/bgi/1521186133451071.jpg') },
            { id: 12, url: require('../../assets/images/bgi/1522660019106280.png') },
            { id: 13, url: require('../../assets/images/bgi/1521186133451071.jpg') },
            { id: 14, url: require('../../assets/images/bgi/1522660019106280.png') }
          ],
          iconList: [
            { id: 1, url: require('../../assets/icon/bridge1.png') },
            { id: 2, url: require('../../assets/icon/person.png') },
            { id: 3, url: require('../../assets/icon/ren1.png') },
            { id: 4, url: require('../../assets/icon/bridge1.png') },
            { id: 5, url: require('../../assets/icon/person.png') },
            { id: 6, url: require('../../assets/icon/ren1.png') },
            { id: 7, url: require('../../assets/icon/bridge1.png') },
            { id: 8, url: require('../../assets/icon/person.png') },
            { id: 9, url: require('../../assets/icon/bridge1.png') },
            { id: 10, url: require('../../assets/icon/person.png') },
            { id: 11, url: require('../../assets/icon/person.png') },
            { id: 12, url: require('../../assets/icon/ren1.png') },
            { id: 13, url: require('../../assets/icon/bridge1.png') },
            { id: 14, url: require('../../assets/icon/person.png') }
          ],
          imageSelectItem:
            {
              id: null,
              url:
                ''
            }
        }
      },
      created () { },
      mounted () {
      },
      computed: {
        // 取当前页面
        curPage () {
          return this.$store.getters.curPage
        },
        // 取当前组件
        currComp () {
          return this.$store.getters.curComp
        }
      },
      methods: {
        // 关闭弹出框
        closeDialog () {
          this.dialogVisible = false
        },
        // 新增图片/背景图增弹出框
        showAddDialog (flag) {
          this.operationFlag = flag
          switch (flag) {
            case 'Image':
              this.title = '新增图标'
              break
            case 'BackgroundImage':
              this.title = '设置背景图'
              break
            default:
              break
          }
          this.dialogVisible = true
        },
        // 编辑图片/背景图增弹出框
        showEditDialog () {
          this.title = '修改图标'
          this.operationFlag = 'UpdateImages'
          this.dialogVisible = true
        },
        // 取消图片选中
        cancelSelectImage () {
          this.imageSelectItem.id = null
          this.imageSelectItem.url = ''
        },
        // 确认操作
        confirmSelectImage () {
          this.closeDialog()
          switch (this.operationFlag) {
            case 'Image':
              this.handleAddImageComponent(this.imageSelectItem)
              break
            case 'BackgroundImage':
              this.updateCurPageBackgroundImage(this.imageSelectItem)
              break
            case 'UpdateImages':
              this.updateProps()
              break
            default:
              break
          }
        },
        // 选中事件
        selectImage (item) {
          console.log(item)
          this.imageSelectItem.id = item.id
          this.imageSelectItem.url = item.url
        },
        // 添加图片
        handleAddImageComponent (item) {
          this.$store.dispatch('addNewCompByParams', { name: 'Image', url: item.url })
        },
        // 编辑图片组件
        updateProps () {
          this.$store.dispatch('editComp', {
            type: 'props',
            value: { 'url': this.imageSelectItem.url }
          })
        },
        // 设置当前页面背景
        updateCurPageBackgroundImage (item) {
          this.curPage.css.bgi = item.url
          this.syncCss('css', { 'bgi': this.curPage.css['bgi'] }, this.curPage.id)
        },
        // 同步到持久化
        syncCss (type, val, pageId) {
          this.$store.dispatch('editCurPage', {
            type: type,
            value: val,
            pageId: pageId
          })
        }
      }
    }
    </script>
    
    <style lang="scss">
    .custom-dialog-wrapper {
      z-index: 2025;
      position: fixed;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      overflow: auto;
      margin: 0;
      .custom-dialog-container {
        position: relative;
        margin: 0 auto 50px;
        background: #fff;
        border-radius: 2px;
        box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
        box-sizing: border-box;
         50%;
      }
    }
    
    .el-aside {
      color: #333;
      text-align: center;
      .custom-aside__wrapper {
         100%;
        position: relative;
        background-color: #1f2d3d;
        .custom-aside__title {
          position: absolute;
          top: 0;
          left: 0;
           200px;
          height: 60px;
          line-height: 60px;
          font-size: 18px;
          color: #fff;
          background: #212121;
          font-weight: bolder;
        }
        .upload-demo {
          position: absolute;
          top: 80px;
          left: 0;
          .el-upload-dragger {
             180px;
          }
        }
      }
    }
    
    .el-header {
      color: #333;
      box-shadow: 0 6px 6px 0 rgba(0, 0, 0, .16);
      .custom-dialog__header {
        padding: 20px 20px 10px 20px;
        .custom-dialog__title {
          line-height: 24px;
          font-size: 18px;
          color: #303133;
        }
        .custom-dialog__headerbtn {
          position: absolute;
          top: 20px;
          right: 20px;
          padding: 0;
          background: transparent;
          border: none;
          outline: none;
          cursor: pointer;
          font-size: 16px;
        }
      }
    }
    
    .el-main {
      background-color: #E9EEF3;
      color: #333;
      text-align: center;
      height: 60vh;
      overflow: scroll;
      ul {
        li {
          float: left;
           24%;
          margin-top: 1.5vh;
          .item-bg-images {
             100%;
            height: 100%;
            background-repeat: no-repeat;
            background-size: 100% 100%;
            .item-active {
               100%;
              height: 100%;
              display: flex;
              justify-content: center;
              align-items: center;
              background-color: rgb(0, 0, 0);
              opacity: 0.7;
              span {
                color: #31b4a7;
                font-size: 52px;
              }
            }
          }
        }
        li:nth-child(4n + 1) {
          margin-left: 0px;
        }
        li:nth-child(4n + 2) {
          margin-left: 1%;
        }
        li:nth-child(4n + 3) {
          margin-left: 1%;
        }
        li:nth-child(4n + 4) {
          margin-left: 1%;
        }
      }
    }
    
    .el-footer {
      text-align: center;
      line-height: 60px;
    }
    </style>
    
    //srcpageseditorcomponentsTheTemplateList.vue
    <template>
      <!--region 左边在线模板列表-->
      <div class="the-template-list">
    
      </div>
      <!--endregion-->
    </template>
    
    <script>
    export default {
      components: {},
      data () {
        return {}
      },
      created () {
      },
      mounted () {
      },
      computed: {},
      methods: {}
    }
    </script>
    
    <style lang="scss">
    @import "../../../styles/variables";
    .the-template-list {
      background-color: #fff;
      box-sizing: border-box;
      border-right: 1px solid $border-color-1;
      flex: 0 0 $template-width;
    }
    </style>
    

    //srcvuexconstTypes.js
    export default {
      // 页面组件
      ADD_COMPONENT: 'add_component', // 添加组件
      TOGGLE_COMP: 'toggle_component', // 切换组件
      COPY_COMP: 'copy_comp', // 拷贝组件
      EDIT_COMP: 'edit_component', // 编辑组件
      REMOVE_COMP: 'remove_component', // 删除组件
      // 页面
      ADD_PAGE: 'add_page', // 添加新页面
      INSERT_PAGE: 'insert_page', // 插入页面
      COPY_PAGE: 'copy_page', // 拷贝页面
      REMOVE_PAGE: 'remove_page', // 删除页面
      EDIT_PAGE: 'edit_page', // 编辑页面
      TOGGLE_PAGE: 'toggle_page', // 切换页面
      ADD_COMP_TO_PAGES: 'add_component_to_page', // 往页面中添加组件
      REMOVE_COMP_TO_PAGES: 'remove_component_to_page', // 往页面中删除组件
      // 属性编辑调整
      OPEN_PROPS_PANEL: 'open_props_panel', // 打开组件属性面板
      CLOSE_PROPS_PANEL: 'close_props_panel', // 关闭组件属性面板
      // 图层编辑调整
      OPEN_LAYER_PANEL: 'open_layer_panel', // 打开图层属性面板
      CLOSE_LAYER_PANEL: 'close_layer_panel' // 关闭图层属性面板
    }
    
    //srcvuexmodulespages.js
    import types from '../constTypes.js'
    import { getNewPageId, getNewPage } from '../function.js'
    import { cloneObj } from '../../assets/utils/util.js'
    
    const state = {
      list: [],
      curPageId: null
    }
    const getters = {
      // 取页面列表
      pages: (state) => state.list,
      // 取当前页面ID
      curPageId: (state) => {
        if (state.curPageId) {
          return state.curPageId
        }
        if (state.list[0]) {
          return state.list[0]['id']
        }
      },
      // 取当前页面
      curPage: (state) => {
        return state.list
          .find(
            (_x) => _x.id === state.curPageId
          ) || state.list[0]
      },
      getPageConfigByPageId: (state) => (pageId) => {
        return state.list.find(_x => _x.id === pageId)
      }
    }
    const mutations = {
      // 切换页面
      [types.TOGGLE_PAGE] (state, pageId) {
        state.curPageId = pageId
      },
      // 添加页面
      [types.ADD_PAGE] (state, pageData) {
        state.list.push(pageData)
      },
      // 插入页面
      [types.INSERT_PAGE] (state, { page, pageId }) {
        let index = state.list
          .findIndex(
            _x => _x.id === pageId
          )
        if (index > -1) {
          state.list.splice(index + 1, 0, page)
        }
      },
      // 拷贝页面
      [types.COPY_PAGE] (state, { prePageId, pageId }) {
        let list = state.list
        let index = list.findIndex(_x => _x.id === prePageId)
        if (index > -1) {
          // 复制一个新的页面JSON对象
          let pageData = cloneObj(list[index])
          pageData.id = pageId
          // 克隆的页面中组件处理
          // if (pageData.comps && pageData.comps.length > 0) {
          //   for (let i = 0; i < pageData.comps.length; i++) {
          //     pageData.comps[i].id = pageData.comps[i].id + (i + 1) * pageData.comps[i].id
          //     pageData.comps[i].parentId = pageId
          //   }
          // }
          list.splice(index + 1, 0, pageData)
          state.curPageId = pageId
        }
      },
      // 删除页面
      [types.REMOVE_PAGE] (state, pageId) {
        let list = state.list
        let index = list.findIndex(_x => _x.id === pageId)
        if (index > -1) {
          list.splice(index, 1)
          let nextActivePage = list[index] || list[index - 1]
          state.curPageId = nextActivePage['id']
        }
      },
      // 编辑当前页面
      [types.EDIT_PAGE] (state, { type, value, pageId }) {
        let page = state.list
          .find(
            (_x) => _x.id === pageId || _x.id === state.curPageId
          )
        if (page) {
          let pageProp = page[type]
          for (let key in value) {
            if (!pageProp[key]) {
              continue
            }
            if (typeof value[key] === 'object') {
              Object.assign(pageProp[key], value[key])
            } else {
              pageProp[key] = value[key]
            }
          }
        }
      },
      // 往页面中添加组件
      [types.ADD_COMP_TO_PAGES] (state, compData) {
        let curPage = state.list
          .find((_x) => _x.id === state.curPageId)
        if (curPage) {
          curPage.comps.push(compData)
        }
      },
      // 往页面中删除组件
      [types.REMOVE_COMP_TO_PAGES] (state, compId) {
        let curPage = state.list.find((_x) => _x.id === state.curPageId)
        if (curPage) {
          let comps = curPage.comps
          let index = curPage.comps.findIndex(_x => _x.id === compId)
          if (index > -1) {
            comps.splice(index, 1)
            if (comps && comps.length > 0) {
              curPage.comps = comps
            } else {
              curPage.comps = []
            }
          }
        }
      }
    }
    const actions = {
      // 添加新的页面
      addNewPages ({ commit }) {
        const page = getNewPage()
        if (page) {
          commit(types.ADD_PAGE, page)
        }
        return page.id
      },
      // 拷贝指定页面
      copyPage ({ commit }, pageId) {
        let id = getNewPageId()
        if (id) {
          commit(types.COPY_PAGE, {
            prePageId: pageId,
            pageId: id
          })
        }
        return id
      },
      // 删除指定页面
      removePage ({ commit }, pageId) {
        commit(types.REMOVE_PAGE, pageId)
        return pageId
      },
      // 从页面中删除组件
      removeComponentToPage ({ commit }, compId) {
        commit(types.REMOVE_COMP_TO_PAGES, compId)
        return compId
      },
      /**
       * 初始化Editor 编辑器
       * 同时新建一个页面
       **/
      initPageEditor ({ dispatch, commit }) {
        dispatch('addNewPages')
          .then((id) => {
            commit(types.TOGGLE_PAGE, id)
          })
      },
      /**
       * 切换页面
       * @param commit
       * @param pageId
       */
      togglePage ({ commit }, pageId) {
        commit(types.TOGGLE_PAGE, pageId)
      },
      /**
       * 修改当前页面
       * @param commit
       * @param type
       * @param value
       * @param pageId
       */
      editCurPage ({ commit }, { type, value, pageId }) {
        commit([types.EDIT_PAGE], { type, value, pageId })
      },
      /**
       * 更新页面中的某个组件中的css属性
       * @param commit
       * @param getters
       * @param state
       * @param type
       * @param key
       */
      editCompOfCurPage ({ commit, getters, state }, { type, key }) {
        // 首先找到当前页面
        let _page = state.list.find(_x => _x.id === state.curPageId)
        if (_page) {
          // 找当前页面中的组件
          let _comp = _page.comps.find(_x => _x.id === window.$globalHub.$store.state.components.curCompId)
          let _obj = _comp[type]
          if (typeof _obj === 'object') {
            if (typeof _obj[key] === 'object') {
              Object.assign(_obj[key], window.$globalHub.$store.getters.curComp[type][key])
              console.log(' state.list:', state.list)
            } else {
              _obj[key] = window.$globalHub.$store.getters.curComp[type][key]
              console.log(' state.list:', state.list)
            }
          }
        }
      }
    }
    export default {
      state,
      getters,
      actions,
      mutations
    }
    

    这个里面主要是数据的流向,真的简化了很多dom的思想不需要dom思想

    //srcpageseditorcomponentsTheTemplateList.vue
    <template>
      <!--region 页面列表-->
      <div class="the-page-list">
        <div class="title">页面管理</div>
        <div class="action-group">
          <div class="item" @click="handleAddPage">
            <span class="axon-icon" v-html="'&#xe635;'"></span>添加
          </div>
          <div class="item" @click="handleCopyPage">
            <span class="axon-icon" v-html="'&#xe62f;'"></span>复制
          </div>
          <div class="item" @click="handleRemovePage">
            <span class="axon-icon" v-html="'&#xe63d;'"></span>删除
          </div>
        </div>
        <ul class="list">
          <template v-for="(page, key) in pages">
            <li :key="key"
                :id="page.id"
                :class="curPageId === page.id ? 'page active' : 'page'" @click="handleTogglePage(page.id)">
              <span class="key">
                <em>{{ key + 1 }}</em>
              </span>
              <p class="name">第 {{ key + 1 }} 页</p>
              <span class="more-action axon-icon" v-html="'&#xe61c;'">
              </span>
            </li>
          </template>
        </ul>
      </div>
      <!--endregion-->
    </template>
    
    <script>
    export default {
      components: {},
      data () {
        return {}
      },
      created () {
      },
      mounted () {
      },
      computed: {
        // 取页面列表
        pages () {
          try {
            return this.$store.getters.pages
          } catch (e) {
            this.$notify.error({
              title: '错误',
              message: '初始化页面失败,请刷新后重试!'
            })
          }
        },
        // 当前页面ID
        curPageId () {
          try {
            return this.$store.getters.curPageId
          } catch (e) {
            this.$notify.error({
              title: '错误',
              message: '初始化页面失败,请刷新后重试!'
            })
          }
        }
      },
      methods: {
        /**
         * 创建新页面
         */
        handleAddPage () {
          this.$store.dispatch('addNewPages', this.curPageId).then((id) => {
            this.handleTogglePage(id)
          })
        },
        /**
         * 拷贝当前页面
         */
        handleCopyPage () {
          this.$store.dispatch('copyPage', this.curPageId).then((id) => {
            this.handleTogglePage(id)
          })
        },
        /**
         * 删除当前页面
         */
        handleRemovePage () {
          this.$store.dispatch('removePage', this.curPageId)
        },
        /**
         * 切换页面
         * @param pageId
         */
        handleTogglePage (pageId) {
          this.$store.dispatch('togglePage', pageId)
        }
      }
    }
    </script>
    
    <style lang="scss">
    @import "../../../styles/variables";
    
    .the-page-list {
      background-color: #fafafa;
      border-left: 1px solid $border-color-1;
      box-sizing: border-box;
      flex: 0 0 $setting-width;
      // 页头
      .title {
        height: 40px;
        line-height: 40px;
        text-align: center;
        background-color: #e0e0e0;
        font-weight: 600;
      }
      // 列表
      .list {
        .page {
          border-top: 1px solid #e6ebed;
          height: 70px;
          line-height: 70px;
          color: #666;
          cursor: pointer;
          font-size: 12px;
          position: relative;
          border-bottom: 1px solid #e6ebed;
          display: flex;
          .key, .more-action {
            flex: 0 0 44px;
            text-align: center;
            display: block;
            em {
              display: inline-block;
               24px;
              height: 24px;
              line-height: 24px;
              text-align: center;
              border-radius: 12px;
              background-color: #ccc;
              color: #fff;
              font-weight: 400;
              font-style: normal;
            }
          }
          .more-action {
            font-size: 13px;
          }
          .name {
            font-size: 14px;
            flex: 100%;
          }
          &.active {
            background-color: #eee;
            .key {
              em {
                background-color: #1593ff;
              }
            }
            .name {
              color: #000;
            }
          }
        }
      }
      .action-group {
        display: flex;
        height: 36px;
        line-height: 36px;
        .item {
          flex: 0 0 33.33%;
          text-align: center;
          font-size: 12px;
          &:not(:last-child) {
            border-right: 1px solid #eee;
          }
          span {
            font-size: 14px;
            padding-right: 4px;
          }
        }
      }
    }
    </style>
    
    //srcpageseditorcomponentsTheEditorContainer.vue
    <template>
      <div class="the-editor-container">
        <div class="container-wrapper">
          <!--region 页面列表-->
          <div class="page-list">
            <div class="page-wrapper">
              <!--region 背景-->
              <div class="wrapper-background"
                   :style="{
                       backgroundColor: curPage.css.bgc,
                       backgroundImage: 'url(' + curPage.css.bgi + ')'
                     }"></div>
              <!--endregion-->
              <!--region 组件-->
              <div class="component-wrapper">
                <template v-for="comp in curPage.comps">
                  <vue-drr
                    :ref="'comp_' + comp.id"
                    :id="'comp_' + comp.id"
                    :key="comp.id"
                    :w="parseInt(comp.css.w)"
                    :h="parseInt(comp.css.h)"
                    :x="parseInt(comp.css.l)"
                    :y="parseInt(comp.css.t)"
                    :minw="20"
                    :minh="30"
                    :grid="grid"
                    :parent="true"
                    :angle="parseInt(comp.css.rotate)"
                    @activated="toggleComp(comp.id, comp.name)"
                    @resizestop="handleResizing"
                    @dragstop="handleDragging"
                    @rotatestop="handleRotating">
                    <comp-list
                      @click="handleClick(comp)"
                      @dblclick="handleDbClick"
                      @contextmenu="handleContextmenu(comp)"
                      :id="comp.id"
                      class="comp"
                      :style="comp.css | formatStyle('ft', 'lh')"
                      :type="comp.name"></comp-list>
                  </vue-drr>
                </template>
              </div>
              <!--endregion-->
            </div>
          </div>
          <!--endregion-->
        </div>
      </div>
    </template>
    
    <script>
    import vueDrr from 'vue-drr'
    import { throttle } from '../../../assets/utils/util.js'
    import BaseComps from '../../../components/iTemplate/index.js'
    
    const BASE_COMP_NAME = 'Base'
    const BASE_COMP_CONFIG = 'Config'
    export default {
      components: {
        vueDrr,
        compList: {
          props: {
            id: {
              type: [String, Number],
              required: true
            }, // 组件ID
            type: {
              type: String,
              required: true
            } // 组件类型名称
          },
          render (h) {
            let _compFlagName = `${BASE_COMP_NAME}${this.type}`
            let _module = BaseComps[_compFlagName]
            return h(_module, {
              props: {
                id: this.id
              },
              nativeOn: {
                click: throttle(this.handleClick, 1000),
                dblclick: this.handleDbClick,
                // contextmenu: throttle(this.handleContextmenu, 1000)
                contextmenu: evt => {
                  // 阻止浏览器默认点击行为
                  evt.preventDefault()
                  throttle(this.handleContextmenu(this.comp), 1000)
                }
              }
            })
          },
          methods: {
            // 单击:左击
            handleClick (comp) {
              this.$emit('click', comp)
            },
            // 双击
            handleDbClick () {
              this.$emit('dblclick', this.type)
            },
            // 单击:右击
            handleContextmenu (comp) {
              this.$emit('contextmenu', comp)
            }
          }
        }
      },
      data () {
        return {
          grid: [1, 1] // 组件拖动的网格
        }
      },
      created () {
      },
      mounted () {
      },
      computed: {
        // 取当前页面
        curPage () {
          try {
            return this.$store.getters.curPage
          } catch (e) {
            this.$notify.error({
              title: '错误',
              message: '初始化页面失败,请刷新后重试!'
            })
          }
        },
        // 取当前正在编辑的组件
        curCompId () {
          return this.$store.state.components.curCompId
        },
        // 取当前组件
        curComp () {
          return this.$store.getters.currComp
        }
      },
      methods: {
        // 切换组件
        toggleComp (id, name) {
          // 打开组件属性设置面板
          this.$store.dispatch('openPropsPanel', {
            id: id,
            name: name + BASE_COMP_CONFIG
          })
          this.$store.dispatch('toggleComp', id)
        },
        // 左击:点击组件
        handleClick (comp) {
          // 关闭图层面板
          this.$store.dispatch('closeLayerPanel')
          // 打开组件属性设置面板
          this.$store.dispatch('openPropsPanel', {
            id: comp.id,
            name: comp.name + BASE_COMP_CONFIG
          })
        },
        /**
         * 双击组件
         * @param type 组件类型
         */
        handleDbClick (type) {
          console.log('双击666')
        },
        handleContextmenu (comp) {
          console.log('右击666')
          this.$store.dispatch('closePropsPanel')
          // 打开图层设置面板
          this.$store.dispatch('openLayerPanel', {
            id: comp.id,
            name: comp.name + 'layer' + BASE_COMP_CONFIG
          })
        },
        // 组件拉伸时触发
        handleResizing (x, y, w, h) {
          this.updateCompStyle({ l: x, t: y, w, h })
        },
        // 组件拖动时触发
        handleDragging (x, y) {
          this.updateCompStyle({ l: x, t: y })
        },
        // 组件旋转时触发
        handleRotating (angle) {
          this.updateCompStyle({ rotate: angle })
        },
        // 同步用户修改
        updateCompStyle (value) {
          this.$store.dispatch('editComp', {
            type: 'css',
            value: value
          })
        }
      }
    }
    </script>
    
    <style lang="scss">
    .the-editor-container {
      margin: 0 auto;
      border: 2px solid #ddd;
      border-radius: 2px;
       52.7rem;
      min-height: 82.6rem;
      flex: 0 0 1;
      height: 100%;
      background-color: #fff;
      position: relative;
      .container-wrapper {
         100%;
        height: 100%;
        .page-list {
           100%;
          height: 100%;
          .page-wrapper {
             100%;
            height: 100%;
            .wrapper-background {
              position: absolute;
              top: 0;
              left: 0;
               100%;
              height: 100%;
              background-repeat: no-repeat;
              background-size: cover;
              background-origin: content-box;
              background-position: 50% 50%;
            }
            .component-wrapper {
               100%;
              height: 100%;
              position: relative;
            }
          }
        }
      }
    }
    </style>
    
    //srcpageseditorcomponentsTheCompPropsConfig.vue
    <!--region 组件属性设置面板-->
    <template>
      <div ref="propsPanelConfig" class="the-comp-props-config-page" v-if="propsPanel && propsPanel.show"
           :style="{ height: panelHeight }">
        <div :class="isMouseDown ? 'header pointer-events': 'header'">
          <div class="title" ref="header" @mousedown="handleDragMouseDown">组件设置</div>
          <div class="close">
            <span class="axon-icon" v-html="'&#xe622;'" @click="closePropsPanel()"></span>
          </div>
        </div>
        <div class="props-panel-config">
          <props-panel :name="propsPanel.name"></props-panel>
        </div>
      </div>
    </template>
    <!--endregion-->
    <script>
    import CompPropsConfig from '../../../components/iTemplate/config.js'
    import { throttle } from '../../../assets/utils/util.js'
    
    export default {
      components: {
        propsPanel: {
          props: {
            name: {
              type: String,
              required: true
            }
          },
          render (h) {
            let _module = CompPropsConfig[this.name]
            return h(_module, {
              props: {
                name: {
                  type: String
                }
              }
            })
          }
        }
      },
      data () {
        return {
          isMouseDown: false, // 鼠标是否按住可拖放区域
          dragObj: {
            initX: 0,
            indexY: 0,
             260,
            height: 48
          } // 拖动对象位置
        }
      },
      created () {
      },
      mounted () {
        document.addEventListener('mousemove', (e) => { this.throttleMove(e) })
        document.addEventListener('mouseup', (e) => { this.handleDragMouseUp(e) })
      },
      computed: {
        // 根据父级组件高度设定属性面板高度
        panelHeight () {
          // 取父级组件中的父级标签属性
          let _parentHeight = 0
          try {
            _parentHeight = this.$parent.$refs.centerBox.offsetHeight
            return `${_parentHeight - 100}px`
          } catch (e) {
            return 0
          }
        },
        // 取面板信息
        propsPanel () {
          return this.$store.getters.propPanel
        }
      },
      watch: {},
      methods: {
        // 关闭属性面板
        closePropsPanel () {
          console.log(' close:')
          this.$store.dispatch('closePropsPanel')
        },
        // 拖动:当鼠标点下的时候,给要拖动的元素附上初始值
        handleDragMouseDown (e) {
          this.isMouseDown = true
          this.dragObj.initX = e.offsetX
          this.dragObj.initY = e.offsetY
          this.dragObj.width = this.$refs.propsPanelConfig.offsetWidth
          this.dragObj.height = this.$refs.propsPanelConfig.offsetHeight - this.$refs.header.offsetHeight
        },
        // 鼠标移动 --- 节流
        throttleMove (e) {
          return throttle(this.handleDragMouseMove(e), 500)
        },
        // 拖动过程中,需要实时监听位置变化
        handleDragMouseMove (e) {
          if (this.isMouseDown) {
            // 移动外层需要移动的div
            let _cx = e.clientX - this.dragObj.initX
            let _cy = e.clientY - this.dragObj.indexY
            // 限制panel不能超出浏览器边界
            _cx = _cx >= 0 ? _cx : 0
            _cy = _cy >= 0 ? _cy : 0
            if (window.innerWidth - e.clientX + this.dragObj.initX < this.dragObj.width + 16 || window.innerWidth - this.dragObj.width < _cx) {
              _cx = window.innerWidth - this.dragObj.width
            }
            if (e.clientY > window.innerHeight - this.dragObj.height - this.$refs.header.offsetHeight + this.dragObj.initY) {
              _cy = window.innerHeight - this.$refs.header.offsetHeight - this.dragObj.height
            }
            this.$refs.propsPanelConfig.style.left = _cx + 'px'
            this.$refs.propsPanelConfig.style.top = _cy + 'px'
          }
        },
        // 鼠标移除,取消监听
        handleDragMouseUp (e) {
          if (e.clientY > window.innerWidth || e.clientY < 0 || e.clientX < 0 || e.clientX > window.innerHeight) {
            this.isMouseDown = false
          }
          this.isMouseDown = false
        }
      }
    }
    </script>
    
    <style lang="scss">
    .the-comp-props-config-page {
      background-color: #fafafa;
      position: fixed;
      left: 0;
      top: 0;
       260px;
      overflow: hidden;
      box-shadow: 0 0 16px rgba(0, 0, 0, 0.16);
      z-index: 1000;
      height: 100%;
      user-select: none;
      .header {
        background-color: #fff;
        height: 48px;
        line-height: 48px;
         100%;
        padding: 0 16px;
        user-select: none;
        display: flex;
        &.pointer-events {
          pointer-events: none;
        }
        .title {
          flex: 0 0 50%;
          font-size: 13px;
          cursor: move;
        }
        .close {
          flex: 0 0 50%;
          text-align: right;
          z-index: 9999;
          span {
            cursor: pointer;
            font-size: 14px;
            color: #a3afb7;
          }
        }
      }
      .props-panel-config {
        height: calc(#{"100% - 54px"});
        .text-comp-config-page {
          height: 100%;
          .el-tabs {
            height: 100%;
            overflow: hidden;
            .el-tabs__content {
              height: calc(#{"100% - 50px"});
              overflow-y: auto;
              .el-tab-pane {
                height: 100%;
              }
            }
          }
        }
      }
    }
    </style>
    
    //srcpageseditorcomponentsTheCompLayerManager.vue
    <!--region 图层设置面板-->
    <template>
      <div ref="layerManagerPanel" class="the-comp-layer-manager-page" v-if="layerPanel && layerPanel.show">
        <div :class="isMouseDown ? 'header pointer-events': 'header'">
          <div class="title" ref="header" @mousedown="handleDragMouseDown">图层设置</div>
          <div class="close">
            <span class="axon-icon" v-html="'&#xe622;'" @click="closeLareyPanel()"></span>
          </div>
        </div>
        <div class="layer-panel-list">
          <ul>
            <li @click="handRemove">删除组件</li>
            <li @click="handleCopyComp">拷贝组件</li>
            <!---图层显示依据数组的降序排练--关于图层数据的操作要反过来-->
            <li @click="handleSwapItem('down')">上移一层</li>
            <li @click="handleSwapItem('up')">下移一层</li>
            <li @click="handleSwapItem('bottom')">置顶</li>
            <li @click="handleSwapItem('top')">置底</li>
          </ul>
        </div>
      </div>
    </template>
    <!--endregion-->
    <script>
    import { throttle } from '../../../assets/utils/util.js'
    import SwapArrayItem from '../../../assets/utils/swapArrayItem.js'
    
    export default {
      components: {},
      data () {
        return {
          isMouseDown: false, // 鼠标是否按住可拖放区域
          dragObj: {
            initX: 0,
            indexY: 0,
             160,
            height: 48
          } // 拖动对象位置
        }
      },
      created () {
      },
      mounted () {
        document.addEventListener('mousemove', (e) => { this.throttleMove(e) })
        document.addEventListener('mouseup', (e) => { this.handleDragMouseUp(e) })
      },
      computed: {
        // 取图层信息
        layerPanel () {
          return this.$store.getters.layerPanel
        },
        // 取当前页面ID
        curPageId () {
          return this.$store.getters.curPage.id
        },
        // 取当前页面中所有组件
        curPageComps () {
          return this.$store.getters.curPage.comps
        },
        // 取当前正在编辑的组件
        curCompId () {
          return this.$store.state.components.curCompId
        }
      },
      watch: {},
      methods: {
        // 关闭图层面板
        closeLareyPanel () {
          this.$store.dispatch('closeLayerPanel')
        },
        // 删除当前组件
        handRemove () {
          let compId = this.curCompId
          // 删除当前组件
          this.$store.dispatch('removeComp', compId)
          // 将组件从当前页面中移除
          this.$store.dispatch('removeComponentToPage', compId)
          // 关闭图层面板
          this.$store.dispatch('closeLayerPanel')
        },
        // 拷贝当前组件
        handleCopyComp () {
          this.$store.dispatch('copyComp', this.curCompId)
        },
        // 交换数组元素
        handleSwapItem (operateName) {
          // 获取当前数组下标
          let index = this.accpetIndex(this.curPageComps, this.curCompId)
          SwapArrayItem.swapByOperate(this.curPageComps, index, operateName)
        },
        /**
         * 获取下标
         * @param ary
         * @param id
         * @returns {number}
         */
        accpetIndex (ary, id) {
          var index = -1
          for (let i = 0; i < ary.length; i++) {
            if (ary[i] && ary[i].id === id) {
              index = i
            }
          }
          return index
        },
        // 拖动:当鼠标点下的时候,给要拖动的元素附上初始值
        handleDragMouseDown (e) {
          this.isMouseDown = true
          this.dragObj.initX = e.offsetX
          this.dragObj.initY = e.offsetY
          this.dragObj.width = this.$refs.layerManagerPanel.offsetWidth
          this.dragObj.height = this.$refs.layerManagerPanel.offsetHeight - this.$refs.header.offsetHeight
        },
        // 鼠标移动 --- 节流
        throttleMove (e) {
          return throttle(this.handleDragMouseMove(e), 500)
        },
        // 拖动过程中,需要实时监听位置变化
        handleDragMouseMove (e) {
          if (this.isMouseDown) {
            // 移动外层需要移动的div
            let _cx = e.clientX - this.dragObj.initX
            let _cy = e.clientY - this.dragObj.indexY
            // 限制panel不能超出浏览器边界
            _cx = _cx >= 0 ? _cx : 0
            _cy = _cy >= 0 ? _cy : 0
            if (window.innerWidth - e.clientX + this.dragObj.initX < this.dragObj.width + 16 || window.innerWidth - this.dragObj.width < _cx) {
              _cx = window.innerWidth - this.dragObj.width
            }
            if (e.clientY > window.innerHeight - this.dragObj.height - this.$refs.header.offsetHeight + this.dragObj.initY) {
              _cy = window.innerHeight - this.$refs.header.offsetHeight - this.dragObj.height
            }
            this.$refs.layerManagerPanel.style.left = _cx + 'px'
            this.$refs.layerManagerPanel.style.top = _cy + 'px'
          }
        },
        // 鼠标移除,取消监听
        handleDragMouseUp (e) {
          if (e.clientY > window.innerWidth || e.clientY < 0 || e.clientX < 0 || e.clientX > window.innerHeight) {
            this.isMouseDown = false
          }
          this.isMouseDown = false
        }
      }
    }
    </script>
    
    <style lang="scss">
    .the-comp-layer-manager-page {
      background-color: #fafafa;
      position: fixed;
      left: 0;
      top: 0;
       160px;
      overflow: hidden;
      box-shadow: 0 0 16px rgba(0, 0, 0, 0.16);
      z-index: 1000;
      height: 286px;
      user-select: none;
      box-sizing: content-box;
      .header {
        background-color: #fff;
        height: 48px;
        line-height: 48px;
         100%;
        padding: 0 16px;
        user-select: none;
        display: flex;
        &.pointer-events {
          pointer-events: none;
        }
        .title {
          flex: 0 0 50%;
          font-size: 13px;
          cursor: move;
        }
        .close {
          flex: 0 0 30%;
          text-align: right;
          z-index: 9999;
          span {
            cursor: pointer;
            font-size: 14px;
            color: #a3afb7;
          }
        }
      }
      .layer-panel-list {
        height: calc(#{"100% - 54px"});
        font-size: 13px;
        li {
          height: 40px;
          line-height: 40px;
          padding: 0 16px;
          box-sizing: content-box;
          border-top: 1px solid #e6ebed;
          &:hover {
            color: #ffffff;
            background-color: #1593ff;
          }
        }
        li:nth-child(1) {
          &:hover {
            color: #ffffff;
            background-color: #ff2a6a;
          }
        }
      }
    }
    </style>
    

    //srcpagesiconsIndex.vue
    <template>
      <div class="icon-page">
        <el-row :gutter="20">
          <template v-for="(item, index) in icons">
            <el-col :key="index" :span="6" class="item">
              <span class="axon-icon" v-html="item.icon"></span>
              <p><span style="color: #606266;height: 1em;font-size: 12px;">{{item.icon}}</span></p>
            </el-col>
          </template>
        </el-row>
      </div>
    </template>
    <script>
    export default {
      components: {},
      data () {
        return {
          icons: [{ name: '叉号', icon: '&#xe603;' },
            { name: '原型叉号', icon: '&#xe614;' },
            { name: '栏目', icon: '&#xe61f;' },
            { name: '箭头', icon: '&#xe608;' },
            { name: '隐藏密码', icon: '&#xe620;' },
            { name: '显示密码', icon: '&#xe60c;' },
            { name: '用户', icon: '&#xe604;' },
            { name: '用户', icon: '&#xe7e9;' },
            { name: '发货', icon: '&#xe63e;' },
            { name: '订单', icon: '&#xe72d;' },
            { name: '用户', icon: '&#xe6a3;' },
            { name: '金额', icon: '&#xe736;' },
            { name: '主页', icon: '&#xe63d;' },
            { name: '错误', icon: '&#xe625;' },
            { name: '错误', icon: '&#xe610;' },
            { name: '栏目', icon: '&#xe626;' },
            { name: '栏目', icon: '&#xe60a;' },
            { name: '验证码', icon: '&#xe61b;' },
            { name: '手机', icon: '&#xe60d;' }
          ]
        }
      },
      mounted () {},
      methods: {}
    }
    </script>
    
    <style lang="less" rel="stylesheet/less" scoped>
    .icon-page {
      background-color: #fff;
      padding: 20px 40px;
      .item {
        text-align: center;
        height: 120px;
        .axon-icon {
          font-size: 24px;
          color: #606266;
          margin-bottom: 15px;
          display: block;
        }
      }
    }
    </style>
    

    //srcpages	ableIndex.vue
    
    <template>
      <div class="table-page">
        <!--region table 表格-->
        <i-table :list="list" :total="total" :loading="loading" :otherHeight="otherHeight"
                 @handleSizeChange="handleSizeChange"
                 @handleIndexChange="handleIndexChange" @handleSelectionChange="handleSelectionChange" :options="options"
                 :pagination="pagination" :columns="columns" :operates="operates">
        </i-table>
        <!--endregion-->
      </div>
    </template>
    <script type="text/jsx">
    import iTable from '../../components/iTable/Index'
    import BLL from './Index'
    import { mapGetters } from 'vuex'
    
    export default {
      components: { iTable },
      data () {
        return {
          total: 0,
          list: [],
          otherHeight: 208,
          filter: {
            date: '',
            phone: ''
          },
          collapseTitle: '筛选条件:',
          columns: [
            {
              prop: 'id',
              label: '编号',
              align: 'center',
               60
            },
            {
              prop: 'title',
              label: '标题',
              align: 'center',
               400,
              formatter: (row, column, cellValue) => {
                return `<span style="white-space: nowrap;color: dodgerblue;">${row.title}</span>`
              }
            },
            {
              prop: 'state',
              label: '状态',
              align: 'center',
               '160',
              render: (row, column) => {
                return row.state === 0 ? <el-tag type='success'> 上架 </el-tag> : row.state === 1
                  ? <el-tag type="info">下架</el-tag> : <el-tag type='danger'>审核中</el-tag>
              }
            }, {
              prop: 'author',
              label: '作者',
              align: 'center',
               120
            },
            {
              prop: 'phone',
              label: '联系方式',
              align: 'center',
               160
            },
            {
              prop: 'email',
              label: '邮箱',
              align: 'center',
               240
            },
            {
              prop: 'createDate',
              label: '发布时间',
              align: 'center'
            }
          ], // 需要展示的列
          operates: {
             200,
            fixed: 'right',
            list: [
              {
                label: '编辑',
                type: 'warning',
                show: true,
                icon: 'el-icon-edit',
                plain: false,
                disabled: false,
                method: (index, row) => {
                  this.handleEdit(index, row)
                }
              },
              {
                label: '删除',
                type: 'danger',
                icon: 'el-icon-delete',
                show: true,
                plain: false,
                disabled: false,
                method: (index, row) => {
                  this.handleDel(index, row)
                }
              }
            ]
          }, // 操作按钮组
          pagination: {
            pageIndex: 1,
            pageSize: 20
          }, // 分页参数
          options: {
            stripe: true, // 是否为斑马纹 table
            highlightCurrentRow: true, // 是否支持当前行高亮显示
            mutiSelect: true // 是否支持列表项选中功能
          } // table 的参数
        }
      },
      created () {
        this.BLL = new BLL(this)
      },
      mounted () {
        this.BLL.getList()
      },
      computed: {
        ...mapGetters(['button']),
        loading () {
          return this.$store.getters.btnLoading.str && this.$store.getters.btnLoading.id
        }
      },
      methods: {
        // 切换每页显示的数量
        handleSizeChange (pagination) {
          this.pagination = pagination
          this.BLL.getList()
        },
        // 切换页码
        handleIndexChange (pagination) {
          this.pagination = pagination
          this.BLL.getList()
        },
        // 选中行
        handleSelectionChange (val) {
          console.log('val:', val)
        },
        // 编辑
        handleEdit (index, row) {
          console.log(' index:', index)
          console.log(' row:', row)
          this.BLL.getList()
        },
        // 删除
        handleDel (index, row) {
          console.log(' index:', index)
          console.log(' row:', row)
        },
        // 刷新
        reload (form) {
          console.log('into reload')
          this.$refs['filter'].resetFields()
          this.filter = { date: '', phone: '' }
          this.BLL.getList()
        },
        // 筛选数据
        filterData () {
          this.BLL.getList()
        }
      }
    }
    </script>
    
    <style lang="scss">
    @import "./Index";
    </style>
    

    //srcpagesmenusIndex.vue
    <!--菜单页-->
    <template>
      <div class="menus-page">
        <div slot="header" class="clearfix header">
        </div>
        <i-table :list="menusList" :otherHeight="otherHeight" :columns="columns" :operates="operates"></i-table>
      </div>
    </template>
    
    <script type="text/jsx">
    import iTable from '../../components/iTable/Index'
    import { mapState } from 'vuex'
    
    export default {
      components: { iTable },
      data () {
        return {
          menusList: [],
          columns: [
            {
              prop: 'name',
              label: '名称',
              align: 'left',
               200,
              className: 'first-level',
              formatter: function (row, column) {
                let marginLeft = parseInt(row.level) * 20
                if (row.icon) {
                  let icon = `<span class="axon-icon" style="margin-right:4px;margin-left:${marginLeft}px;font-size:16px;">${row.icon}</span>`
                  return `${icon}<span>${row.name}</span>`
                } else {
                  return `<span style="margin-left:${marginLeft}px;">${row.name}</span>`
                }
              }
            }, {
              prop: 'link',
              label: '地址',
              align: 'center'
            }, {
              prop: 'level',
              label: '排序',
              align: 'center',
               100
            }, {
              prop: 'state',
              label: '状态',
              align: 'center',
               100,
              render: function (row, column) {
                return row.state === 1 ? <el-tag>启用</el-tag> : <el-tag type="info">禁用</el-tag>
              }
            }
          ],
          operates: {
             200,
            fixed: 'right',
            list: [
              {
                label: '编辑',
                type: 'warning',
                show: true,
                icon: 'el-icon-edit',
                plain: true,
                disabled: false,
                method: (index, row) => {
                  this.handleEdit(index, row)
                }
              },
              {
                label: '删除',
                type: 'danger',
                show: true,
                icon: 'el-icon-delete',
                plain: true,
                disabled: false,
                method: (index, row) => {
                  this.handelDel(index, row)
                }
              }
            ]
          },
          otherHeight: 216
        }
      },
      mounted () {
        this.initMenus()
      },
      computed: {
        ...mapState([
          'menus'
        ]),
        loading () {
          return this.$store.getters.btnLoading.str && this.$store.getters.btnLoading.id
        }
      },
      methods: {
        initMenus () {
          if (this.menus && this.menus.sidebar.length > 0) {
            let level = 1
            this.formatMenusList(this.menus.sidebar, level)
          }
        },
        // 递归menus的各级菜单
        formatMenusList (list, level) {
          for (let i = 0; i < list.length; i++) {
            this.menusList.push({ ...list[i], level })
            if (list[i].children) {
              level++
              this.formatMenusList(list[i].children, level)
            }
          }
        },
        // 编辑菜单
        handleEdit (index, row) {
          console.log(row)
        },
        // 删除菜单
        handelDel (index, row) {
          console.log(row)
        },
        add () {
          console.log('into add()')
        },
        // 转换 string 方法名为 Function 对象
        callback (fn) {
          fn = `this.${fn}()`
          return eval(fn) // eslint-disable-line
          // this.__callback(fn, this)
        }
      }
    }
    </script>
    
    <style lang="scss">
    .menus-page {
      background-color: #fff;
      .header {
        padding: 10px 10px;
      }
    }
    </style>
    

    <!--角色管理页面-->
    <template>
      <div class="role-page">
        <i-table></i-table>
      </div>
    </template>
    
    <script>
    import iTable from '../../components/iTable/Index'
    export default {
      components: { iTable },
      data () {
        return {}
      },
      mounted () { },
      computed: {},
      methods: {}
    }
    </script>
    
    <style lang="scss" scoped>
    .role-page {
      background-color: #fff;
    }
    </style>
    

    <template>
      <div class="MyContain">
    
      </div>
    </template>
    
    <script>
    export default {
      components: {},
      data () {
        return {}
      },
      mounted () { },
      computed: {},
      methods: {}
    }
    </script>
    
    <style lang="scss" scoped>
    .MyContain {
      background-color: #fff;
    }
    </style>
    

    <template>
      <div class="MyContain">
    
      </div>
    </template>
    
    <script>
    export default {
      components: {},
      data () {
        return {}
      },
      mounted () { },
      computed: {},
      methods: {}
    }
    </script>
    
    <style lang="scss" scoped>
    .MyContain {
      background-color: #fff;
    }
    </style>
    
    
  • 相关阅读:
    随机id
    vue关于父组件调用子组件的方法
    ES6——块级作用域
    在vue中引用superMap
    MSSQL备份脚本
    Ubuntu 使用命令导出数据库
    CSS before 中文乱码
    CentOS7 安装linux 网络不联通问题
    SVN update failed问题解决
    JQ实现树形菜单点击高亮
  • 原文地址:https://www.cnblogs.com/smart-girl/p/11376713.html
Copyright © 2020-2023  润新知