Explorar o código

feat: 生成入口文件代码

王家程 %!s(int64=3) %!d(string=hai) anos
pai
achega
328b5d81f9

+ 49 - 74
README.md

@@ -1,74 +1,49 @@
-![image](https://ae01.alicdn.com/kf/U51bfb661aba945b48a4c71774421d414C.gif)
-## 简介
-Element UI表单设计及代码生成器,可将生成的代码直接运行在基于Element的vue项目中;也可导出JSON表单,使用配套的解析器将JSON解析成真实的表单。 
-- 【[国内预览地址](https://mrhj.gitee.io/form-generator)】 【[国际预览地址](https://jakhuang.github.io/form-generator)】
-- 【[github仓库](https://github.com/JakHuang/form-generator)】 【 [码云仓库](https://gitee.com/mrhj/form-generator)】
-- [配套vscode插件](https://github.com/JakHuang/form-generator-plugin)
-- [配套JSON解析器](https://github.com/JakHuang/form-generator/blob/dev/src/components/parser/example/Index.vue)
-
-## 友情链接
-
-- **vue-admin-beautiful** —— [企业级、通用型中后台前端解决方案(基于vue/cli 4 最新版,同时支持电脑,手机,平板)](https://github.com/chuzhixin/vue-admin-beautiful)
-- **vue-admin-beautiful** —— [在线演示](http://beautiful.panm.cn/vue-admin-beautiful/#/index)
-- **pl-table** —— [完美解决 element 万级表格数据渲染卡顿问题](https://github.com/livelyPeng/pl-table)
-
-## 文档
-- [el-dialog的封装与调用](https://github.com/JakHuang/form-generator/wiki/el-dialog%E7%9A%84%E5%B0%81%E8%A3%85%E4%B8%8E%E8%B0%83%E7%94%A8)
-- [项目主要结构分析](https://github.com/JakHuang/form-generator/wiki/%E9%A1%B9%E7%9B%AE%E4%B8%BB%E8%A6%81%E7%BB%93%E6%9E%84%E5%88%86%E6%9E%90)
-- [【常见问题】如何以npm的方式集成monaco编辑器](https://github.com/JakHuang/monaco-vue-demo)
-- 系列教程:
-[《表单设计器 · 开发教程》](https://github.com/JakHuang/form-generator/issues/30)
-[《表单解析器 · 开发教程》](https://github.com/JakHuang/form-generator/issues/32)
-[《vue代码生成器 · 开发教程》](https://github.com/JakHuang/form-generator/issues/31)
-[《vue代码预览器 · 开发教程》](https://github.com/JakHuang/form-generator/issues/33)
-- [JSON表单参数对照表](https://github.com/JakHuang/form-generator/issues/46)
-
-## JSON解析器
-将保存在数据库中的JSON表单,解析成真实的表单  
-[查看在线示例](https://mrhj.gitee.io/form-generator/#/parser) 
-```
-// 安装
-npm i form-gen-parser
-```
-[更多信息](https://github.com/JakHuang/form-generator/tree/dev/src/components/parser) 
-
-## vscode插件
-帮助使用element UI的开发者完成基本的表单代码搭建任务,减少重复的劳动。  
-vscode-plugin分支配套插件为:[form-generator-plugin](https://github.com/JakHuang/form-generator-plugin);  
-使用插件可右键打开设计器,直接将代码保存到工程中。  
-安装插件请在vscode中搜索:
->jakHuang   
-或  
-Form Generator Plugin
-## 运行
-- 确保已经安装node.js 10+
-- 首次下载项目后,安装项目依赖:
-```
-yarn
-```
-或
-```
-npm install
-```
-- 本地开发
-```
-npm run dev
-```
-- 构建
-```
-npm run build
-```
-## 交流
-- QQ群 976154366【已满】   1137173566【已满】   514953560【3群】
-
-
-## PR鸣谢
-- [IWANABETHATGUY](https://github.com/IWANABETHATGUY)
-
-## 捐赠
-#### 如果您觉得本项目对您有帮助,可以请作者喝一杯咖啡, 让开源走的更远,感谢支持。  
-#### 也请您在捐赠后,加下qq群:976154366,私聊群主留个赞助链接。
-<img src="https://ae01.alicdn.com/kf/H50d81220a202490f961878f42ed1a636i.jpg">
-
-## 开源协议
-[MIT](https://opensource.org/licenses/MIT)
+# iview 组件替换
+
+## 人事管理-1
+组织架构 薪酬管理 入转调离
+## 资产管理
+车场管理 智能表管理 片区管理
+## 物业服务-1
+服务电话
+服务标准
+流程公约
+收费标准公示
+服务动态管理
+## 任务系统-1
+工单系统
+任务统计
+任务管理
+安保巡逻
+保洁清洁
+执勤记录
+## 收费设置
+账期设置
+押金设置
+预存款设置
+## 财务审核
+流水记录
+作废记录
+减免记录
+补录记录
+退费管理
+账单调账
+## 收费管理
+预存款列表
+退费记录
+押金列表
+## 财务报表
+报表导出
+数据大屏
+## 成本核算-1
+预算计划
+预算统计
+成本代码设置
+## 智能硬件-1
+对讲机
+对讲机绑定管理
+开门记录
+访客记录
+## 设备巡检-1
+设备设置
+设备台账

+ 2 - 1
package.json

@@ -17,11 +17,12 @@
     "clipboard": "^2.0.4",
     "core-js": "^3.6.5",
     "element-ui": "^2.15.1",
-    "file-saver": "^2.0.2",
+    "file-saver": "^2.0.5",
     "throttle-debounce": "^2.1.0",
     "vue": "^2.6.11",
     "vue-router": "^3.5.1",
     "vuedraggable": "^2.23.2",
+    "vuex": "^3.6.2",
     "wisdom-ui": "1.0.1-rc.1"
   },
   "devDependencies": {

+ 115 - 0
pageconfig.js

@@ -0,0 +1,115 @@
+// 页面 schema
+const config = {
+  pageConfig: {
+    pageName: '',
+    router: '',
+    header: {
+      desc: '',
+      button: [
+        {
+          text: '返回',
+          type: '',
+          loading: false,
+          disabled: false,
+          clickEvent: '',
+          backPath: ''
+        },
+        {
+          text: '报事',
+          type: '',
+          loading: false,
+          disabled: false,
+          clickEvent: ''
+        },
+        {
+          text: '',
+          type: '',
+          loading: false,
+          disabled: false,
+          clickEvent: ''
+        }
+      ]
+    },
+  },
+  queryForm: {
+    fields: [
+      {
+        __config__: {
+          label: '单行输入',
+          labelWidth: null,
+          showLabel: true,
+          changeTag: true,
+          tag: 'el-input',
+          tagIcon: 'input',
+          required: true,
+          layout: 'colFormItem',
+          span: 24,
+          document: 'https://element.eleme.cn/#/zh-CN/component/input',
+          regList: [],
+          formId: 362,
+          renderKey: 1619055104509
+        },
+        __slot__: {
+          prepend: '',
+          append: ''
+        },
+        placeholder: '请输入单行输入',
+        style: {
+          width: '100%'
+        },
+        clearable: true,
+        'prefix-icon': '',
+        'suffix-icon': '',
+        maxlength: 64,
+        'show-word-limit': false,
+        readonly: false,
+        disabled: false,
+        __vModel__: 'inputField362'
+      },
+      {
+        __config__: {
+          label: '金额',
+          showLabel: true,
+          changeTag: true,
+          labelWidth: null,
+          tag: 'el-input-number',
+          tagIcon: 'money',
+          span: 24,
+          layout: 'colFormItem',
+          required: true,
+          regList: [],
+          document: 'https://element.eleme.cn/#/zh-CN/component/input-number',
+          formId: 361,
+          renderKey: 1619055101287
+        },
+        isConditionValue: true,
+        placeholder: '请输入金额,不超过2位小数金额',
+        step: 1,
+        'step-strictly': false,
+        precision: 2,
+        'controls-position': 'right',
+        disabled: false,
+        __vModel__: 'moneyField361'
+      }
+    ],
+    formRef: 'elForm',
+    formModel: 'formData',
+    size: 'medium',
+    labelPosition: 'right',
+    labelWidth: 110,
+    formRules: 'rules',
+    gutter: 15,
+    disabled: false,
+    span: 24,
+    formBtns: false
+  },
+  table: {
+    column: {
+      label: '',
+      prop: '',
+      fixed: '',
+      minWidth: '',
+      showOverflowTooltip: false
+    }
+  }
+}

+ 2 - 2
src/components/generator/css.js

@@ -11,8 +11,8 @@ function addCss(cssList, el) {
   }
 }
 
-export function makeUpCss(conf) {
+export function makeUpCss({ fields = [] }) {
   const cssList = []
-  conf.fields.forEach(el => addCss(cssList, el))
+  fields.forEach(el => addCss(cssList, el))
   return cssList.join('\n')
 }

+ 15 - 8
src/components/generator/html.js

@@ -1,5 +1,6 @@
 /* eslint-disable max-len */
 import ruleTrigger from './ruleTrigger'
+import { wcTags } from './wc.js'
 
 let confGlobal
 let someSpanIsNot24
@@ -15,23 +16,23 @@ export function dialogWrapper(str) {
 }
 
 export function vueTemplate(str) {
-  return `<template>
-    <div>
+  return `\n<template>
       ${str}
-    </div>
-  </template>`
+  </template>
+  \n`
 }
 
 export function vueScript(str) {
   return `<script>
     ${str}
-  </script>`
+  </script>
+  \n`
 }
 
-export function cssStyle(cssStr) {
-  return `<style>
+export function cssStyle(cssStr, isScoped) {
+  return `<style lang="less" ${isScoped ? 'scoped' : ''}>
     ${cssStr}
-  </style>`
+    </style>`
 }
 
 function buildFormTemplate(scheme, child, type) {
@@ -397,3 +398,9 @@ export function makeUpHtml(formConfig, type) {
   confGlobal = null
   return temp
 }
+
+export function makeUpWcHtml(formConfig, type) {
+  const content = wcTags['w-content']()
+  const layout = wcTags['w-layout']({ child: content })
+  return layout
+}

+ 58 - 0
src/components/generator/js.js

@@ -47,6 +47,31 @@ export function makeUpJs(formConfig, type) {
   return script
 }
 
+/**
+ * 组装wc 入口js 【入口函数】
+ * @param {Object} formConfig 整个表单配置
+ * @param {String} type 生成类型,文件或弹窗等
+ */
+export function makeUpWcJs(formConfig, type) {
+  confGlobal = formConfig = deepClone(formConfig)
+  const dataList = []
+  const ruleList = []
+  const optionsList = []
+  const propsList = []
+  const methodList = mixinMethod(type)
+  const uploadVarList = []
+  const created = []
+
+  const script = buildWcExport(
+    formConfig,
+    type,
+    propsList.join('\n'),
+    methodList.join('\n')
+  )
+  confGlobal = null
+  return script
+}
+
 // 构建组件属性
 function buildAttributes(scheme, dataList, ruleList, optionsList, methodList, propsList, uploadVarList, created) {
   const config = scheme.__config__
@@ -238,6 +263,39 @@ function buildOptionMethod(methodName, model, methodList, scheme) {
   methodList.push(str)
 }
 
+// wc js整体拼接
+function buildWcExport(config, type, props, methods) {
+  const str = `
+  import tab1 from './tab1/index'
+  \n
+  ${exportDefault} {
+  ${inheritAttrs[type]}
+  name: 'ContentComp',
+  components: {},
+  data () {
+    const self = this
+    return {
+      tabList: [
+        {
+          name: '${config.tabName}',
+          key: 'tab1',
+          component: tab1,
+          props: {
+          }
+        }
+      ]
+      ${props}
+    }
+  },
+  computed: {},
+  mounted () {},
+  methods: {
+    ${methods}
+  }
+}`
+  return str
+}
+
 // js整体拼接
 function buildexport(conf, type, data, rules, selectOptions, uploadVar, props, methods, created) {
   const str = `${exportDefault}{

+ 18 - 0
src/components/generator/wc.js

@@ -0,0 +1,18 @@
+/*
+ * @Author: WangJiaCheng
+ * @Date: 2021-07-22 10:17:13
+ * @LastEditors: WangJiaCheng
+ * @LastEditTime: 2021-07-23 10:34:30
+ * @Description: wisdomcity 特有组件
+ */
+
+function buildElWLayoutChild(scheme) {
+}
+
+export const wcTags = {
+  'w-layout': (el = {}) => `<w-layout>${el.child}</w-layout>`,
+  'w-content': (el = {}) => {
+    const { child = '' } = el
+    return `<w-content :tabList='tabList'>${child}</w-content>`
+  }
+}

+ 28 - 2
src/layouts/DefaultLayout/DefaultSide/SideItem.vue

@@ -2,7 +2,7 @@
  * @Author: WangJiaCheng
  * @Date: 2021-05-07 16:07:56
  * @LastEditors: WangJiaCheng
- * @LastEditTime: 2021-05-07 17:41:55
+ * @LastEditTime: 2021-07-09 14:39:57
  * @Description:
 -->
 <template>
@@ -19,6 +19,7 @@
           :index="resolvePath(theOnlyOneChild.path)"
           :class="{'submenu-title-noDropdown': isFirstLevel}"
         >
+          <i class="el-icon-menu" />
           <span
             v-if="theOnlyOneChild.meta.title"
             slot="title"
@@ -32,6 +33,7 @@
       popper-append-to-body
     >
       <template slot="title">
+        <i class="el-icon-menu" />
         <span
           v-if="item.meta && item.meta.title"
           slot="title"
@@ -112,5 +114,29 @@ export default {
 </script>
 
 <style lang="less">
-
+  .el-menu--collapse {
+    .simple-mode {
+      .el-menu-item {
+        span {
+          height: 0;
+          width: 0;
+          overflow: hidden;
+          visibility: hidden;
+          display: inline-block;
+        }
+      }
+      .el-submenu {
+        span {
+          height: 0;
+          width: 0;
+          overflow: hidden;
+          visibility: hidden;
+          display: inline-block;
+        }
+        .el-submenu__icon-arrow {
+          display: none;
+        }
+      }
+    }
+  }
 </style>

+ 15 - 4
src/layouts/DefaultLayout/DefaultSide/index.vue

@@ -2,15 +2,21 @@
  * @Author: WangJiaCheng
  * @Date: 2021-05-06 17:57:16
  * @LastEditors: WangJiaCheng
- * @LastEditTime: 2021-05-08 09:31:07
+ * @LastEditTime: 2021-07-09 14:37:28
  * @Description:
 -->
 <template>
-  <el-aside class="layout-aside">
+  <el-aside
+    class="layout-aside"
+    :width="isCollapse ? '64px' : '210px'"
+  >
     <logo
       title="绘开发"
       :collapse="isCollapse"
     />
+    <span @click="handleCollapse">
+      <i :class="`${isCollapse ? 'el-icon-d-arrow-left' : 'el-icon-d-arrow-right'}`" />
+    </span>
     <el-scrollbar>
       <el-menu
         :collapse="isCollapse"
@@ -48,13 +54,18 @@ export default {
     routes() {
       return routes
     }
+  },
+  methods: {
+    handleCollapse() {
+      this.isCollapse = !this.isCollapse
+    }
   }
 }
 </script>
 
 <style lang="less">
- .layout-aside {
-   width: 210px !important;
+ .layout-aside.el-aside {
+   width: 210px;
    background-color: rgb(238, 241, 246);
    .el-scrollbar {
      height: calc(100% - 60px);

+ 3 - 2
src/layouts/DefaultLayout/index.vue

@@ -2,7 +2,7 @@
  * @Author: WangJiaCheng
  * @Date: 2021-05-06 17:24:30
  * @LastEditors: WangJiaCheng
- * @LastEditTime: 2021-05-11 18:16:38
+ * @LastEditTime: 2021-07-09 16:50:53
  * @Description:
 -->
 <template>
@@ -45,6 +45,7 @@ export default {
   .default-layout {
     width: 100%;
     height: 100%;
+    background: #fff;
   }
   .el-aside {
     color: #333;
@@ -53,6 +54,6 @@ export default {
     width: 100%;
     height: calc(100% - 60px);
     padding: 0;
-    background: #e6e6e6;
+    background: #fff;
   }
 </style>

+ 3 - 1
src/main.js

@@ -2,7 +2,7 @@
  * @Author: WangJiaCheng
  * @Date: 2021-05-07 11:34:26
  * @LastEditors: WangJiaCheng
- * @LastEditTime: 2021-05-08 15:29:03
+ * @LastEditTime: 2021-07-09 15:46:45
  * @Description:  入口
  */
 import Vue from 'vue'
@@ -13,6 +13,7 @@ import 'wisdom-ui/lib/theme/index.css'
 
 import App from './App.vue'
 import router from '@/router'
+import store from '@/store'
 import '@/styles/index.less'
 import '@/icons'
 import axios from 'axios'
@@ -28,5 +29,6 @@ Vue.use(WisdomUI)
 
 new Vue({
   router,
+  store,
   render: h => h(App)
 }).$mount('#app')

+ 31 - 0
src/store/index.js

@@ -0,0 +1,31 @@
+/*
+ * @Author: WangJiaCheng
+ * @Date: 2021-07-09 15:43:50
+ * @LastEditors: WangJiaCheng
+ * @LastEditTime: 2021-07-16 10:37:39
+ * @Description:
+ */
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+// import app from './modules/app'
+// import page from './modules/page'
+
+Vue.use(Vuex)
+
+const modulesFiles = require.context('./modules/', true, /\.js$/)
+
+const modules = modulesFiles.keys().reduce((ms, modulePath) => {
+  // set './app.js' => 'app'
+  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
+  const value = modulesFiles(modulePath)
+  ms[moduleName] = value.default
+  return ms
+}, {})
+
+const store = new Vuex.Store({
+  namespaced: true,
+  modules
+})
+
+export default store

+ 12 - 0
src/store/modules/app.js

@@ -0,0 +1,12 @@
+/*
+ * @Author: WangJiaCheng
+ * @Date: 2021-07-09 16:01:52
+ * @LastEditors: WangJiaCheng
+ * @LastEditTime: 2021-07-09 16:03:23
+ * @Description:
+ */
+export default {
+  namespaced: true,
+  state: {
+  }
+}

+ 34 - 0
src/store/modules/page.js

@@ -0,0 +1,34 @@
+/*
+ * @Author: WangJiaCheng
+ * @Date: 2021-07-09 16:03:43
+ * @LastEditors: WangJiaCheng
+ * @LastEditTime: 2021-07-16 10:55:19
+ * @Description: 页面 store
+ */
+export default {
+  namespaced: true,
+  state: {
+    activeTab: 'page',
+    pageConfig: {},
+    formConfig: {},
+    listConfig: {},
+    detailConfig: {}
+  },
+  mutations: {
+    setActiveTab(state, payload) {
+      state.activeTab = payload
+    },
+    setPageConfig(state, payload) {
+      state.pageConfig = payload
+    },
+    setFormConfig(state, payload) {
+      state.formConfig = payload
+    },
+    setListConfig(state, payload) {
+      state.listConfig = payload
+    },
+    setDetailConfig(state, payload) {
+      state.detailConfig = payload
+    }
+  }
+}

+ 2 - 2
src/utils/index.js

@@ -46,7 +46,7 @@ export const beautifierConf = {
     indent_size: '2',
     indent_char: ' ',
     max_preserve_newlines: '-1',
-    preserve_newlines: false,
+    preserve_newlines: true,
     keep_array_indentation: false,
     break_chained_methods: false,
     indent_scripts: 'separate',
@@ -65,7 +65,7 @@ export const beautifierConf = {
     indent_size: '2',
     indent_char: ' ',
     max_preserve_newlines: '-1',
-    preserve_newlines: false,
+    preserve_newlines: true,
     keep_array_indentation: false,
     break_chained_methods: false,
     indent_scripts: 'normal',

+ 17 - 1
src/views/Page/Edit/FormConfig.vue

@@ -2,12 +2,13 @@
  * @Author: WangJiaCheng
  * @Date: 2021-05-11 17:37:33
  * @LastEditors: WangJiaCheng
- * @LastEditTime: 2021-05-12 16:16:05
+ * @LastEditTime: 2021-07-16 15:23:32
  * @Description: 表单配置
 -->
 <template>
   <div class="form-config">
     <el-form
+      ref="form"
       :model="formData"
       label-width="100px"
     >
@@ -30,11 +31,15 @@
       <el-form-item label="请求方式" prop="apiMethod">
         <el-input v-model="formData.searchApi" />
       </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="next">下一步</el-button>
+      </el-form-item>
     </el-form>
   </div>
 </template>
 
 <script>
+import { mapMutations } from 'vuex'
 
 export default {
   name: 'FormConfig',
@@ -54,6 +59,17 @@ export default {
         }
       ]
     }
+  },
+  methods: {
+    ...mapMutations('page', ['setFormConfig', 'setActiveTab']),
+    next() {
+      this.$refs.form.validate(valid => {
+        if (valid) {
+          this.setFormConfig(this.formData)
+          this.setActiveTab('list')
+        }
+      })
+    }
   }
 }
 </script>

+ 34 - 2
src/views/Page/Edit/PageConfig.vue

@@ -2,14 +2,16 @@
  * @Author: WangJiaCheng
  * @Date: 2021-05-11 11:18:32
  * @LastEditors: WangJiaCheng
- * @LastEditTime: 2021-05-11 17:18:41
+ * @LastEditTime: 2021-07-23 15:28:44
  * @Description: 页面配置
 -->
 <template>
   <div class="page-edit">
     <el-form
+      ref="pageForm"
+      :rules="rules"
       :model="formData"
-      label-width="100px"
+      label-width="120px"
     >
       <el-form-item label="页面Tab名称" prop="tabName">
         <el-input v-model="formData.tabName" />
@@ -43,22 +45,37 @@
       <el-form-item label="路由" prop="route">
         <el-input v-model="formData.route" />
       </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="next">下一步</el-button>
+        <el-button type="primary" @click="previewCode">预览代码</el-button>
+      </el-form-item>
     </el-form>
   </div>
 </template>
 
 <script>
+import { mapMutations } from 'vuex'
+
 export default {
   name: 'PageConfig',
   data() {
     return {
       formData: {},
+      rules: {
+        tabName: [
+          { required: true, message: '请输入Tab名称' }
+        ],
+        headerDesc: [
+          { required: true, message: '请输入Header描述' }
+        ]
+      },
       dynamicTags: [],
       inputVisible: false,
       inputValue: ''
     }
   },
   methods: {
+    ...mapMutations('page', ['setPageConfig', 'setActiveTab']),
     handleClose(tag) {
       this.dynamicTags.splice(this.dynamicTags.indexOf(tag), 1)
     },
@@ -75,6 +92,21 @@ export default {
       }
       this.inputVisible = false
       this.inputValue = ''
+    },
+    next() {
+      this.$refs.pageForm.validate(valid => {
+        if (valid) {
+          this.setPageConfig(this.formData)
+          this.setActiveTab('form')
+        }
+      })
+    },
+    previewCode() {
+      this.$refs.pageForm.validate(valid => {
+        if (valid) {
+          this.$emit('preview', this.formData)
+        }
+      })
     }
   }
 }

+ 33 - 2
src/views/Page/Edit/Preview.vue

@@ -2,18 +2,49 @@
  * @Author: WangJiaCheng
  * @Date: 2021-05-12 15:45:40
  * @LastEditors: WangJiaCheng
- * @LastEditTime: 2021-05-12 15:46:26
+ * @LastEditTime: 2021-07-16 10:01:07
  * @Description:
 -->
 <template>
   <div class="preview">
     预览
+    <div>
+      <el-button type="primary">保存页面</el-button>
+      <el-button @click="renderCode">生成代码</el-button>
+    </div>
   </div>
 </template>
 
 <script>
+import {
+  makeUpHtml, vueTemplate, vueScript, cssStyle
+} from '@/components/generator/html'
+import { makeUpJs } from '@/components/generator/js'
+import { makeUpCss } from '@/components/generator/css'
+import loadBeautifier from '@/utils/loadBeautifier'
+import {
+  beautifierConf
+} from '@/utils/index'
+
+let beautifier
+
 export default {
-  name: 'Preview'
+  name: 'Preview',
+  mounted() {
+    loadBeautifier(btf => {
+      beautifier = btf
+    })
+  },
+  methods: {
+    renderCode() {
+      this.AssembleFormData()
+      const script = vueScript(makeUpJs(this.formData, 'file'))
+      const html = vueTemplate(makeUpHtml(this.formData, 'file'))
+      const css = cssStyle(makeUpCss(this.formData))
+      console.log('code', this.formData)
+      return beautifier.html(html + script + css, beautifierConf.html)
+    }
+  }
 }
 </script>
 

+ 34 - 8
src/views/Page/Edit/index.vue

@@ -2,7 +2,7 @@
  * @Author: WangJiaCheng
  * @Date: 2021-05-10 17:25:15
  * @LastEditors: WangJiaCheng
- * @LastEditTime: 2021-05-12 16:17:33
+ * @LastEditTime: 2021-07-23 15:25:24
  * @Description:
 -->
 <template>
@@ -11,29 +11,42 @@
       title="页面编辑"
     />
     <main-content>
-      <el-tabs v-model="activeName" @tab-click="handleClick">
-        <el-tab-pane label="页面配置" name="first">
-          <page-config />
+      <el-tabs :value="activeTab" @tab-click="handleClick">
+        <el-tab-pane label="页面配置" name="page">
+          <page-config
+            @preview="handlePreview"
+          />
         </el-tab-pane>
-        <el-tab-pane label="表单配置" name="second">
+        <el-tab-pane label="表单配置" name="form">
           <form-config />
         </el-tab-pane>
-        <el-tab-pane label="列表配置" name="third">
+        <el-tab-pane label="列表配置" name="list">
           <list-config />
         </el-tab-pane>
-        <el-tab-pane label="详情页配置" name="fourth">详情页配置</el-tab-pane>
+        <el-tab-pane label="详情页配置" name="detail">
+          详情页配置
+        </el-tab-pane>
         <el-tab-pane label="预览页面" name="preview">
           <preview />
         </el-tab-pane>
       </el-tabs>
+      <preview-code
+        :visible.sync="previewVisible"
+        :form-data="formData"
+        size="80%"
+      />
     </main-content>
   </div>
 </template>
 
 <script>
+import { mapMutations, mapState } from 'vuex'
+
 import Headers from '@/components/Headers'
 import MainContent from '@/components/MainContent'
 
+import PreviewCode from '@/views/components/previewCode'
+
 import PageConfig from './PageConfig.vue'
 import FormConfig from './FormConfig.vue'
 import ListConfig from './ListConfig.vue'
@@ -44,6 +57,7 @@ export default {
   components: {
     Headers,
     MainContent,
+    PreviewCode,
     PageConfig,
     FormConfig,
     ListConfig,
@@ -51,11 +65,23 @@ export default {
   },
   data() {
     return {
-      activeName: 'first'
+      formData: {},
+      previewVisible: false
     }
   },
+  computed: {
+    ...mapState({
+      activeTab: state => state.page.activeTab
+    })
+  },
   methods: {
+    ...mapMutations('page', ['setActiveTab']),
     handleClick(tab, event) {
+      this.setActiveTab(tab.name)
+    },
+    handlePreview(d) {
+      this.formData = d
+      this.previewVisible = true
     }
   }
 }

+ 29 - 0
src/views/Page/Edit/test.vue

@@ -0,0 +1,29 @@
+<template>
+  <w-layout>
+    <w-content :tabList='tabList'></w-content>
+  </w-layout>
+</template>
+<script>
+import tab1 from './tab1/index'
+export default {
+  name: 'ContentComp',
+  components: {},
+  data() {
+    const self = this
+    return {
+      tabList: [{
+        name: '123',
+        key: 'tab1',
+        component: tab1,
+        props: {}
+      }]
+    }
+  },
+  computed: {},
+  mounted() {},
+  methods: {}
+}
+
+</script>
+<style lang="less">
+</style>

+ 313 - 0
src/views/components/previewCode.vue

@@ -0,0 +1,313 @@
+<template>
+  <div>
+    <el-drawer v-bind="$attrs" v-on="$listeners" @opened="onOpen" @close="onClose">
+      <div style="height:100%">
+        <el-row style="height:100%;overflow:auto">
+          <el-col :md="24" :lg="12" class="left-editor">
+            <el-tabs v-model="activeTab" type="card" class="editor-tabs">
+              <el-tab-pane name="html">
+                <span slot="label">
+                  <i v-if="activeTab==='html'" class="el-icon-edit" />
+                  <i v-else class="el-icon-document" />
+                  template
+                </span>
+              </el-tab-pane>
+              <el-tab-pane name="js">
+                <span slot="label">
+                  <i v-if="activeTab==='js'" class="el-icon-edit" />
+                  <i v-else class="el-icon-document" />
+                  script
+                </span>
+              </el-tab-pane>
+              <el-tab-pane name="css">
+                <span slot="label">
+                  <i v-if="activeTab==='css'" class="el-icon-edit" />
+                  <i v-else class="el-icon-document" />
+                  css
+                </span>
+              </el-tab-pane>
+            </el-tabs>
+            <div v-show="activeTab==='html'" id="editorHtml" class="tab-editor" />
+            <div v-show="activeTab==='js'" id="editorJs" class="tab-editor" />
+            <div v-show="activeTab==='css'" id="editorCss" class="tab-editor" />
+          </el-col>
+          <el-col :md="24" :lg="12" class="right-preview">
+            <div class="action-bar" :style="{'text-align': 'left'}">
+              <span class="bar-btn" @click="runCode">
+                <i class="el-icon-refresh" />
+                刷新
+              </span>
+              <span class="bar-btn" @click="exportFile">
+                <i class="el-icon-download" />
+                导出vue文件
+              </span>
+              <span ref="copyBtn" class="bar-btn copy-btn">
+                <i class="el-icon-document-copy" />
+                复制代码
+              </span>
+              <span class="bar-btn delete-btn" @click="$emit('update:visible', false)">
+                <i class="el-icon-circle-close" />
+                关闭
+              </span>
+            </div>
+            <!-- <iframe
+              v-show="isIframeLoaded"
+              ref="previewPage"
+              class="result-wrapper"
+              frameborder="0"
+              src="preview.html"
+              @load="iframeLoad"
+            /> -->
+            <!-- <div v-show="!isIframeLoaded" v-loading="true" class="result-wrapper" /> -->
+          </el-col>
+        </el-row>
+      </div>
+    </el-drawer>
+  </div>
+</template>
+<script>
+import { parse } from '@babel/parser'
+import ClipboardJS from 'clipboard'
+import { saveAs } from 'file-saver'
+import {
+  makeUpHtml, vueTemplate, vueScript, cssStyle, makeUpWcHtml
+} from '@/components/generator/html'
+import { makeUpJs, makeUpWcJs } from '@/components/generator/js'
+import { makeUpCss } from '@/components/generator/css'
+import { exportDefault, beautifierConf } from '@/utils/index'
+import loadMonaco from '@/utils/loadMonaco'
+import loadBeautifier from '@/utils/loadBeautifier'
+
+const editorObj = {
+  html: null,
+  js: null,
+  css: null
+}
+const mode = {
+  html: 'html',
+  js: 'javascript',
+  css: 'css'
+}
+let beautifier
+let monaco
+
+export default {
+  name: 'PreviewCode',
+  components: {},
+  props: {
+    formData: {
+      type: Object,
+      default() {
+        return {}
+      }
+    },
+    generateConf: {
+      type: Object,
+      default() {
+        return {}
+      }
+    }
+  },
+  data() {
+    return {
+      activeTab: 'html',
+      htmlCode: '',
+      jsCode: '',
+      cssCode: '',
+      codeFrame: '',
+      isIframeLoaded: false,
+      isInitcode: false, // 保证open后两个异步只执行一次runcode
+      isRefreshCode: false, // 每次打开都需要重新刷新代码
+      scripts: [],
+      links: [],
+      monaco: null
+    }
+  },
+  computed: {
+  },
+  watch: {},
+  created() {
+  },
+  mounted() {
+    window.addEventListener('keydown', this.preventDefaultSave)
+    const clipboard = new ClipboardJS('.copy-btn', {
+      text: trigger => {
+        const codeStr = this.generateCode()
+        this.$notify({
+          title: '成功',
+          message: '代码已复制到剪切板,可粘贴。',
+          type: 'success'
+        })
+        return codeStr
+      }
+    })
+    clipboard.on('error', e => {
+      this.$message.error('代码复制失败')
+    })
+  },
+  beforeDestroy() {
+    window.removeEventListener('keydown', this.preventDefaultSave)
+  },
+  methods: {
+    preventDefaultSave(e) {
+      if (e.key === 's' && (e.metaKey || e.ctrlKey)) {
+        e.preventDefault()
+      }
+    },
+    onOpen() {
+      const { type = 'file' } = this.generateConf
+      this.htmlCode = makeUpWcHtml(this.formData, type)
+      this.jsCode = makeUpWcJs(this.formData, type)
+      this.cssCode = makeUpCss(this.formData)
+      console.log('html', this.jsCode)
+
+      loadBeautifier(btf => {
+        beautifier = btf
+        this.htmlCode = beautifier.html(this.htmlCode, beautifierConf.html)
+        this.jsCode = beautifier.js(this.jsCode, beautifierConf.js)
+        // this.cssCode = beautifier.css(this.cssCode, beautifierConf.html)
+
+        loadMonaco(val => {
+          monaco = val
+          this.setEditorValue('editorHtml', 'html', this.htmlCode)
+          this.setEditorValue('editorJs', 'js', this.jsCode)
+          this.setEditorValue('editorCss', 'css', this.cssCode)
+          if (!this.isInitcode) {
+            this.isRefreshCode = true
+            this.isIframeLoaded && (this.isInitcode = true) && this.runCode()
+          }
+        })
+      })
+    },
+    onClose() {
+      this.isInitcode = false
+      this.isRefreshCode = false
+    },
+    iframeLoad() {
+      if (!this.isInitcode) {
+        this.isIframeLoaded = true
+        this.isRefreshCode && (this.isInitcode = true) && this.runCode()
+      }
+    },
+    setEditorValue(id, type, codeStr) {
+      if (editorObj[type]) {
+        editorObj[type].setValue(codeStr)
+      } else {
+        editorObj[type] = monaco.editor.create(document.getElementById(id), {
+          value: codeStr,
+          theme: 'vs-dark',
+          language: mode[type],
+          automaticLayout: true
+        })
+      }
+      // ctrl + s 刷新
+      editorObj[type].onKeyDown(e => {
+        if (e.keyCode === 49 && (e.metaKey || e.ctrlKey)) {
+          this.runCode()
+        }
+      })
+    },
+    runCode() {
+      const jsCodeStr = editorObj.js.getValue()
+      try {
+        const ast = parse(jsCodeStr, { sourceType: 'module' })
+        const astBody = ast.program.body
+        if (astBody.length > 1) {
+          this.$confirm(
+            'js格式不能识别,仅支持修改export default的对象内容',
+            '提示',
+            {
+              type: 'warning'
+            }
+          )
+          return
+        }
+        if (astBody[0].type === 'ExportDefaultDeclaration') {
+          const postData = {
+            type: 'refreshFrame',
+            data: {
+              generateConf: this.generateConf,
+              html: editorObj.html.getValue(),
+              js: jsCodeStr.replace(exportDefault, ''),
+              css: editorObj.css.getValue(),
+              scripts: this.scripts,
+              links: this.links
+            }
+          }
+
+          this.$refs.previewPage.contentWindow.postMessage(
+            postData,
+            location.origin
+          )
+        } else {
+          this.$message.error('请使用export default')
+        }
+      } catch (err) {
+        this.$message.error(`js错误:${err}`)
+        console.error(err)
+      }
+    },
+    generateCode() {
+      const html = vueTemplate(editorObj.html.getValue())
+      const script = vueScript(editorObj.js.getValue())
+      const css = cssStyle(editorObj.css.getValue())
+      return beautifier.html(html + script + css, beautifierConf.html)
+    },
+    exportFile() {
+      this.$prompt('文件名:', '导出文件', {
+        inputValue: `${+new Date()}.vue`,
+        closeOnClickModal: false,
+        inputPlaceholder: '请输入文件名'
+      }).then(({ value }) => {
+        if (!value) value = `${+new Date()}.vue`
+        const codeStr = this.generateCode()
+        const blob = new Blob([codeStr], { type: 'text/plain;charset=utf-8' })
+        saveAs(blob, value)
+      })
+    },
+    showResource() {
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+@import '@/styles/mixin.scss';
+.tab-editor {
+  position: absolute;
+  top: 33px;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  font-size: 14px;
+}
+.left-editor {
+  position: relative;
+  height: 100%;
+  background: #1e1e1e;
+  overflow: hidden;
+}
+.setting{
+  position: absolute;
+  right: 15px;
+  top: 3px;
+  color: #a9f122;
+  font-size: 18px;
+  cursor: pointer;
+  z-index: 1;
+}
+.right-preview {
+  height: 100%;
+  .result-wrapper {
+    height: calc(100vh - 33px);
+    width: 100%;
+    overflow: auto;
+    padding: 12px;
+    box-sizing: border-box;
+  }
+}
+@include action-bar;
+::v-deep .el-drawer__header {
+  display: none;
+}
+</style>

+ 7 - 3
yarn.lock

@@ -3381,9 +3381,9 @@ file-loader@^4.2.0:
     loader-utils "^1.2.3"
     schema-utils "^2.5.0"
 
-file-saver@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.npm.taobao.org/file-saver/download/file-saver-2.0.2.tgz#06d6e728a9ea2df2cce2f8d9e84dfcdc338ec17a"
+file-saver@^2.0.5:
+  version "2.0.5"
+  resolved "http://nexus.wisdomcity.com.cn/repository/wisdomcity-npm-group/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38"
 
 file-uri-to-path@1.0.0:
   version "1.0.0"
@@ -7555,6 +7555,10 @@ vuedraggable@^2.23.2:
   dependencies:
     sortablejs "^1.10.1"
 
+vuex@^3.6.2:
+  version "3.6.2"
+  resolved "http://nexus.wisdomcity.com.cn/repository/wisdomcity-npm-group/vuex/-/vuex-3.6.2.tgz#236bc086a870c3ae79946f107f16de59d5895e71"
+
 watch-size@^2.0.0:
   version "2.0.0"
   resolved "http://nexus.wisdomcity.com.cn/repository/wisdomcity-npm-group/watch-size/-/watch-size-2.0.0.tgz#096ee28d0365bd7ea03d9c8bf1f2f50a73be1474"