wangsaitao 1 year ago
parent
commit
9634130dbc
100 changed files with 30660 additions and 2 deletions
  1. 3 0
      .browserslistrc
  2. 24 0
      .editorconfig
  3. 8 0
      .env.development
  4. 8 0
      .env.production
  5. 8 0
      .env.test
  6. 101 0
      .eslintrc.js
  7. 22 0
      .gitignore
  8. 8 0
      .htaccess
  9. 484 2
      README.md
  10. 7 0
      babel.config.js
  11. 32 0
      doc/git.md
  12. 37 0
      doc/index.md
  13. 62 0
      doc/mock.md
  14. 27 0
      doc/print.js.md
  15. 148 0
      doc/router.md
  16. 437 0
      doc/rule.md
  17. 221 0
      doc/vue.md
  18. 5 0
      jsconfig.json
  19. 13938 0
      package-lock.json
  20. 56 0
      package.json
  21. 5 0
      postcss.config.js
  22. BIN
      public/favicon.png
  23. 19 0
      public/index.html
  24. BIN
      public/lessee.xlsx
  25. 389 0
      public/tinymce/langs/zh_CN.js
  26. 59 0
      public/tinymce/skins/content/dark/content.css
  27. 8 0
      public/tinymce/skins/content/dark/content.min.css
  28. 49 0
      public/tinymce/skins/content/default/content.css
  29. 8 0
      public/tinymce/skins/content/default/content.min.css
  30. 53 0
      public/tinymce/skins/content/document/content.css
  31. 8 0
      public/tinymce/skins/content/document/content.min.css
  32. 50 0
      public/tinymce/skins/content/writer/content.css
  33. 8 0
      public/tinymce/skins/content/writer/content.min.css
  34. 589 0
      public/tinymce/skins/ui/oxide-dark/content.css
  35. 615 0
      public/tinymce/skins/ui/oxide-dark/content.inline.css
  36. 8 0
      public/tinymce/skins/ui/oxide-dark/content.inline.min.css
  37. 8 0
      public/tinymce/skins/ui/oxide-dark/content.min.css
  38. 29 0
      public/tinymce/skins/ui/oxide-dark/content.mobile.css
  39. 8 0
      public/tinymce/skins/ui/oxide-dark/content.mobile.min.css
  40. BIN
      public/tinymce/skins/ui/oxide-dark/fonts/tinymce-mobile.woff
  41. 2672 0
      public/tinymce/skins/ui/oxide-dark/skin.css
  42. 8 0
      public/tinymce/skins/ui/oxide-dark/skin.min.css
  43. 673 0
      public/tinymce/skins/ui/oxide-dark/skin.mobile.css
  44. 8 0
      public/tinymce/skins/ui/oxide-dark/skin.mobile.min.css
  45. 607 0
      public/tinymce/skins/ui/oxide/content.css
  46. 615 0
      public/tinymce/skins/ui/oxide/content.inline.css
  47. 8 0
      public/tinymce/skins/ui/oxide/content.inline.min.css
  48. 8 0
      public/tinymce/skins/ui/oxide/content.min.css
  49. 29 0
      public/tinymce/skins/ui/oxide/content.mobile.css
  50. 8 0
      public/tinymce/skins/ui/oxide/content.mobile.min.css
  51. BIN
      public/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff
  52. 2672 0
      public/tinymce/skins/ui/oxide/skin.css
  53. 8 0
      public/tinymce/skins/ui/oxide/skin.min.css
  54. 673 0
      public/tinymce/skins/ui/oxide/skin.mobile.css
  55. 8 0
      public/tinymce/skins/ui/oxide/skin.mobile.min.css
  56. 19 0
      scripts/verify-commit-msg.js
  57. 5 0
      src/app.vue
  58. 34 0
      src/apps/account/api.js
  59. 334 0
      src/apps/account/components/message-remind.vue
  60. 224 0
      src/apps/account/components/page-aside.vue
  61. 235 0
      src/apps/account/components/page-header.vue
  62. 105 0
      src/apps/account/components/personal-info.vue
  63. 147 0
      src/apps/account/components/reset-password.vue
  64. BIN
      src/apps/account/images/background.jpg
  65. BIN
      src/apps/account/images/background_old.jpg
  66. BIN
      src/apps/account/images/mima-shuruzhong@2x.png
  67. BIN
      src/apps/account/images/mima-weishuru@2x.png
  68. BIN
      src/apps/account/images/news.jpg
  69. BIN
      src/apps/account/images/shouji-shuruzhong@2x.png
  70. BIN
      src/apps/account/images/shouji@2x.png
  71. BIN
      src/apps/account/images/sishi.jpg
  72. BIN
      src/apps/account/images/yonghu-shuruzhong@2x.png
  73. BIN
      src/apps/account/images/yonghu-weishuru@2x.png
  74. 18 0
      src/apps/account/router.js
  75. 137 0
      src/apps/account/store/account.js
  76. 124 0
      src/apps/account/style.scss
  77. 135 0
      src/apps/account/views/login.js
  78. 125 0
      src/apps/account/views/login.scss
  79. 90 0
      src/apps/account/views/login.vue
  80. 206 0
      src/apps/account/views/main.vue
  81. 59 0
      src/apps/app-modules/api.js
  82. 124 0
      src/apps/app-modules/components/module-group-create.vue
  83. 22 0
      src/apps/app-modules/router.js
  84. 212 0
      src/apps/app-modules/views/module-edit.vue
  85. 104 0
      src/apps/app-modules/views/module-manage.js
  86. 93 0
      src/apps/app-modules/views/module-manage.vue
  87. 35 0
      src/apps/app-version/api.js
  88. 11 0
      src/apps/app-version/router.js
  89. 144 0
      src/apps/app-version/views/app-version-create.vue
  90. 110 0
      src/apps/app-version/views/app-version.js
  91. 104 0
      src/apps/app-version/views/app-version.vue
  92. 145 0
      src/apps/approval/api.js
  93. 335 0
      src/apps/approval/components/apply-content.vue
  94. 289 0
      src/apps/approval/components/apply-print/apply-print-app133.vue
  95. 287 0
      src/apps/approval/components/apply-print/apply-print-app134.vue
  96. 277 0
      src/apps/approval/components/apply-print/apply-print-app151.vue
  97. 276 0
      src/apps/approval/components/apply-print/apply-print-app152.vue
  98. 271 0
      src/apps/approval/components/apply-print/apply-print10.vue
  99. 278 0
      src/apps/approval/components/apply-print/apply-print17.vue
  100. 0 0
      src/apps/approval/components/apply-print/apply-print20.vue

+ 3 - 0
.browserslistrc

@@ -0,0 +1,3 @@
+> .1% in cn
+last 2 versions
+not ie <= 8

+ 24 - 0
.editorconfig

@@ -0,0 +1,24 @@
+# EditorConfig is awesome: https://EditorConfig.org
+
+# top-most EditorConfig file 表示是最顶层的配置文件,发现设为true时,才会停止查找.editorconfig文件
+root = true
+
+# Unix-style newlines with a newline ending every file 对于所有的文件  始终在文件末尾插入一个新行
+[*]
+end_of_line = lf
+insert_final_newline = true
+
+# Matches multiple files with brace expansion notation
+# Set default charset  对于所有的js文件,设置文件字符集为utf-8
+[*.{js,vue,scss}]
+charset = utf-8
+
+# 4 space indentation 控制vue文件类型的缩进大小
+[*.js,vue,scss]
+indent_style = space
+indent_size = 4
+
+# Matches the exact files either package.json or .travis.yml 设置确切文件 package.json/.travis/.yml的缩进类型
+[{package.json}]
+indent_style = space
+indent_size = 2

+ 8 - 0
.env.development

@@ -0,0 +1,8 @@
+NODE_ENV = 'production'
+VUE_APP_ENV = 'development'
+VUE_APP_VERSION = '1.0'
+VUE_APP_PLATFORM = 'pc'
+VUE_APP_URL = http://121.36.46.135:8083
+VUE_APP_WSURL = ws://121.36.53.58:8383
+VUE_APP_GDMAP_KEY = 5364e58c269ec0605343ee222e3cc51a
+VUE_APP_PHP_URL = http://jyaoaserver.jya-tech.com

+ 8 - 0
.env.production

@@ -0,0 +1,8 @@
+NODE_ENV = 'production'
+VUE_APP_ENV = 'production'
+VUE_APP_VERSION = '1.0'
+VUE_APP_PLATFORM = 'pc'
+VUE_APP_URL = http://121.36.46.135:8083
+VUE_APP_WSURL = ws://121.36.53.58:8383
+VUE_APP_GDMAP_KEY = 5364e58c269ec0605343ee222e3cc51a
+VUE_APP_PHP_URL = http://jyaoaserver.jya-tech.com

+ 8 - 0
.env.test

@@ -0,0 +1,8 @@
+NODE_ENV = 'development'
+VUE_APP_ENV = 'test'
+VUE_APP_VERSION = '1.0'
+VUE_APP_PLATFORM = 'pc'
+VUE_APP_URL = http://121.36.46.135:8083
+VUE_APP_WSURL = ws://121.36.46.135:8383
+VUE_APP_GDMAP_KEY = 5364e58c269ec0605343ee222e3cc51a
+VUE_APP_PHP_URL = http://127.0.0.73

+ 101 - 0
.eslintrc.js

@@ -0,0 +1,101 @@
+module.exports = {
+    root: true,
+    env: {
+        node: true,
+    },
+    extends: [
+        'plugin:vue/strongly-recommended',
+        '@vue/airbnb',
+    ],
+    rules: {
+        "linebreak-style": [0 ,"error", "windows"], //允许windows开发环境
+        'class-methods-use-this': 0,
+        'no-console': process.env.NODE_ENV === 'production' ? ['error', { allow: ['warn', 'error'] }] : 'off',
+        'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
+        'no-param-reassign': [
+            'error',
+            {
+                props: true,
+                ignorePropertyModificationsFor: ['state', 'vm', 'item'],
+            },
+        ],
+        'import/extensions': [
+            'error',
+            'always',
+            {
+                js: 'never',
+                vue: 'always',
+            },
+        ],
+        indent: [
+            'error',
+            4,
+            {
+                SwitchCase: 1,
+            },
+        ],
+        'max-len': [2, 260, 4],
+        'vue/attribute-hyphenation': [
+            'error',
+            'always',
+        ],
+        'vue/attributes-order': 'error',
+        'vue/html-closing-bracket-newline': [
+            'error',
+            {
+                singleline: 'never',
+                multiline: 'always',
+            },
+        ],
+        'vue/html-closing-bracket-spacing': 'error',
+        'vue/html-end-tags': 'error',
+        'vue/html-indent': [
+            'error',
+            4,
+        ],
+        'vue/html-quotes': [
+            'error',
+            'double',
+        ],
+        'vue/html-self-closing': 'error',
+        'vue/max-attributes-per-line': [
+            'error',
+            {
+                singleline: 6,
+                multiline: {
+                    max: 1,
+                    allowFirstLine: false,
+                },
+            },
+        ],
+        'vue/no-confusing-v-for-v-if': 'error',
+        'vue/no-parsing-error': 'error',
+        'vue/no-use-v-if-with-v-for': 'error',
+        'vue/no-v-html': 'error',
+        'vue/order-in-components': 'error',
+        'vue/prop-name-casing': 'error',
+        'vue/require-default-prop': 'error',
+        'vue/require-prop-types': 'error',
+        'vue/script-indent': [
+            'error',
+            4,
+            {
+                baseIndent: 0,
+                switchCase: 1,
+                ignores: [],
+            },
+        ],
+        'vue/this-in-template': 'error',
+    },
+    parserOptions: {
+        parser: 'babel-eslint',
+        "ecmaFeatures": {
+            "legacyDecorators": true
+        }
+    },
+    globals: {
+        'AMap': false,
+        'AMapUI': false,
+    },
+    
+};

+ 22 - 0
.gitignore

@@ -0,0 +1,22 @@
+.DS_Store
+node_modules
+/dist
+
+# local env files
+#.env.Member
+#.env.*
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+.VSCodeCounter
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw*

+ 8 - 0
.htaccess

@@ -0,0 +1,8 @@
+<IfModule mod_rewrite.c>
+    RewriteEngine On
+    RewriteBase /
+    RewriteRule ^index\.html$ - [L]
+    RewriteCond %{REQUEST_FILENAME} !-f
+    RewriteCond %{REQUEST_FILENAME} !-d
+    RewriteRule . /index.html [L]
+</IfModule>

+ 484 - 2
README.md

@@ -1,3 +1,485 @@
-# jya-oa
+# 原则
+## **奉行原则:代码是给人阅读的,机器只是执行程序的**
 
-京宜安oa正式
+### 所需技能:
+1. 基础三件套: JavaScript、HTML(5)、CSS(3)
+2. [Vue](https://cn.vuejs.org/v2/guide/installation.html)、[Vue Router](https://router.vuejs.org/zh/)、[Vuex](https://vuex.vuejs.org/zh/)(是否使用以项目实际情况为准)
+3. [Vue CLI](https://cli.vuejs.org/zh/guide/)(了解,亦可不看,学习中可以试着自己用脚手架搭建)
+4. 本项目UI组件库:[Element UI ^2.13.0](https://element.eleme.io/#/zh-CN/component/layout) (不用看,直接用,没什么看的)
+
+### 其他
+* PS:```Vue```使用之前,可不用了解原理,亦不用了解本项目架构的搭建(后续学习中可以了解),官网教程认真看一遍,边做边学;Vue全家桶(Vue + Vue Router + Vuex)认真了解下。未对```JavaScript```有所了解,不建议直接学习并使用Vue框架或其他前端框架。
+
+* 如果项目中的代码编写与学习时不相同,是因为使用了ES7中的修饰器,引入了依赖```vue-class-component```,请阅读README文件(本文件)的第十条。
+
+* 项目中遇到的问题及问题解决方法,请记录在doc文件夹下的相关文件里(写在本文件下面亦可),形成问题解决库很重要。
+
+## **开发之前,请仔细阅读doc文件夹下的所有文件(这很重要)**
+
+## [README.md](./README.md)
+
+ 介绍项目整体原则以及项目启动流程
+ 
+ ---
+
+## doc文档目录介绍
+
+## [rule.md](./doc/rule.md)
+
+ 项目中各个模块的原则以及可能引申涉及出来的问题
+
+---
+ 
+## [vue.md](./doc/vue.md)
+
+ 项目中遇到的有关Vue的问题
+
+---
+
+## [router.md](./doc/router.md)
+
+ 项目中遇到的有关router的问题
+
+---
+
+## [git.md](./doc/git.md)
+
+ 项目中遇到的有关git的问题
+
+---
+
+## [mock.md](./doc/mock.md)
+
+ 项目中遇到的有关mock的问题
+
+---
+
+## 开发基本原则
+1. 隔离,每个独立的功能抽象为独立的子APP(模块);
+2. 统一,保持模式、功能实现和排版格式的一致性;
+3. 简洁,保持模板和样式的简练、减少第三方库的依赖,避免引入复杂;
+4. 清晰,保持清晰的树形结构,保持**单向数据流**。
+
+
+## 项目启动
+```
+npm install
+```
+
+### 开发命令(支持HMR)
+```
+npm run serve
+```
+
+### 编译命令
+#### 正式环境打包
+```
+npm run build
+```
+#### 测试环境打包
+```
+npm run dev-build
+```
+
+### Lint命令
+```
+npm run lint
+```
+### node-sass 被墙解决方法
+npm install node-sass --sass-binary-site=http://npm.taobao.org/mirrors/node-sass
+
+### vue-cookies
+https://github.com/cmp-cc/vue-cookies
+
+# 代码整体原则
+
+### 请查看doc文件夹下的rule.md文件(规范)(很重要)
+### *大部分所遇到的问题可在doc里得到解决*
+
+## QA
+
+### 1. 关于Element UI中的```NavigationDuplicated {_name: "NavigationDuplicated", name: "NavigationDuplicated"}```
+
+#### 解决方法
+
+在引用vue-router的文件中添加一段代码
+```JavaScript
+const originalPush = Router.prototype.push;
+Router.prototype.push = function push(location) {
+    return originalPush.call(this, location).catch(err => err);
+};
+```
+*注:Element UI ^2.13.0版本已修复此bug*
+
+### 2. Vue如何每次打开子组件弹窗都进行初始化
+
+#### 场景
+```html
+<el-dialog
+    v-if="dialogFormVisible"
+    title="新增图标"
+    width="45%"
+    :visible.sync="dialogFormVisible"
+    :append-to-body="true"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    :show-close="false"
+    center
+>
+```
+#### 解决方法
+
+```
+:visible.sync 与 v-if同时使用
+```
+
+### 3. 关于资源预加载 ```preload``` 和资源预读取 ```prefetch```
+
+#### 场景
+针对于Vue-cli 3
+
+webpack打包之后的文件,在首次访问时,会对资源进行预读取,如不想进行预读取可进行如下操作:
+在webpack配置文件中,移除 ```prefetch``` 插件(和具体项目框架搭建有所差异):
+```JavaScript
+chainWebpack: (config) => {
+    // 移除 prefetch 插件
+    config.plugins.delete('prefetch');
+},
+```
+[**参考Vue CLI 文档**](https://cli.vuejs.org/zh/guide/html-and-static-assets.html#prefetch)
+
+### 4. 环境变量及模式
+
+#### 解决问题
+发版过程中存在很多的发版环境,例如测试环境、开发环境等,我们需要根据不同的环境做相应的调整。
+#### 解决方法
+1. 增加模式 test
+
+```vue-cli-service build --mode test``` 会在 test 模式下加载可能存在的 .env、.env.test 和 .env.test.local 文件然后构建出生产环境应用。
+
+env.test
+
+    ```
+    NODE_ENV=production
+    VUE_APP_API_BASE_URL=http://47.99.189.168:8090/
+    ```
+
+2. 增加变量 test
+以 ```VUE_APP_ ```开头的变量会被 ```webpack.DefinePlugin``` 静态嵌入到客户端侧的包中
+```VUE_APP_SERVER_MODE=test vue-cli-service build --modern --mode production```
+
+3. 以本项目为例:
+
+    .env.test 为本地开发环境
+    ```
+    NODE_ENV = 'development'
+    VUE_APP_ENV = 'test'
+    ```
+
+    .env.development 为测试环境
+    ```
+    NODE_ENV = 'production'
+    VUE_APP_ENV = 'development'
+    ```
+
+    .env.production 为正式环境(线上环境)
+    ```
+    NODE_ENV = 'production'
+    VUE_APP_ENV = 'production'
+    ```
+
+    package.json 文件
+    ```json
+    "scripts": {
+        "serve": "vue-cli-service serve --mode Member",
+        "build": "vue-cli-service build --mode production",
+        "dev-build": "vue-cli-service build --mode development",
+        "lint": "vue-cli-service lint"
+    },
+    ```
+    [**参考Vue CLI 文档**](https://cli.vuejs.org/zh/guide/mode-and-env.html#模式)
+
+    **说明:**
+    测试环境 ```NODE_ENV = 'production'``` ,是因为webpack在打包时,会对文件进行压缩;如果 ```NODE_ENV = 'development'``` 时,在打包时不会对文件进行压缩,导致文件体积会特别大。
+
+### 5. reload 刷新当前页面
+#### 场景
+
+在处理列表时,常常有删除一条数据或者新增数据之后需要重新刷新当前页面的需求。
+
+#### 解决问题
+
+1. 用vue-router重新路由到当前页面,页面是不进行刷新的
+
+2. 采用```window.reload()```,或者```router.go(0)```刷新时,整个浏览器进行了重新加载,闪烁,体验不好
+
+#### 解决方法
+```provide / inject``` 组合
+
+作用:允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
+
+
+```html
+<template>
+    <el-container>
+        <el-main>
+            <router-view v-if="isRouterAlive" />
+        </el-main>
+    </el-container>
+</template>
+<script>
+import Vue from 'vue';
+import Component from 'vue-class-component';
+@Component({
+    provide() {
+        return {
+            reload: this.reload,
+        };
+    },
+})
+export default class Main extends Vue {
+    isRouterAlive = true;
+
+    reload() {
+        this.isRouterAlive = false;
+        this.$nextTick(() => {
+            this.isRouterAlive = true;
+        });
+    }
+}
+```
+
+在使用```reload```的页面:
+```js
+import Vue from 'vue';
+import Component from 'vue-class-component';
+
+@Component({
+    inject: ['reload'],
+})
+export default class Child extends Vue {
+    handleSwitchOrg() {
+        this.reload();
+    }
+}
+</script>
+```
+
+### 6. 关于菜单在用户退出重新登录,重复出现问题
+
+#### 原因
+使用状态管理机Vuex,用户退出登录时,Vuex的存储数据未清理,导致数据重复
+
+#### 解决方法
+1. 在Vuex的退出登录时,清空数据存储(如下)
+    ```js
+    [ACCOUNT_LOGOUT]({ commit }, payload) {
+        return accountApi.accountLogout(payload.id).then((res) => {
+            commit(SET_USERINFO, {});
+            return res;
+        });
+    },
+    ```
+    [Vuex文档](https://vuex.vuejs.org/zh/)
+2. 对数据进行加锁处理(详见account下的components里的pageAside.vue文件)
+
+    ```js
+    // 获取菜单数据,格式化数据,加锁以防多次重复调用,产生错误
+    getRulesData() {
+        if (this.flag) {
+            this.flag = false;
+
+            // ......
+
+            this.flag = true;
+        }
+    }
+    ```
+
+### 7. ESLint for循环 (尽量使用ES6语法)
+
+规则不允许一元运算符++和--。
+
+错误示例:
+```js
+var foo = 0;
+foo++;
+
+var bar = 42;
+bar--;
+
+for (let i = 0; i < l; i++) {
+    return;
+}
+```
+
+正确示例:
+```js
+var foo = 0;
+foo += 1;
+
+var bar = 42;
+bar -= 1;
+
+for (i = 0; i < l; i += 1) {
+    return;
+}
+```
+### 8. ESLint ```for ... in```
+循环遍历对象```for in```将包含通过原型链继承的属性。此行为可能会导致 for 循环中出现意外的项目。
+```js
+for (key in foo) {
+    doSomething(key);
+}
+```
+请注意,```foo.hasOwnProperty(key)```在某些情况下,简单检查可能会导致错误; 见 ```no-prototype-builtins``` 。
+
+规则细节
+此规则旨在防止使用```for in```循环而不过滤循环中的结果时可能出现的意外行为。因此,当```for in```循环不会用if语句过滤结果时,它会发出警告。
+
+此规则的错误代码示例:
+```js
+/*eslint guard-for-in: "error"*/
+
+for (key in foo) {
+    doSomething(key);
+}
+```
+此规则的正确代码示例:
+```js
+/*eslint guard-for-in: "error"*/
+
+for (key in foo) {
+    if (Object.prototype.hasOwnProperty.call(foo, key)) {
+        doSomething(key);
+    }
+    if ({}.hasOwnProperty.call(foo, key)) {
+        doSomething(key);
+    }
+}
+```
+### 9. Element UI ```el-image```组件造成页面无法滚动
+#### 场景
+a 标签里有 ```el-image``` 组件进行链接跳转时,会为```body```添加内联属性:```overflow:hidden```,由此造成页面无法进行滚动。
+```html
+<router-link :to="{ name: 'journal.add'}">
+    <el-image
+        style="width: 50px; height: 50px"
+        src="https://nuodastorage.oss-cn-beijing.aliyuncs.com/gyhq/0/common/png/2019/10/30/15724335243123882.png"
+    />
+</router-link>
+```
+
+#### 解决问题
+不采用```el-image```,使用原生```img```
+
+```html
+<router-link :to="{ name: 'journal.add'}">
+    <img
+        style="width: 50px; height: 50px"
+        src="https://nuodastorage.oss-cn-beijing.aliyuncs.com/gyhq/0/common/png/2019/10/30/15724335243123882.png"
+    >
+</router-link>
+```
+
+#### 原因:
+```
+尚未知
+```
+
+### 10. 关于ES7中的修饰器(解释一下相关问题,强烈建议学习一下ES6+语法)
+
+#### 解释相关疑问
+
+在此之前,先提一下这个依赖 ```vue-class-component```
+是尤大大推出的Vue对typescript支持的装饰器(库)
+
+```vue-class-component``` 强调了几点用法:
+
+1、```methods``` 可以直接声明为类成员方法
+
+2、```computed``` 可以将计算的属性声明为类属性getter / setter:
+
+3、```data``` 数据可以声明为类属性
+
+4、```data```、```render``` 和所有Vue生命周期挂钩也可以直接声明为类成员方法,但不能在实例本身上调用它们。在声明自定义方法时,应避免使用这些保留名称。
+
+请阅读
+[Vue Class Component](https://class-component.vuejs.org/)
+
+#### 示:1:
+
+TypeScript 语法:
+
+```ts
+import Vue from 'vue';
+import Component from 'vue-class-component';
+ 
+@Component({
+    props: {
+        firstName: String,
+        lastName: String
+    },
+    components: {
+        'component-a': ComponentA
+    }
+})
+export class XXXX extends Vue {
+    firstName: string; // TypeScript 语法
+    lastName: string;
+     
+    //初始data
+    middleName = 'middle';
+     
+    //computed 属性
+    get fullName() {
+        return this.firstName + this.lastName;
+    }
+     
+    //method
+    hello() {
+        alert(`Hello ${this.fullName}!`);
+    }
+     
+    //钩子
+    mounted() {
+        this.hello();
+    }
+}
+```
+
+#### 示例2:
+```js
+// XXXX 类名,最好以文件名命名
+import Vue from 'vue';
+import Component from 'vue-class-component';
+ 
+@Component({
+    props: {
+        firstName,
+        lastName
+    },
+    components: {
+        ComponentA
+    }
+})
+export class XXXX extends Vue {
+    //初始data
+    middleName = 'middle';
+     
+    //computed 属性
+    get fullName() {
+        return this.firstName + this.lastName;
+    }
+     
+    //method
+    hello() {
+        alert(`Hello ${this.fullName}!`);
+    }
+     
+    //钩子
+    mounted() {
+        this.hello();
+    }
+}
+```

+ 7 - 0
babel.config.js

@@ -0,0 +1,7 @@
+module.exports = {
+    presets: [
+        ['@vue/app', {
+            useBuiltIns: 'entry',
+        }],
+    ],
+};

+ 32 - 0
doc/git.md

@@ -0,0 +1,32 @@
+# git相关
+
+### 1. 代码rebase注意事项
+
+#### 场景
+分支代码进行rebase的时候没有及时更新本地dev分支,导致merge的时候提交了大量历史分支的代码修改,增加了review的重复工作量,没有意义
+
+push前必须rebase(评论之后禁止rebase),保证代码的历史路径的简单。
+
+#### 解决
+在分支确定执行rebase前,先切换到dev分支更新最新代码,然后再切换回当前分支,执行dev的rebase操作,最大程度缩短更新dev和进行rebase操作的时间间隔,确保在merge的时候,只review当前分支修改涉及到的代码
+
+#### 案例
+1. 从dev分支更新issue分支操作步骤
+
+``` bash
+git checkout dev
+git pull origin dev
+git checkout issue
+git rebase dev
+=
+git fetch origin
+git rebase origin/dev
+```
+
+2. 从远程库分支更新当前同一issue分支
+
+``` bash
+git fetch origin
+git rebase origin/issue
+```
+

+ 37 - 0
doc/index.md

@@ -0,0 +1,37 @@
+# doc文档目录介绍
+
+## [README.md](../README.md)
+
+ 介绍项目整体原则以及项目启动流程
+ 
+ ---
+
+## [rule.md](./rule.md)
+
+ 项目中各个模块的原则以及可能引申涉及出来的问题
+
+---
+ 
+## [vue.md](./vue.md)
+
+ 项目中遇到的有关Vue的问题
+
+---
+
+## [router.md](./router.md)
+
+ 项目中遇到的有关router的问题
+
+---
+
+## [git.md](./git.md)
+
+ 项目中遇到的有关git的问题
+
+---
+
+## [mock.md](./mock.md)
+
+ 项目中遇到的有关mock的问题
+
+---

+ 62 - 0
doc/mock.md

@@ -0,0 +1,62 @@
+# mock相关
+
+## QA
+
+### 1. mockjs加载时机
+
+在import mockjs之后再挂载;
+
+##### 原因
+最初在import所有业务代码之后,才import mockjs,(import本身也是一个Promise,所以涉及到加载的时机)导致异步请求时,先走原本请求不到的接口,已经显示请求失败了,才开始走mockjs;
+调整后的代码
+
+```
+const app = new Vue({
+    data: {
+        isExpire: false,
+    },
+    router,
+    render: h => h(App),
+});
+if (process.env.NODE_ENV === 'development') {
+    import('./mock').then(() => {
+        app.$mount('#app');
+    });
+} else {
+    app.$mount('#app');
+}
+```
+
+### 2. 设置参数错误(POST)导致mock失败
+
+##### 原因
+ 大小写不匹配
+
+```
+...
+const data = mock.mock('/zhiyuadminBack/manageusers/getPassUsers', 'post', {
+    code: '0',
+    result: 'success',
+    msg: '成功',
+...
+```
+
+追踪源码发现,必须为小写才能请求成功;因为mock的options会和request请求中的参数大小写不匹配导致无法send() xhr对象
+
+```
+ function find(options) {
+
+    for (var sUrlType in MockXMLHttpRequest.Mock._mocked) {
+        var item = MockXMLHttpRequest.Mock._mocked[sUrlType]
+        if (
+           // 注意⚠️  断点处 debugger;  原请求参数中被转为小写,而mock中如果为大写,无法match()
+            (!item.rurl || match(item.rurl, options.url)) &&
+            (!item.rtype || match(item.rtype, options.type.toLowerCase()))
+        ) {
+            // console.log('[mock]', options.url, '>', item.rurl)
+            return item
+        }
+    }
+```
+
+

+ 27 - 0
doc/print.js.md

@@ -0,0 +1,27 @@
+# vuePlugs_printjs
+vue打印插件
+**使用方法**
+
+```
+import Print from '@/plugs/print'
+Vue.use(Print) // 注册
+<template>
+<section ref="print">
+	打印内容
+	<div class="no-print">不要打印我</div>
+</section>
+</template>
+this.$print(this.$refs.print) // 使用
+```
+<font color=#c00 >注意事项</font>
+需使用ref获取dom节点,若直接通过id或class获取则webpack打包部署后打印内容为空
+**指定不打印区域**
+方法一. 添加no-print样式类
+```
+<div class="no-print">不要打印我</div>
+```
+方法二. 自定义类名
+```
+<div class="do-not-print-me-xxx">不要打印我</div>
+this.$print(this.$refs.print,{'no-print':'.do-not-print-me-xxx'}) // 使用
+```

+ 148 - 0
doc/router.md

@@ -0,0 +1,148 @@
+# 路由相关
+
+## QA
+
+### 1. `beforeRouteUpdate`触发问题
+
+**触发条件:在当前路由改变,并且该组件被复用时调用**
+
+#### 原因
+不同路由同对一个组件存在复用,如下例所示从路由1进入路由2时,不会触发`beforeRouteUpdate`方法。
+
+#### 解决
+1. 让两者路由兼容,使其在同一个路由中匹配,避免上面的场景出现;
+
+#### Todo
+有可能在别名路由中也会存在上面的问题,未测试。
+
+#### 其他
+1. `beforeRouteUpdate`只能在绑定路由的组件中使用,其他组件中不会响应;
+
+#### 使用发生场景
+1. 在列表页有分页以及在当前页需要进行查询搜索条件时,url路由配置时候需要注意
+
+#### 示例代码
+```javascript
+// 更改前
+{
+    // 路由1
+    path: '',
+    component: () => import(/* webpackChunkName: "ecs-seahare" */ './views/index.vue'),
+},
+{
+    // 路由2
+    path: ':page(\\d+)/:keyword?',
+    component: () => import(/* webpackChunkName: "ecs-seahare" */ './views/index.vue'),
+},
+
+// 更改后
+{
+    // 注意路由之间会相互冲突
+    path: ':page(\\d+)/:keyword?',
+    component: () => import(/* webpackChunkName: "ecs-seahare" */ './views/index.vue'),
+},
+```
+
+### 2. 被复用组件状态更新问题
+
+#### 表现
+项目中的左侧菜单在部分页面跳转时,没有相应改变当前高亮项目,依旧是前一个页面的高亮状态
+
+#### 原因
+`router-view` 内的页面跳转,会复用相同组件,当前菜单高亮方法 `currentHighlight` 在组件内 `created` 周期中调用,因为组件实例复用,组件的生命周期钩子不会再被调用,高亮方法没有执行。
+
+#### 分析尝试
+`currentHighlight` 方法在适当时机调用是解决问题的关键,如果在组件的生命周期内调用方法会因为组件的复用而不被执行,因为是路由发生变化,因此尝试在组件内使用导航守卫提供的`beforeRouteEnter` 调用 `currentHighlight` 方法
+
+``` javascript
+beforeRouteEnter(to, from, next) {
+    next((vm) => {
+        vm.currentHighlight();
+    });
+},
+```
+
+但方法依然没有被调用执行,为了验证 `beforeRouteEnter` 可以使用的组件场景,进行了三种情况的测试;
+
+* 在 `router.js` 内引用的组件,**可以使用**
+* 在 `router.js` 内引用的组件的子组件,**无法使用!**
+* 没有在 `router.js` 内引用的组件,**无法使用!**
+
+因此,只有在 `router.js` 里直接引用的组件实例内可以使用 `beforeRouteEnter`等组件钩子方法,其他组件内都不能使用,菜单组件属于没有被 `router.js` 引用的组件,因此只能采用其他方式调用 `currentHighlight` 方法;
+
+#### 解决
+在被复用组件内,使用 `watch` 观察 `$route` 的变化,触发 `currentHighlight` 方法,可以解决
+
+```javascript
+watch: {
+        $route() {
+            this.currentHighlight();
+        },
+    },
+```
+
+参考:[Vue Router官方说明](https://router.vuejs.org/zh/guide/essentials/dynamic-matching.html#响应路由参数的变化)
+
+### 3. 路由组件传参
+
+常见路由传参:
+
+```javascript
+const User = {
+  template: '<div>User {{ $route.params.id }}</div>'
+}
+
+const router = new VueRouter({
+  routes: [
+    { path: '/user/:id', component: User },
+  ]
+})
+```
+
+组件和路由高度耦合,使用id时必须由通过路由获取。
+
+通过`props`解耦:
+
+```javascript
+const User = {
+  props: ['id'],
+  template: '<div>User {{ id }}</div>'
+}
+
+const router = new VueRouter({
+  routes: [
+    { path: '/user/:id', component: User, props: true },
+  ]
+})
+```
+
+其它组件也可通过传入id的方式使用`User`组件。
+
+### 4. 组件设置inheritAttrs为false
+
+默认情况下父作用域的不被认作 props 的特性绑定(即没有被子组件继承)将会“回退”且作为普通的 HTML 特性应用在子组件的根元素上。
+
+```javascript
+const User = {
+  props: ['id'],
+  template: '<div>User {{ id }}</div>'
+}
+
+<User :id="0" :hahaha="'hahaha'" />
+```
+
+此时`hahaha`属性会显示在User组件的根元素上,为避免如此,设置`inheritAttrs`为`false`。如果想在子元素中获得这些额外属性,通过`$attr`获取。
+
+### 5. router-link的生成LI标签
+
+router-link默认渲染成带有正确链接的 <a> 标签,可以通过配置 tag 属性生成别的标签。
+
+```javascript
+<router-link tag="li" to="{name: 'foo'}">
+  <a>/foo</a>
+</router-link>
+```
+在这种情况下,<a> 将作为真实的链接 (它会获得正确的 href 的),而 "激活时的CSS类名" 则设置到外层的 LI。
+
+
+

+ 437 - 0
doc/rule.md

@@ -0,0 +1,437 @@
+# 代码整体原则
+
+## 组件设计
+
+### 原则
+1. 将组件设计为“黑盒子”,即纯组件,外部对其使用依赖于确定值。
+
+### 规范
+1. 始终使用 kebab-case 的事件名([原因](https://cn.vuejs.org/v2/guide/components-custom-events.html#事件名))
+2. 超过100行的Vue文件应拆分为独立的js,css,html文件;
+3. 事件类型统一添加`on-`前缀,即`this.$emit('on-success', value)`;
+4. 事件处理器统一添加`handle`前缀,即`@on-success="handleUpdateNameSuccess"`;
+
+---
+
+## `v-model` 使用原则
+1. 尽量不要使用,`v-model` 的黑魔法是从组件的黑盒子内部对一个值的“突变”,存在不确定性和不可控性;
+2. 不确定性是无法确定值的改变是由外部代码操控,还是组件内部触控;
+3. 不可控性跟不确定性类似,组件的黑盒子内部可以不受外部控制的改变值,而这个值有可能会对外部的程序产生副作用。
+
+### `v-model` 的合适使用场景
+1. 表单型组件(比如`i-input`)的处理,在该场景下,表单组件的`value`是在外部明确期望获取的,而且明确由组件内部输入控制的,是对`onChange`的简写形式(本质上:期待的数据流向是明确的,从内部流向外部)。
+2. 组件可完全控制的场景。
+
+    > 一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件(https://cn.vuejs.org/v2/guide/components-custom-events.html#自定义组件的-v-model)
+
+#### 示例代码
+
+```javascript
+// 父组件
+<tai-slider
+    v-model="form.disk" // form表单期望获取的值
+    :min="20"
+    :max="1000"
+/>
+
+// 子组件
+<div class="tai-form-slider">
+    <div class="tai-slider-content">
+        <i-slider
+            :value="innerValue"  // 重新定义的值
+            :min="minSize"
+            :max="maxSize"
+            :step="step"
+            name="disk"
+            show-tip="hover"
+            class="tai-slider"
+            @on-input="handleChangeDisk"
+        />
+        <i-input-number
+            :value="innerValue"
+            :min="minSize"
+            :max="maxSize"
+            :step="step"
+            @on-change="handleChangeDisk"
+            @on-blur="handleBlurInput"
+        />GB
+    </div>
+    <div class="tai-slider-tip">
+        <span class="min-size">{{ minSize }}GB</span>
+        <span class="max-size">{{ maxSize }}GB</span>
+        <span class="ram-tip">存储空间{{ minSize }}GB~{{ maxSize }}GB</span>
+    </div>
+</div>
+
+// watch中监听,将传入的value值赋值给新定义的值,避免直接影响操作外部值。
+value() {
+    this.innerValue = this.value;
+},
+```
+
+### `.sync`和`v-model`的区别
+1. 暂时没有想到两者严格的差异🤪;
+2. 表面上看只是`$emit 'input'`和`$emit 'update'`的区别([.sync](https://cn.vuejs.org/v2/guide/components-custom-events.html#sync-修饰符))。
+
+#### 参考
+1. [通过事件向父级组件发送消息](https://cn.vuejs.org/v2/guide/components.html#通过事件向父级组件发送消息)
+2. [自定义事件](https://cn.vuejs.org/v2/guide/components-custom-events.html)
+
+---
+
+## 路由设计
+
+### 原则
+1. 将路由简化抽象为[RESTful](https://zh.wikipedia.org/wiki/表现层状态转换)的URL,即每一个URL可独立启动对应应用程序的状态和功能;
+2. 无状态,刷新浏览器后可完整还原页面的状态和功能,不依赖于之前的页面操作。
+
+### 规范
+1. 均通过一个名称来标识一个‘叶子路由’,名称(name)严格使用camelCase格式,遵循module.nested.routeName的命名规范;
+2. 路径(path)全小写,和文件的命名格式保持一致,使用kebab-case格式;
+3. 路由统一使用描述地址的对象执行跳转,在`:to`和编程两种形式中保持一致。
+4. 路由打包名字(webpackChunkName)设置唯一
+
+#### 代码示例
+
+```
+export default {
+    path: '/ecs-rabbit',
+    name: 'ecs-rabbit',
+    component: () => import(/* webpackChunkName: "ecs-rabbit" */ './app.vue'),
+    children: [
+        {
+            // 注意路由之间会相互冲突
+            path: ':page(\\d+)?/:keyword?',
+            component: () => import(/* webpackChunkName: "ecs-rabbit" */ './views/index.vue'),
+        },
+        {
+            path: 'detail/:id',
+            component: () => import(/* webpackChunkName: "ecs-rabbit" */ './views/detail.vue'),
+        },
+    ],
+};
+```
+
+### 简化路由设计
+1. 用query代替path中嵌套的查询参数,直观显示路由结构
+
+#### 路由更新代码示例(原路由设计如上)
+```
+export default {
+    path: '/ecs-rabbit',
+    name: 'ecs-rabbit',
+    component: () => import(/* webpackChunkName: "ecs-rabbit" */ './app.vue'),
+    children: [
+        {
+            // 注意路由之间会相互冲突
+            path: '',
+            component: () => import(/* webpackChunkName: "ecs-rabbit" */ './views/index.vue'),
+        },
+        {
+            path: ':id',
+            component: () => import(/* webpackChunkName: "ecs-rabbit" */ './views/detail.vue'),
+        },
+    ],
+};
+
+```
+
+#### 取参数代码示例
+```
+// 原先数据获取参数page
+this.page.current = parseInt(this.$route.params.page, 10) || 1;
+
+// 更新路由后获取参数page
+this.page.current = parseInt(this.$route.query.page, 10) || 1;
+```
+
+#### 传参时代码示例
+```
+// 原先传参,直接在路由中传递(如:http://localhost:8080/console/ecs-rabbit/1/test )
+this.$router.push(`/ecs-rabbit/${page}/${this.keyword}`);
+
+// 更新之后传参,作为参数传递(如:http://localhost:8080/console/ecs-rabbit?page=1&keyword=test )
+this.$router.push({
+    path: '/ecs-rabbit',
+    query: {
+        page,
+        keyword: this.keyword,
+    },
+});
+```
+
+---
+## Store 设计
+
+### 原则
+1. 保持单向数据流;
+2. 使用namespace 隔离各个功能状态;
+3. 从模型属性衍生出的视图模型属性,使用computed(getter)表达。
+
+### 规范
+1. strict设置为true;
+2. namespace设置为true;
+3. 当使用 mapState, mapGetters, mapActions 和 mapMutations 这些函数来绑定带命名空间的模块时,将模块的空间名称字符串作为第一个参数传递给上述函数;
+
+---
+
+## Request
+
+### 原则
+使用代理磨平开发、测试和生产环境的差异,直接使用最终的Path发起请求;
+
+---
+## Filter
+
+### 原则
+定义一套标准来过滤一组对象;
+
+### 规范
+1. 脱离业务逻辑而抽象出来的公用的过滤器(例如时间的格式化过滤器),放在common的filter文件夹中并且全局定义过滤器;
+2. 在业务模块中,过滤器定义在模块的filter文件中。需要使用过滤器的组件,引用filter文件并在组件中定义本地的过滤器;
+
+---
+
+## 文件及代码命名规范
+
+### 文件夹命名规范
+
+1. 文件夹一律采用全小写中杠分隔的形式,例如 `ecs-seahare` 进行命名。
+
+### 文件命名规范
+
+1. 文件一律采用全小写中杠分隔的形式,例如 `toggle-password.vue` 进行命名
+
+### 代码命名规范
+
+1. 输出和注册组件一律采用大写驼峰形式,例如`LoginTemplate`;
+2. 使用组件时一律采用全小写中杠分隔的形式,例如`<login-template></login-template>`;
+3. 在书写 html 时,为区分`iview`自定义的标签和`html`原生标签,需要将`iview`标签写成带有`i-`前缀的形式。如:`<i-menu-item></i-menu-item>`;
+4. `import`vue文件组件时必须添加`vue`后缀(原因:webpack无法区分同名的.js和.vue文件,两者会产生不确定随机引用,有可能造成文件入口错误);
+5. `import`频道(子App)内文件用相对路径,频道外文件用绝对路径(`'@/common/utils/request'`);
+
+#### 遗留待考虑问题
+
+1. 项目中js、css以及Vue文件是否需要进行文件夹归属拆分
+
+---
+
+## API接口
+
+1. 所有的API接口字段信息保持一致;
+2. 时间统一以毫秒级别的形式进行传递,前端进行对时间数据的处理;
+3. API接口信息中不用的字段不应该传递;
+4. API接口字段尽量返回数据库原始数据,保持数据的可复用性以及原始性;
+
+### API处理
+1. 尽量减少接口的请求。能复用的请求可以直接复用。最好定义API时候保持API的纯粹性
+
+---
+
+## URL使用
+
+### 自动扩展URL
+
+1. 用户在浏览器或者地址href中不需要输入完整的URL地址,浏览器会自动扩展。
+2. 协议的自动扩展是为了兼容http和https两种协议。
+
+```html
+// 如下href地址
+<li><a href="//zhiyucloud.facethink.com/tools/OSSClientSetup.exe">客户端工具</a></li>
+```
+#### 引申:自动扩展两种方式
+
+##### 1:主机名扩展
+
+浏览器通常可以在没有帮助的情况下,将主机名扩展为完整的主机名,比如yahoo会自动构建为www.yahoo.com。
+如果找不到与yahoo匹配的站点,有些浏览器会在放弃之前尝试几种扩展方式。
+浏览器通过简单的技巧来节省你的时间,减少找不到的可能。
+
+##### 2:历史扩展:
+
+浏览器将以前用户访问过的URL历史存储起来,当输入的URL与历史记录的前缀匹配,就会提供一些完整的选项供你选择。
+如用户输入http时下拉框就会出现很多匹配链接。
+
+---
+
+## 时间处理规范
+
+1. 时间一律使用luxon来进行处理
+
+---
+
+## 接口错误处理
+
+一个页面有时可能会涉及到多个接口同时请求,对着这种请求,我们需要对接口信息进行多种情况的判断,避免页面强制刷新带来重复请求
+
+目前考虑到的有`请求中`、`请求成功有数据`、`请求成功无数据`、`请求失败`这四种情况
+
+---
+
+## 模式
+
+### widget模式
+widget 模式名字借鉴于 [Web Widget](https://zh.wikipedia.org/wiki/Web_Widget),与其含义基本相同,表示小部件、小工具、挂件等,包含可在任意地方直接调用并执行的含义。
+
+在一个页面中可以存在多个Vue实例,将独立小组件封装成单独Vue的实例,并提供函数的调用接口,即widget。
+
+需要使用的时候即直接调用,本质上是在DOM种插入新的Vue实例,通过回调或Promise的形式完成数据的传递。
+
+### 解决的问题
+在典型的Vue架构中,UI组件的展示是对组件树中某个组件的状态改变(v-if、v-show等),对于跟业务代码执行某个小分叉逻辑有关的独立小组件(比如 Alert、Confirm、Prompt、Tip 等)有些刻板和沉重,在这种情况下,更有利于调用函数等形式会更便于使用。
+
+由于减少组件树中此类组件的占位,也可以优化程序的性能。
+
+---
+
+## 常用自定义指令
+
+### 1. 常用自定义过滤器`dtf`
+
+##### 作用
+格式化时间字符串。
+
+##### 使用方法
+`dtf`或`dtf('yyyy-MM-dd')`,[API参考](https://moment.github.io/luxon/docs/manual/formatting.html#formatting-with-tokens--strings-for-cthulhu-)。
+
+### 2. `v-timely-validate`
+
+##### 表单的构成
+通常表单共有 label、input(包含radio、checkbox、select、slider等)、placeholder、help-tip、error-message 5部分。
+
+其中 error-message 可以看做 help-tip的加强版,是为了给用户更强烈的提示作用,促进用户更准确的填写表单。所以,error-message 应该是 help-tip 的拆解,按验证规则优先级顺序给用户更准确、更具体的错误信息。
+
+在上面的前提下,为了简化用户所看到的表单结构,error-message 覆盖掉 help-tip,两类信息不同时出现。
+
+##### 作用
+将表单验证规则`rules`中验证的触发时机从`blur`修改为`change`,以及时消除错误。
+
+##### 表单校验原则
+1. 尽量不强行阻止用户(比如`text`表单元素中不截断用户的超长度输入,`number`表单元素中不阻止用户输入字符等,及时提醒就好);
+2. 及时提醒用户出错;
+3. 不打扰用户正常输入(比如第一次输入用`blur`时机校验);
+4. 尽早消除错误提醒(比如有了出错提醒后用`change`时机校验);
+
+##### 使用方法
+1. 在`i-form`表单组件中添加`v-timely-validate`指令;
+2. 在`i-input`等表单元素组件中添加跟`rules`对应的`name`属性;
+3. 在`i-form`的`rules`中使用`blur`触发;
+4. 在`i-form-item`中添加 help-tip 元素,并为该元素添加`tai-form-item-help-tip`类(借助了`i-input`所隐藏的状态机)。
+
+##### 代码示例
+
+```html
+<i-form
+    v-timely-validate
+    ref="iForm"
+    :model="form"
+    :rules="rules"
+    @submit.native.prevent="handleSubmit"
+>
+    <i-form-item label="用户账号" prop="account">
+        <i-input v-model="form.account" name="account" placeholder="请输入用户账号" />
+        <p class="form-tip-name form-tip">由小写字母,数字,下划线组成,字母开头,最长16个字符</p>
+    </i-form-item>
+</i-form>
+```
+
+---
+
+## QA
+
+### 1. template中只包含简单的表达式
+#### 原因
+复杂表达式会让你的模板过重且难以维护。我们应该尽量描述应该出现的是什么,而非如何计算那个值。
+
+```javascript
+    {{
+        fullName.split(' ').map(function (word) {
+        return word[0].toUpperCase() + word.slice(1)
+        }).join(' ')
+    }}
+```
+#### 解决方法
+尽量写成有语义的计算属性或方法、filter,从而使得代码可以重用
+
+```javascript
+    computed: {
+        normalizedFullName: function () {
+            return this.fullName.split(' ').map(function (word) {
+                return word[0].toUpperCase() + word.slice(1)
+            }).join(' ')
+        }
+    }
+```
+
+### 2. 项目文件夹层级过多
+
+#### 原因
+文件夹的设置习惯于参照产品菜单来创建,菜单出现三级子菜单时,依照现有的文件夹建立方式会出现7级嵌套,不利于维护开发;
+
+#### 解决方法
+跳出原有的思维限制,按照具体项目来建立一级文件夹,避免过深的文件夹层级嵌套,这样也有利于路由的配置简化;
+
+通过路径规划设置,让配置更简练,屏蔽请求路径差异
+
+### 3. 样式维护问题
+
+#### 场景
+局部组件的class因为恰好和其他页面的class重名,结果导致样式覆盖,影响了其他页面的展示
+
+#### 解决
+局部组件内的class应该只定义组件相关样式,必须加上`scope`限制作用域,如果有需要复用的class,应当提取到公共css内
+
+
+### 4. package.json升级注意事项
+当第一次 `git clone`项目到本地时,要先执行 `npm install` 安装依赖包,保证`node_modules`中包的完整性,然后执行 `npm update`, 如果在没安装依赖包时直接执行 `npm update`,会导致 `package.json` 没有自动更新
+
+### 5. `style`标签中必须添加`lang`属性
+
+原因:否则有可能会报错(升级`vue-cli@3.0.5`版本是个暴露点,但应该不是这个原因)。
+
+### 6. 资源在js中通过import引用
+
+##### 场景
+在data中或通过方法对资源的地址赋值,无法引用到该资源。
+
+##### 原因
+在template模版里的路径会被webpack解析打包,而放在data和method中的路径,只是一个字符串,webpack不会处理。
+
+##### 解决方案
+通过import或required引入。
+
+### 7. 环境变量及模式
+
+#### 解决问题
+发版过程中存在很多的发版环境,例如测试环境、开发环境等,我们需要根据不同的环境做相应的调整。
+#### 解决方法
+1.增加模式 test
+vue-cli-service build --mode test 会在 test 模式下加载可能存在的 .env、.env.test 和 .env.test.local 文件然后构建出生产环境应用。
+env.test
+
+```
+NODE_ENV=production
+VUE_APP_API_BASE_URL=http://39.107.112.223:8008/
+```
+
+2.增加变量 test
+以 VUE_APP_ 开头的变量会被 webpack.DefinePlugin 静态嵌入到客户端侧的包中
+VUE_APP_SERVER_MODE=test vue-cli-service build --modern --mode production
+
+### 8. 方法参数的传递,优先使用对象格式。
+####原因:
+1. 便于参数的扩展;
+2. 有一定的自解释性;
+3. 对于顺序不要求
+
+
+
+
+
+
+
+
+
+
+
+

File diff suppressed because it is too large
+ 221 - 0
doc/vue.md


+ 5 - 0
jsconfig.json

@@ -0,0 +1,5 @@
+{
+    "compilerOptions": {
+        "experimentalDecorators": true,
+    }
+}

File diff suppressed because it is too large
+ 13938 - 0
package-lock.json


+ 56 - 0
package.json

@@ -0,0 +1,56 @@
+{
+  "name": "nuoda-oa-fe",
+  "version": "1.1.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve --mode test",
+    "build": "vue-cli-service build --mode production",
+    "dev-build": "vue-cli-service build --mode development",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "@aximario/json-tree": "^2.1.0",
+    "@babel/polyfill": "^7.6.0",
+    "@tinymce/tinymce-vue": "^3.0.1",
+    "axios": "^0.19.0",
+    "chalk": "^2.4.2",
+    "crypto-js": "^3.1.9-1",
+    "echarts": "^4.8.0",
+    "element-ui": "^2.13.0",
+    "file-saver": "^2.0.2",
+    "jsplumb": "^2.11.2",
+    "lodash": "^4.17.15",
+    "luxon": "^1.21.2",
+    "nprogress": "^0.2.0",
+    "nzh": "^1.0.4",
+    "qs": "^6.8.0",
+    "tinymce": "^5.0.16",
+    "vue": "^2.6.10",
+    "vue-amap": "^0.5.10",
+    "vue-barcode": "^1.2.0",
+    "vue-class-component": "^7.1.0",
+    "vue-cookies": "^1.5.13",
+    "vue-router": "^3.1.2",
+    "vuex": "^3.1.1",
+    "wl-vue-select": "^0.5.2",
+    "xlsx": "^0.15.5",
+    "xss": "^1.0.6"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "^3.11.0",
+    "@vue/cli-plugin-eslint": "^3.11.0",
+    "@vue/cli-service": "^3.11.0",
+    "@vue/eslint-config-airbnb": "^4.0.1",
+    "babel-core": "7.0.0-bridge.0",
+    "babel-eslint": "^10.0.3",
+    "babel-plugin-component": "^1.1.1",
+    "copy-webpack-plugin": "^5.0.4",
+    "eslint": "^5.16.0",
+    "eslint-plugin-vue": "^5.2.3",
+    "node-sass": "^4.14.1",
+    "sass-loader": "^7.3.1",
+    "script-loader": "^0.7.2",
+    "uglifyjs-webpack-plugin": "^2.2.0",
+    "vue-template-compiler": "^2.6.10"
+  }
+}

+ 5 - 0
postcss.config.js

@@ -0,0 +1,5 @@
+module.exports = {
+    plugins: {
+        autoprefixer: {},
+    },
+};

BIN
public/favicon.png


+ 19 - 0
public/index.html

@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8">
+        <meta name="renderer" content="webkit">
+        <meta http-equiv="pragma" content="no-cache">
+        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+        <meta name="viewport" content="width=device-width,initial-scale=1.0">
+        <link rel="icon" href="<%= BASE_URL %>favicon.png">
+        <title>北京京宜安科技有限公司</title>
+    </head>
+    <body>
+        <noscript>
+            <strong>We're sorry but OA doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+        </noscript>
+        <div id="app"></div>
+        <!-- built files will be auto injected -->
+    </body>
+</html>

BIN
public/lessee.xlsx


+ 389 - 0
public/tinymce/langs/zh_CN.js

@@ -0,0 +1,389 @@
+tinymce.addI18n('zh_CN',{
+"Redo": "\u91cd\u505a",
+"Undo": "\u64a4\u9500",
+"Cut": "\u526a\u5207",
+"Copy": "\u590d\u5236",
+"Paste": "\u7c98\u8d34",
+"Select all": "\u5168\u9009",
+"New document": "\u65b0\u6587\u4ef6",
+"Ok": "\u786e\u5b9a",
+"Cancel": "\u53d6\u6d88",
+"Visual aids": "\u7f51\u683c\u7ebf",
+"Bold": "\u7c97\u4f53",
+"Italic": "\u659c\u4f53",
+"Underline": "\u4e0b\u5212\u7ebf",
+"Strikethrough": "\u5220\u9664\u7ebf",
+"Superscript": "\u4e0a\u6807",
+"Subscript": "\u4e0b\u6807",
+"Clear formatting": "\u6e05\u9664\u683c\u5f0f",
+"Align left": "\u5de6\u8fb9\u5bf9\u9f50",
+"Align center": "\u4e2d\u95f4\u5bf9\u9f50",
+"Align right": "\u53f3\u8fb9\u5bf9\u9f50",
+"Justify": "\u4e24\u7aef\u5bf9\u9f50",
+"Bullet list": "\u9879\u76ee\u7b26\u53f7",
+"Numbered list": "\u7f16\u53f7\u5217\u8868",
+"Decrease indent": "\u51cf\u5c11\u7f29\u8fdb",
+"Increase indent": "\u589e\u52a0\u7f29\u8fdb",
+"Close": "\u5173\u95ed",
+"Formats": "\u683c\u5f0f",
+"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u4f60\u7684\u6d4f\u89c8\u5668\u4e0d\u652f\u6301\u6253\u5f00\u526a\u8d34\u677f\uff0c\u8bf7\u4f7f\u7528Ctrl+X\/C\/V\u7b49\u5feb\u6377\u952e\u3002",
+"Headers": "\u6807\u9898",
+"Header 1": "\u6807\u98981",
+"Header 2": "\u6807\u98982",
+"Header 3": "\u6807\u98983",
+"Header 4": "\u6807\u98984",
+"Header 5": "\u6807\u98985",
+"Header 6": "\u6807\u98986",
+"Headings": "\u6807\u9898",
+"Heading 1": "\u6807\u98981",
+"Heading 2": "\u6807\u98982",
+"Heading 3": "\u6807\u98983",
+"Heading 4": "\u6807\u98984",
+"Heading 5": "\u6807\u98985",
+"Heading 6": "\u6807\u98986",
+"Preformatted": "\u9884\u5148\u683c\u5f0f\u5316\u7684",
+"Div": "Div",
+"Pre": "Pre",
+"Code": "\u4ee3\u7801",
+"Paragraph": "\u6bb5\u843d",
+"Blockquote": "\u5f15\u6587\u533a\u5757",
+"Inline": "\u6587\u672c",
+"Blocks": "\u57fa\u5757",
+"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u5f53\u524d\u4e3a\u7eaf\u6587\u672c\u7c98\u8d34\u6a21\u5f0f\uff0c\u518d\u6b21\u70b9\u51fb\u53ef\u4ee5\u56de\u5230\u666e\u901a\u7c98\u8d34\u6a21\u5f0f\u3002",
+"Fonts": "\u5b57\u4f53",
+"Font Sizes": "\u5b57\u53f7",
+"Class": "\u7c7b\u578b",
+"Browse for an image": "\u6d4f\u89c8\u56fe\u50cf",
+"OR": "\u6216",
+"Drop an image here": "\u62d6\u653e\u4e00\u5f20\u56fe\u50cf\u81f3\u6b64",
+"Upload": "\u4e0a\u4f20",
+"Block": "\u5757",
+"Align": "\u5bf9\u9f50",
+"Default": "\u9ed8\u8ba4",
+"Circle": "\u7a7a\u5fc3\u5706",
+"Disc": "\u5b9e\u5fc3\u5706",
+"Square": "\u65b9\u5757",
+"Lower Alpha": "\u5c0f\u5199\u82f1\u6587\u5b57\u6bcd",
+"Lower Greek": "\u5c0f\u5199\u5e0c\u814a\u5b57\u6bcd",
+"Lower Roman": "\u5c0f\u5199\u7f57\u9a6c\u5b57\u6bcd",
+"Upper Alpha": "\u5927\u5199\u82f1\u6587\u5b57\u6bcd",
+"Upper Roman": "\u5927\u5199\u7f57\u9a6c\u5b57\u6bcd",
+"Anchor...": "\u951a\u70b9...",
+"Name": "\u540d\u79f0",
+"Id": "\u6807\u8bc6\u7b26",
+"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u6807\u8bc6\u7b26\u5e94\u8be5\u4ee5\u5b57\u6bcd\u5f00\u5934\uff0c\u540e\u8ddf\u5b57\u6bcd\u3001\u6570\u5b57\u3001\u7834\u6298\u53f7\u3001\u70b9\u3001\u5192\u53f7\u6216\u4e0b\u5212\u7ebf\u3002",
+"You have unsaved changes are you sure you want to navigate away?": "\u4f60\u8fd8\u6709\u6587\u6863\u5c1a\u672a\u4fdd\u5b58\uff0c\u786e\u5b9a\u8981\u79bb\u5f00\uff1f",
+"Restore last draft": "\u6062\u590d\u4e0a\u6b21\u7684\u8349\u7a3f",
+"Special characters...": "\u7279\u6b8a\u5b57\u7b26...",
+"Source code": "\u6e90\u4ee3\u7801",
+"Insert\/Edit code sample": "\u63d2\u5165\/\u7f16\u8f91\u4ee3\u7801\u793a\u4f8b",
+"Language": "\u8bed\u8a00",
+"Code sample...": "\u793a\u4f8b\u4ee3\u7801...",
+"Color Picker": "\u9009\u8272\u5668",
+"R": "R",
+"G": "G",
+"B": "B",
+"Left to right": "\u4ece\u5de6\u5230\u53f3",
+"Right to left": "\u4ece\u53f3\u5230\u5de6",
+"Emoticons...": "\u8868\u60c5\u7b26\u53f7...",
+"Metadata and Document Properties": "\u5143\u6570\u636e\u548c\u6587\u6863\u5c5e\u6027",
+"Title": "\u6807\u9898",
+"Keywords": "\u5173\u952e\u8bcd",
+"Description": "\u63cf\u8ff0",
+"Robots": "\u673a\u5668\u4eba",
+"Author": "\u4f5c\u8005",
+"Encoding": "\u7f16\u7801",
+"Fullscreen": "\u5168\u5c4f",
+"Action": "\u64cd\u4f5c",
+"Shortcut": "\u5feb\u6377\u952e",
+"Help": "\u5e2e\u52a9",
+"Address": "\u5730\u5740",
+"Focus to menubar": "\u79fb\u52a8\u7126\u70b9\u5230\u83dc\u5355\u680f",
+"Focus to toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u5de5\u5177\u680f",
+"Focus to element path": "\u79fb\u52a8\u7126\u70b9\u5230\u5143\u7d20\u8def\u5f84",
+"Focus to contextual toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u4e0a\u4e0b\u6587\u83dc\u5355",
+"Insert link (if link plugin activated)": "\u63d2\u5165\u94fe\u63a5 (\u5982\u679c\u94fe\u63a5\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
+"Save (if save plugin activated)": "\u4fdd\u5b58(\u5982\u679c\u4fdd\u5b58\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
+"Find (if searchreplace plugin activated)": "\u67e5\u627e(\u5982\u679c\u67e5\u627e\u66ff\u6362\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
+"Plugins installed ({0}):": "\u5df2\u5b89\u88c5\u63d2\u4ef6 ({0}):",
+"Premium plugins:": "\u4f18\u79c0\u63d2\u4ef6\uff1a",
+"Learn more...": "\u4e86\u89e3\u66f4\u591a...",
+"You are using {0}": "\u4f60\u6b63\u5728\u4f7f\u7528 {0}",
+"Plugins": "\u63d2\u4ef6",
+"Handy Shortcuts": "\u5feb\u6377\u952e",
+"Horizontal line": "\u6c34\u5e73\u5206\u5272\u7ebf",
+"Insert\/edit image": "\u63d2\u5165\/\u7f16\u8f91\u56fe\u7247",
+"Image description": "\u56fe\u7247\u63cf\u8ff0",
+"Source": "\u5730\u5740",
+"Dimensions": "\u5927\u5c0f",
+"Constrain proportions": "\u4fdd\u6301\u7eb5\u6a2a\u6bd4",
+"General": "\u666e\u901a",
+"Advanced": "\u9ad8\u7ea7",
+"Style": "\u6837\u5f0f",
+"Vertical space": "\u5782\u76f4\u8fb9\u8ddd",
+"Horizontal space": "\u6c34\u5e73\u8fb9\u8ddd",
+"Border": "\u8fb9\u6846",
+"Insert image": "\u63d2\u5165\u56fe\u7247",
+"Image...": "\u56fe\u7247...",
+"Image list": "\u56fe\u7247\u5217\u8868",
+"Rotate counterclockwise": "\u9006\u65f6\u9488\u65cb\u8f6c",
+"Rotate clockwise": "\u987a\u65f6\u9488\u65cb\u8f6c",
+"Flip vertically": "\u5782\u76f4\u7ffb\u8f6c",
+"Flip horizontally": "\u6c34\u5e73\u7ffb\u8f6c",
+"Edit image": "\u7f16\u8f91\u56fe\u7247",
+"Image options": "\u56fe\u7247\u9009\u9879",
+"Zoom in": "\u653e\u5927",
+"Zoom out": "\u7f29\u5c0f",
+"Crop": "\u88c1\u526a",
+"Resize": "\u8c03\u6574\u5927\u5c0f",
+"Orientation": "\u65b9\u5411",
+"Brightness": "\u4eae\u5ea6",
+"Sharpen": "\u9510\u5316",
+"Contrast": "\u5bf9\u6bd4\u5ea6",
+"Color levels": "\u989c\u8272\u5c42\u6b21",
+"Gamma": "\u4f3d\u9a6c\u503c",
+"Invert": "\u53cd\u8f6c",
+"Apply": "\u5e94\u7528",
+"Back": "\u540e\u9000",
+"Insert date\/time": "\u63d2\u5165\u65e5\u671f\/\u65f6\u95f4",
+"Date\/time": "\u65e5\u671f\/\u65f6\u95f4",
+"Insert\/Edit Link": "\u63d2\u5165\/\u7f16\u8f91\u94fe\u63a5",
+"Insert\/edit link": "\u63d2\u5165\/\u7f16\u8f91\u94fe\u63a5",
+"Text to display": "\u663e\u793a\u6587\u5b57",
+"Url": "\u5730\u5740",
+"Open link in...": "\u94fe\u63a5\u6253\u5f00\u4f4d\u7f6e...",
+"Current window": "\u5f53\u524d\u7a97\u53e3",
+"None": "\u65e0",
+"New window": "\u5728\u65b0\u7a97\u53e3\u6253\u5f00",
+"Remove link": "\u5220\u9664\u94fe\u63a5",
+"Anchors": "\u951a\u70b9",
+"Link...": "\u94fe\u63a5...",
+"Paste or type a link": "\u7c98\u8d34\u6216\u8f93\u5165\u94fe\u63a5",
+"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u4e3a\u90ae\u4ef6\u5730\u5740\uff0c\u9700\u8981\u52a0\u4e0amailto:\u524d\u7f00\u5417\uff1f",
+"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u5c5e\u4e8e\u5916\u90e8\u94fe\u63a5\uff0c\u9700\u8981\u52a0\u4e0ahttp:\/\/:\u524d\u7f00\u5417\uff1f",
+"Link list": "\u94fe\u63a5\u5217\u8868",
+"Insert video": "\u63d2\u5165\u89c6\u9891",
+"Insert\/edit video": "\u63d2\u5165\/\u7f16\u8f91\u89c6\u9891",
+"Insert\/edit media": "\u63d2\u5165\/\u7f16\u8f91\u5a92\u4f53",
+"Alternative source": "\u955c\u50cf",
+"Alternative source URL": "\u66ff\u4ee3\u6765\u6e90\u7f51\u5740",
+"Media poster (Image URL)": "\u5c01\u9762(\u56fe\u7247\u5730\u5740)",
+"Paste your embed code below:": "\u5c06\u5185\u5d4c\u4ee3\u7801\u7c98\u8d34\u5728\u4e0b\u9762:",
+"Embed": "\u5185\u5d4c",
+"Media...": "\u591a\u5a92\u4f53...",
+"Nonbreaking space": "\u4e0d\u95f4\u65ad\u7a7a\u683c",
+"Page break": "\u5206\u9875\u7b26",
+"Paste as text": "\u7c98\u8d34\u4e3a\u6587\u672c",
+"Preview": "\u9884\u89c8",
+"Print...": "\u6253\u5370...",
+"Save": "\u4fdd\u5b58",
+"Find": "\u67e5\u627e",
+"Replace with": "\u66ff\u6362\u4e3a",
+"Replace": "\u66ff\u6362",
+"Replace all": "\u5168\u90e8\u66ff\u6362",
+"Previous": "\u4e0a\u4e00\u4e2a",
+"Next": "\u4e0b\u4e00\u4e2a",
+"Find and replace...": "\u67e5\u627e\u5e76\u66ff\u6362...",
+"Could not find the specified string.": "\u672a\u627e\u5230\u641c\u7d22\u5185\u5bb9.",
+"Match case": "\u533a\u5206\u5927\u5c0f\u5199",
+"Find whole words only": "\u5168\u5b57\u5339\u914d",
+"Spell check": "\u62fc\u5199\u68c0\u67e5",
+"Ignore": "\u5ffd\u7565",
+"Ignore all": "\u5168\u90e8\u5ffd\u7565",
+"Finish": "\u5b8c\u6210",
+"Add to Dictionary": "\u6dfb\u52a0\u5230\u5b57\u5178",
+"Insert table": "\u63d2\u5165\u8868\u683c",
+"Table properties": "\u8868\u683c\u5c5e\u6027",
+"Delete table": "\u5220\u9664\u8868\u683c",
+"Cell": "\u5355\u5143\u683c",
+"Row": "\u884c",
+"Column": "\u5217",
+"Cell properties": "\u5355\u5143\u683c\u5c5e\u6027",
+"Merge cells": "\u5408\u5e76\u5355\u5143\u683c",
+"Split cell": "\u62c6\u5206\u5355\u5143\u683c",
+"Insert row before": "\u5728\u4e0a\u65b9\u63d2\u5165",
+"Insert row after": "\u5728\u4e0b\u65b9\u63d2\u5165",
+"Delete row": "\u5220\u9664\u884c",
+"Row properties": "\u884c\u5c5e\u6027",
+"Cut row": "\u526a\u5207\u884c",
+"Copy row": "\u590d\u5236\u884c",
+"Paste row before": "\u7c98\u8d34\u5230\u4e0a\u65b9",
+"Paste row after": "\u7c98\u8d34\u5230\u4e0b\u65b9",
+"Insert column before": "\u5728\u5de6\u4fa7\u63d2\u5165",
+"Insert column after": "\u5728\u53f3\u4fa7\u63d2\u5165",
+"Delete column": "\u5220\u9664\u5217",
+"Cols": "\u5217",
+"Rows": "\u884c",
+"Width": "\u5bbd",
+"Height": "\u9ad8",
+"Cell spacing": "\u5355\u5143\u683c\u5916\u95f4\u8ddd",
+"Cell padding": "\u5355\u5143\u683c\u5185\u8fb9\u8ddd",
+"Show caption": "\u663e\u793a\u6807\u9898",
+"Left": "\u5de6\u5bf9\u9f50",
+"Center": "\u5c45\u4e2d",
+"Right": "\u53f3\u5bf9\u9f50",
+"Cell type": "\u5355\u5143\u683c\u7c7b\u578b",
+"Scope": "\u8303\u56f4",
+"Alignment": "\u5bf9\u9f50\u65b9\u5f0f",
+"H Align": "\u6c34\u5e73\u5bf9\u9f50",
+"V Align": "\u5782\u76f4\u5bf9\u9f50",
+"Top": "\u9876\u90e8\u5bf9\u9f50",
+"Middle": "\u5782\u76f4\u5c45\u4e2d",
+"Bottom": "\u5e95\u90e8\u5bf9\u9f50",
+"Header cell": "\u8868\u5934\u5355\u5143\u683c",
+"Row group": "\u884c\u7ec4",
+"Column group": "\u5217\u7ec4",
+"Row type": "\u884c\u7c7b\u578b",
+"Header": "\u8868\u5934",
+"Body": "\u8868\u4f53",
+"Footer": "\u8868\u5c3e",
+"Border color": "\u8fb9\u6846\u989c\u8272",
+"Insert template...": "\u63d2\u5165\u6a21\u677f...",
+"Templates": "\u6a21\u677f",
+"Template": "\u6a21\u677f",
+"Text color": "\u6587\u5b57\u989c\u8272",
+"Background color": "\u80cc\u666f\u8272",
+"Custom...": "\u81ea\u5b9a\u4e49...",
+"Custom color": "\u81ea\u5b9a\u4e49\u989c\u8272",
+"No color": "\u65e0",
+"Remove color": "\u79fb\u9664\u989c\u8272",
+"Table of Contents": "\u5185\u5bb9\u5217\u8868",
+"Show blocks": "\u663e\u793a\u533a\u5757\u8fb9\u6846",
+"Show invisible characters": "\u663e\u793a\u4e0d\u53ef\u89c1\u5b57\u7b26",
+"Word count": "\u5b57\u6570",
+"Words: {0}": "\u5b57\u6570\uff1a{0}",
+"{0} words": "{0} \u5b57",
+"File": "\u6587\u4ef6",
+"Edit": "\u7f16\u8f91",
+"Insert": "\u63d2\u5165",
+"View": "\u89c6\u56fe",
+"Format": "\u683c\u5f0f",
+"Table": "\u8868\u683c",
+"Tools": "\u5de5\u5177",
+"Powered by {0}": "\u7531{0}\u9a71\u52a8",
+"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u5728\u7f16\u8f91\u533a\u6309ALT-F9\u6253\u5f00\u83dc\u5355\uff0c\u6309ALT-F10\u6253\u5f00\u5de5\u5177\u680f\uff0c\u6309ALT-0\u67e5\u770b\u5e2e\u52a9",
+"Image title": "\u56fe\u7247\u6807\u9898",
+"Border width": "\u8fb9\u6846\u5bbd\u5ea6",
+"Border style": "\u8fb9\u6846\u6837\u5f0f",
+"Error": "\u9519\u8bef",
+"Warn": "\u8b66\u544a",
+"Valid": "\u6709\u6548",
+"To open the popup, press Shift+Enter": "\u6309Shitf+Enter\u952e\u6253\u5f00\u5bf9\u8bdd\u6846",
+"Rich Text Area. Press ALT-0 for help.": "\u7f16\u8f91\u533a\u3002\u6309Alt+0\u952e\u6253\u5f00\u5e2e\u52a9\u3002",
+"System Font": "\u7cfb\u7edf\u5b57\u4f53",
+"Failed to upload image: {0}": "\u56fe\u7247\u4e0a\u4f20\u5931\u8d25: {0}",
+"Failed to load plugin: {0} from url {1}": "\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25: {0} \u6765\u81ea\u94fe\u63a5 {1}",
+"Failed to load plugin url: {0}": "\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25 \u94fe\u63a5: {0}",
+"Failed to initialize plugin: {0}": "\u63d2\u4ef6\u521d\u59cb\u5316\u5931\u8d25: {0}",
+"example": "\u793a\u4f8b",
+"Search": "\u641c\u7d22",
+"All": "\u5168\u90e8",
+"Currency": "\u8d27\u5e01",
+"Text": "\u6587\u5b57",
+"Quotations": "\u5f15\u7528",
+"Mathematical": "\u6570\u5b66",
+"Extended Latin": "\u62c9\u4e01\u8bed\u6269\u5145",
+"Symbols": "\u7b26\u53f7",
+"Arrows": "\u7bad\u5934",
+"User Defined": "\u81ea\u5b9a\u4e49",
+"dollar sign": "\u7f8e\u5143\u7b26\u53f7",
+"currency sign": "\u8d27\u5e01\u7b26\u53f7",
+"euro-currency sign": "\u6b27\u5143\u7b26\u53f7",
+"colon sign": "\u5192\u53f7",
+"cruzeiro sign": "\u514b\u9c81\u8d5b\u7f57\u5e01\u7b26\u53f7",
+"french franc sign": "\u6cd5\u90ce\u7b26\u53f7",
+"lira sign": "\u91cc\u62c9\u7b26\u53f7",
+"mill sign": "\u5bc6\u5c14\u7b26\u53f7",
+"naira sign": "\u5948\u62c9\u7b26\u53f7",
+"peseta sign": "\u6bd4\u585e\u5854\u7b26\u53f7",
+"rupee sign": "\u5362\u6bd4\u7b26\u53f7",
+"won sign": "\u97e9\u5143\u7b26\u53f7",
+"new sheqel sign": "\u65b0\u8c22\u514b\u5c14\u7b26\u53f7",
+"dong sign": "\u8d8a\u5357\u76fe\u7b26\u53f7",
+"kip sign": "\u8001\u631d\u57fa\u666e\u7b26\u53f7",
+"tugrik sign": "\u56fe\u683c\u91cc\u514b\u7b26\u53f7",
+"drachma sign": "\u5fb7\u62c9\u514b\u9a6c\u7b26\u53f7",
+"german penny symbol": "\u5fb7\u56fd\u4fbf\u58eb\u7b26\u53f7",
+"peso sign": "\u6bd4\u7d22\u7b26\u53f7",
+"guarani sign": "\u74dc\u62c9\u5c3c\u7b26\u53f7",
+"austral sign": "\u6fb3\u5143\u7b26\u53f7",
+"hryvnia sign": "\u683c\u91cc\u592b\u5c3c\u4e9a\u7b26\u53f7",
+"cedi sign": "\u585e\u5730\u7b26\u53f7",
+"livre tournois sign": "\u91cc\u5f17\u5f17\u5c14\u7b26\u53f7",
+"spesmilo sign": "spesmilo\u7b26\u53f7",
+"tenge sign": "\u575a\u6208\u7b26\u53f7",
+"indian rupee sign": "\u5370\u5ea6\u5362\u6bd4",
+"turkish lira sign": "\u571f\u8033\u5176\u91cc\u62c9",
+"nordic mark sign": "\u5317\u6b27\u9a6c\u514b",
+"manat sign": "\u9a6c\u7eb3\u7279\u7b26\u53f7",
+"ruble sign": "\u5362\u5e03\u7b26\u53f7",
+"yen character": "\u65e5\u5143\u5b57\u6837",
+"yuan character": "\u4eba\u6c11\u5e01\u5143\u5b57\u6837",
+"yuan character, in hong kong and taiwan": "\u5143\u5b57\u6837\uff08\u6e2f\u53f0\u5730\u533a\uff09",
+"yen\/yuan character variant one": "\u5143\u5b57\u6837\uff08\u5927\u5199\uff09",
+"Loading emoticons...": "\u52a0\u8f7d\u8868\u60c5\u7b26\u53f7...",
+"Could not load emoticons": "\u4e0d\u80fd\u52a0\u8f7d\u8868\u60c5\u7b26\u53f7",
+"People": "\u4eba\u7c7b",
+"Animals and Nature": "\u52a8\u7269\u548c\u81ea\u7136",
+"Food and Drink": "\u98df\u7269\u548c\u996e\u54c1",
+"Activity": "\u6d3b\u52a8",
+"Travel and Places": "\u65c5\u6e38\u548c\u5730\u70b9",
+"Objects": "\u7269\u4ef6",
+"Flags": "\u65d7\u5e1c",
+"Characters": "\u5b57\u7b26",
+"Characters (no spaces)": "\u5b57\u7b26(\u65e0\u7a7a\u683c)",
+"Error: Form submit field collision.": "\u9519\u8bef: \u8868\u5355\u63d0\u4ea4\u5b57\u6bb5\u51b2\u7a81\u3002",
+"Error: No form element found.": "\u9519\u8bef: \u6ca1\u6709\u8868\u5355\u63a7\u4ef6\u3002",
+"Update": "\u66f4\u65b0",
+"Color swatch": "\u989c\u8272\u6837\u672c",
+"Turquoise": "\u9752\u7eff\u8272",
+"Green": "\u7eff\u8272",
+"Blue": "\u84dd\u8272",
+"Purple": "\u7d2b\u8272",
+"Navy Blue": "\u6d77\u519b\u84dd",
+"Dark Turquoise": "\u6df1\u84dd\u7eff\u8272",
+"Dark Green": "\u6df1\u7eff\u8272",
+"Medium Blue": "\u4e2d\u84dd\u8272",
+"Medium Purple": "\u4e2d\u7d2b\u8272",
+"Midnight Blue": "\u6df1\u84dd\u8272",
+"Yellow": "\u9ec4\u8272",
+"Orange": "\u6a59\u8272",
+"Red": "\u7ea2\u8272",
+"Light Gray": "\u6d45\u7070\u8272",
+"Gray": "\u7070\u8272",
+"Dark Yellow": "\u6697\u9ec4\u8272",
+"Dark Orange": "\u6df1\u6a59\u8272",
+"Dark Red": "\u6df1\u7ea2\u8272",
+"Medium Gray": "\u4e2d\u7070\u8272",
+"Dark Gray": "\u6df1\u7070\u8272",
+"Black": "\u9ed1\u8272",
+"White": "\u767d\u8272",
+"Switch to or from fullscreen mode": "\u5207\u6362\u5168\u5c4f\u6a21\u5f0f",
+"Open help dialog": "\u6253\u5f00\u5e2e\u52a9\u5bf9\u8bdd\u6846",
+"history": "\u5386\u53f2",
+"styles": "\u6837\u5f0f",
+"formatting": "\u683c\u5f0f\u5316",
+"alignment": "\u5bf9\u9f50",
+"indentation": "\u7f29\u8fdb",
+"permanent pen": "\u8bb0\u53f7\u7b14",
+"comments": "\u5907\u6ce8",
+"Anchor": "\u951a\u70b9",
+"Special character": "\u7279\u6b8a\u7b26\u53f7",
+"Code sample": "\u4ee3\u7801\u793a\u4f8b",
+"Color": "\u989c\u8272",
+"Emoticons": "\u8868\u60c5",
+"Document properties": "\u6587\u6863\u5c5e\u6027",
+"Image": "\u56fe\u7247",
+"Insert link": "\u63d2\u5165\u94fe\u63a5",
+"Target": "\u6253\u5f00\u65b9\u5f0f",
+"Link": "\u94fe\u63a5",
+"Poster": "\u5c01\u9762",
+"Media": "\u5a92\u4f53",
+"Print": "\u6253\u5370",
+"Prev": "\u4e0a\u4e00\u4e2a",
+"Find and replace": "\u67e5\u627e\u548c\u66ff\u6362",
+"Whole words": "\u5168\u5b57\u5339\u914d",
+"Spellcheck": "\u62fc\u5199\u68c0\u67e5",
+"Caption": "\u6807\u9898",
+"Insert template": "\u63d2\u5165\u6a21\u677f"
+});

+ 59 - 0
public/tinymce/skins/content/dark/content.css

@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) Tiny Technologies, Inc. All rights reserved.
+ * Licensed under the LGPL or a commercial license.
+ * For LGPL see License.txt in the project root for license information.
+ * For commercial licenses see https://www.tiny.cloud/
+ */
+body {
+  background-color: #2f3742;
+  color: #dfe0e4;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+  line-height: 1.4;
+  margin: 1rem;
+}
+a {
+  color: #4099ff;
+}
+table {
+  border-collapse: collapse;
+}
+table th,
+table td {
+  border: 1px solid #6d737b;
+  padding: 0.4rem;
+}
+figure {
+  display: table;
+  margin: 1rem auto;
+}
+figure figcaption {
+  color: #8a8f97;
+  display: block;
+  margin-top: 0.25rem;
+  text-align: center;
+}
+hr {
+  border-color: #6d737b;
+  border-style: solid;
+  border-width: 1px 0 0 0;
+}
+code {
+  background-color: #6d737b;
+  border-radius: 3px;
+  padding: 0.1rem 0.2rem;
+}
+/* Make text in selected cells in tables dark and readable */
+td[data-mce-selected],
+th[data-mce-selected] {
+  color: #333;
+}
+.mce-content-body:not([dir=rtl]) blockquote {
+  border-left: 2px solid #6d737b;
+  margin-left: 1.5rem;
+  padding-left: 1rem;
+}
+.mce-content-body[dir=rtl] blockquote {
+  border-right: 2px solid #6d737b;
+  margin-right: 1.5rem;
+  padding-right: 1rem;
+}

File diff suppressed because it is too large
+ 8 - 0
public/tinymce/skins/content/dark/content.min.css


+ 49 - 0
public/tinymce/skins/content/default/content.css

@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) Tiny Technologies, Inc. All rights reserved.
+ * Licensed under the LGPL or a commercial license.
+ * For LGPL see License.txt in the project root for license information.
+ * For commercial licenses see https://www.tiny.cloud/
+ */
+body {
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+  line-height: 1.4;
+  margin: 1rem;
+}
+table {
+  border-collapse: collapse;
+}
+table th,
+table td {
+  border: 1px solid #ccc;
+  padding: 0.4rem;
+}
+figure {
+  display: table;
+  margin: 1rem auto;
+}
+figure figcaption {
+  color: #999;
+  display: block;
+  margin-top: 0.25rem;
+  text-align: center;
+}
+hr {
+  border-color: #ccc;
+  border-style: solid;
+  border-width: 1px 0 0 0;
+}
+code {
+  background-color: #e8e8e8;
+  border-radius: 3px;
+  padding: 0.1rem 0.2rem;
+}
+.mce-content-body:not([dir=rtl]) blockquote {
+  border-left: 2px solid #ccc;
+  margin-left: 1.5rem;
+  padding-left: 1rem;
+}
+.mce-content-body[dir=rtl] blockquote {
+  border-right: 2px solid #ccc;
+  margin-right: 1.5rem;
+  padding-right: 1rem;
+}

File diff suppressed because it is too large
+ 8 - 0
public/tinymce/skins/content/default/content.min.css


+ 53 - 0
public/tinymce/skins/content/document/content.css

@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) Tiny Technologies, Inc. All rights reserved.
+ * Licensed under the LGPL or a commercial license.
+ * For LGPL see License.txt in the project root for license information.
+ * For commercial licenses see https://www.tiny.cloud/
+ */
+@media screen {
+  html {
+    background: #f4f4f4;
+  }
+}
+body {
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+}
+@media screen {
+  body {
+    background-color: #fff;
+    box-shadow: 0 0 4px rgba(0, 0, 0, 0.15);
+    box-sizing: border-box;
+    margin: 1rem auto 0;
+    max-width: 820px;
+    min-height: calc(100vh - 1rem);
+    padding: 4rem 6rem 6rem 6rem;
+  }
+}
+table {
+  border-collapse: collapse;
+}
+table th,
+table td {
+  border: 1px solid #ccc;
+  padding: 0.4rem;
+}
+figure figcaption {
+  color: #999;
+  margin-top: 0.25rem;
+  text-align: center;
+}
+hr {
+  border-color: #ccc;
+  border-style: solid;
+  border-width: 1px 0 0 0;
+}
+.mce-content-body:not([dir=rtl]) blockquote {
+  border-left: 2px solid #ccc;
+  margin-left: 1.5rem;
+  padding-left: 1rem;
+}
+.mce-content-body[dir=rtl] blockquote {
+  border-right: 2px solid #ccc;
+  margin-right: 1.5rem;
+  padding-right: 1rem;
+}

File diff suppressed because it is too large
+ 8 - 0
public/tinymce/skins/content/document/content.min.css


+ 50 - 0
public/tinymce/skins/content/writer/content.css

@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) Tiny Technologies, Inc. All rights reserved.
+ * Licensed under the LGPL or a commercial license.
+ * For LGPL see License.txt in the project root for license information.
+ * For commercial licenses see https://www.tiny.cloud/
+ */
+body {
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+  line-height: 1.4;
+  margin: 1rem auto;
+  max-width: 900px;
+}
+table {
+  border-collapse: collapse;
+}
+table th,
+table td {
+  border: 1px solid #ccc;
+  padding: 0.4rem;
+}
+figure {
+  display: table;
+  margin: 1rem auto;
+}
+figure figcaption {
+  color: #999;
+  display: block;
+  margin-top: 0.25rem;
+  text-align: center;
+}
+hr {
+  border-color: #ccc;
+  border-style: solid;
+  border-width: 1px 0 0 0;
+}
+code {
+  background-color: #e8e8e8;
+  border-radius: 3px;
+  padding: 0.1rem 0.2rem;
+}
+.mce-content-body:not([dir=rtl]) blockquote {
+  border-left: 2px solid #ccc;
+  margin-left: 1.5rem;
+  padding-left: 1rem;
+}
+.mce-content-body[dir=rtl] blockquote {
+  border-right: 2px solid #ccc;
+  margin-right: 1.5rem;
+  padding-right: 1rem;
+}

File diff suppressed because it is too large
+ 8 - 0
public/tinymce/skins/content/writer/content.min.css


File diff suppressed because it is too large
+ 589 - 0
public/tinymce/skins/ui/oxide-dark/content.css


File diff suppressed because it is too large
+ 615 - 0
public/tinymce/skins/ui/oxide-dark/content.inline.css


File diff suppressed because it is too large
+ 8 - 0
public/tinymce/skins/ui/oxide-dark/content.inline.min.css


File diff suppressed because it is too large
+ 8 - 0
public/tinymce/skins/ui/oxide-dark/content.min.css


+ 29 - 0
public/tinymce/skins/ui/oxide-dark/content.mobile.css

@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) Tiny Technologies, Inc. All rights reserved.
+ * Licensed under the LGPL or a commercial license.
+ * For LGPL see License.txt in the project root for license information.
+ * For commercial licenses see https://www.tiny.cloud/
+ */
+.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection {
+  /* Note: this file is used inside the content, so isn't part of theming */
+  background-color: green;
+  display: inline-block;
+  opacity: 0.5;
+  position: absolute;
+}
+body {
+  -webkit-text-size-adjust: none;
+}
+body img {
+  /* this is related to the content margin */
+  max-width: 96vw;
+}
+body table img {
+  max-width: 95%;
+}
+body {
+  font-family: sans-serif;
+}
+table {
+  border-collapse: collapse;
+}

+ 8 - 0
public/tinymce/skins/ui/oxide-dark/content.mobile.min.css

@@ -0,0 +1,8 @@
+/**
+ * Copyright (c) Tiny Technologies, Inc. All rights reserved.
+ * Licensed under the LGPL or a commercial license.
+ * For LGPL see License.txt in the project root for license information.
+ * For commercial licenses see https://www.tiny.cloud/
+ */
+.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{background-color:green;display:inline-block;opacity:.5;position:absolute}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%}body{font-family:sans-serif}table{border-collapse:collapse}
+/*# sourceMappingURL=content.mobile.min.css.map */

BIN
public/tinymce/skins/ui/oxide-dark/fonts/tinymce-mobile.woff


File diff suppressed because it is too large
+ 2672 - 0
public/tinymce/skins/ui/oxide-dark/skin.css


File diff suppressed because it is too large
+ 8 - 0
public/tinymce/skins/ui/oxide-dark/skin.min.css


+ 673 - 0
public/tinymce/skins/ui/oxide-dark/skin.mobile.css

@@ -0,0 +1,673 @@
+/**
+ * Copyright (c) Tiny Technologies, Inc. All rights reserved.
+ * Licensed under the LGPL or a commercial license.
+ * For LGPL see License.txt in the project root for license information.
+ * For commercial licenses see https://www.tiny.cloud/
+ */
+/* RESET all the things! */
+.tinymce-mobile-outer-container {
+  all: initial;
+  display: block;
+}
+.tinymce-mobile-outer-container * {
+  border: 0;
+  box-sizing: initial;
+  cursor: inherit;
+  float: none;
+  line-height: 1;
+  margin: 0;
+  outline: 0;
+  padding: 0;
+  -webkit-tap-highlight-color: transparent;
+  /* TBIO-3691, stop the gray flicker on touch. */
+  text-shadow: none;
+  white-space: nowrap;
+}
+.tinymce-mobile-icon-arrow-back::before {
+  content: "\e5cd";
+}
+.tinymce-mobile-icon-image::before {
+  content: "\e412";
+}
+.tinymce-mobile-icon-cancel-circle::before {
+  content: "\e5c9";
+}
+.tinymce-mobile-icon-full-dot::before {
+  content: "\e061";
+}
+.tinymce-mobile-icon-align-center::before {
+  content: "\e234";
+}
+.tinymce-mobile-icon-align-left::before {
+  content: "\e236";
+}
+.tinymce-mobile-icon-align-right::before {
+  content: "\e237";
+}
+.tinymce-mobile-icon-bold::before {
+  content: "\e238";
+}
+.tinymce-mobile-icon-italic::before {
+  content: "\e23f";
+}
+.tinymce-mobile-icon-unordered-list::before {
+  content: "\e241";
+}
+.tinymce-mobile-icon-ordered-list::before {
+  content: "\e242";
+}
+.tinymce-mobile-icon-font-size::before {
+  content: "\e245";
+}
+.tinymce-mobile-icon-underline::before {
+  content: "\e249";
+}
+.tinymce-mobile-icon-link::before {
+  content: "\e157";
+}
+.tinymce-mobile-icon-unlink::before {
+  content: "\eca2";
+}
+.tinymce-mobile-icon-color::before {
+  content: "\e891";
+}
+.tinymce-mobile-icon-previous::before {
+  content: "\e314";
+}
+.tinymce-mobile-icon-next::before {
+  content: "\e315";
+}
+.tinymce-mobile-icon-large-font::before,
+.tinymce-mobile-icon-style-formats::before {
+  content: "\e264";
+}
+.tinymce-mobile-icon-undo::before {
+  content: "\e166";
+}
+.tinymce-mobile-icon-redo::before {
+  content: "\e15a";
+}
+.tinymce-mobile-icon-removeformat::before {
+  content: "\e239";
+}
+.tinymce-mobile-icon-small-font::before {
+  content: "\e906";
+}
+.tinymce-mobile-icon-readonly-back::before,
+.tinymce-mobile-format-matches::after {
+  content: "\e5ca";
+}
+.tinymce-mobile-icon-small-heading::before {
+  content: "small";
+}
+.tinymce-mobile-icon-large-heading::before {
+  content: "large";
+}
+.tinymce-mobile-icon-small-heading::before,
+.tinymce-mobile-icon-large-heading::before {
+  font-family: sans-serif;
+  font-size: 80%;
+}
+.tinymce-mobile-mask-edit-icon::before {
+  content: "\e254";
+}
+.tinymce-mobile-icon-back::before {
+  content: "\e5c4";
+}
+.tinymce-mobile-icon-heading::before {
+  /* TODO: Translate */
+  content: "Headings";
+  font-family: sans-serif;
+  font-size: 80%;
+  font-weight: bold;
+}
+.tinymce-mobile-icon-h1::before {
+  content: "H1";
+  font-weight: bold;
+}
+.tinymce-mobile-icon-h2::before {
+  content: "H2";
+  font-weight: bold;
+}
+.tinymce-mobile-icon-h3::before {
+  content: "H3";
+  font-weight: bold;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask {
+  align-items: center;
+  display: flex;
+  justify-content: center;
+  background: rgba(51, 51, 51, 0.5);
+  height: 100%;
+  position: absolute;
+  top: 0;
+  width: 100%;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container {
+  align-items: center;
+  border-radius: 50%;
+  display: flex;
+  flex-direction: column;
+  font-family: sans-serif;
+  font-size: 1em;
+  justify-content: space-between;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .mixin-menu-item {
+  align-items: center;
+  display: flex;
+  justify-content: center;
+  border-radius: 50%;
+  height: 2.1em;
+  width: 2.1em;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section {
+  align-items: center;
+  display: flex;
+  justify-content: center;
+  flex-direction: column;
+  font-size: 1em;
+}
+@media only screen and (min-device-width:700px) {
+  .tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section {
+    font-size: 1.2em;
+  }
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon {
+  align-items: center;
+  display: flex;
+  justify-content: center;
+  border-radius: 50%;
+  height: 2.1em;
+  width: 2.1em;
+  background-color: white;
+  color: #207ab7;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon::before {
+  content: "\e900";
+  font-family: 'tinymce-mobile', sans-serif;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section:not(.tinymce-mobile-mask-tap-icon-selected) .tinymce-mobile-mask-tap-icon {
+  z-index: 2;
+}
+.tinymce-mobile-android-container.tinymce-mobile-android-maximized {
+  background: #ffffff;
+  border: none;
+  bottom: 0;
+  display: flex;
+  flex-direction: column;
+  left: 0;
+  position: fixed;
+  right: 0;
+  top: 0;
+}
+.tinymce-mobile-android-container:not(.tinymce-mobile-android-maximized) {
+  position: relative;
+}
+.tinymce-mobile-android-container .tinymce-mobile-editor-socket {
+  display: flex;
+  flex-grow: 1;
+}
+.tinymce-mobile-android-container .tinymce-mobile-editor-socket iframe {
+  display: flex !important;
+  flex-grow: 1;
+  height: auto !important;
+}
+.tinymce-mobile-android-scroll-reload {
+  overflow: hidden;
+}
+:not(.tinymce-mobile-readonly-mode) > .tinymce-mobile-android-selection-context-toolbar {
+  margin-top: 23px;
+}
+.tinymce-mobile-toolstrip {
+  background: #fff;
+  display: flex;
+  flex: 0 0 auto;
+  z-index: 1;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar {
+  align-items: center;
+  background-color: #fff;
+  border-bottom: 1px solid #cccccc;
+  display: flex;
+  flex: 1;
+  height: 2.5em;
+  width: 100%;
+  /* Make it no larger than the toolstrip, so that it needs to scroll */
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group {
+  align-items: center;
+  display: flex;
+  height: 100%;
+  flex-shrink: 1;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group > div {
+  align-items: center;
+  display: flex;
+  height: 100%;
+  flex: 1;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-exit-container {
+  background: #f44336;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-toolbar-scrollable-group {
+  flex-grow: 1;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item {
+  padding-left: 0.5em;
+  padding-right: 0.5em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button {
+  align-items: center;
+  display: flex;
+  height: 80%;
+  margin-left: 2px;
+  margin-right: 2px;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button.tinymce-mobile-toolbar-button-selected {
+  background: #c8cbcf;
+  color: #cccccc;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:first-of-type,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:last-of-type {
+  background: #207ab7;
+  color: #eceff1;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar {
+  /* Note, this file is imported inside .tinymce-mobile-context-toolbar, so that prefix is on everything here. */
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group {
+  align-items: center;
+  display: flex;
+  height: 100%;
+  flex: 1;
+  padding-bottom: 0.4em;
+  padding-top: 0.4em;
+  /* Make any buttons appearing on the left and right display in the centre (e.g. color edges) */
+  /* For widgets like the colour picker, use the whole height */
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog {
+  display: flex;
+  min-height: 1.5em;
+  overflow: hidden;
+  padding-left: 0;
+  padding-right: 0;
+  position: relative;
+  width: 100%;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain {
+  display: flex;
+  height: 100%;
+  transition: left cubic-bezier(0.4, 0, 1, 1) 0.15s;
+  width: 100%;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen {
+  display: flex;
+  flex: 0 0 auto;
+  justify-content: space-between;
+  width: 100%;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen input {
+  font-family: Sans-serif;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container {
+  display: flex;
+  flex-grow: 1;
+  position: relative;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container .tinymce-mobile-input-container-x {
+  -ms-grid-row-align: center;
+      align-self: center;
+  background: inherit;
+  border: none;
+  border-radius: 50%;
+  color: #888;
+  font-size: 0.6em;
+  font-weight: bold;
+  height: 100%;
+  padding-right: 2px;
+  position: absolute;
+  right: 0;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container.tinymce-mobile-input-container-empty .tinymce-mobile-input-container-x {
+  display: none;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next {
+  align-items: center;
+  display: flex;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous::before,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next::before {
+  align-items: center;
+  display: flex;
+  font-weight: bold;
+  height: 100%;
+  padding-left: 0.5em;
+  padding-right: 0.5em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous.tinymce-mobile-toolbar-navigation-disabled::before,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next.tinymce-mobile-toolbar-navigation-disabled::before {
+  visibility: hidden;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item {
+  color: #cccccc;
+  font-size: 10px;
+  line-height: 10px;
+  margin: 0 2px;
+  padding-top: 3px;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item.tinymce-mobile-dot-active {
+  color: #c8cbcf;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-font::before,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-heading::before {
+  margin-left: 0.5em;
+  margin-right: 0.9em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-font::before,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-heading::before {
+  margin-left: 0.9em;
+  margin-right: 0.5em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider {
+  display: flex;
+  flex: 1;
+  margin-left: 0;
+  margin-right: 0;
+  padding: 0.28em 0;
+  position: relative;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container {
+  align-items: center;
+  display: flex;
+  flex-grow: 1;
+  height: 100%;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container .tinymce-mobile-slider-size-line {
+  background: #cccccc;
+  display: flex;
+  flex: 1;
+  height: 0.2em;
+  margin-bottom: 0.3em;
+  margin-top: 0.3em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container {
+  padding-left: 2em;
+  padding-right: 2em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container {
+  align-items: center;
+  display: flex;
+  flex-grow: 1;
+  height: 100%;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container .tinymce-mobile-slider-gradient {
+  background: linear-gradient(to right, hsl(0, 100%, 50%) 0%, hsl(60, 100%, 50%) 17%, hsl(120, 100%, 50%) 33%, hsl(180, 100%, 50%) 50%, hsl(240, 100%, 50%) 67%, hsl(300, 100%, 50%) 83%, hsl(0, 100%, 50%) 100%);
+  display: flex;
+  flex: 1;
+  height: 0.2em;
+  margin-bottom: 0.3em;
+  margin-top: 0.3em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-black {
+  /* Not part of theming */
+  background: black;
+  height: 0.2em;
+  margin-bottom: 0.3em;
+  margin-top: 0.3em;
+  width: 1.2em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-white {
+  /* Not part of theming */
+  background: white;
+  height: 0.2em;
+  margin-bottom: 0.3em;
+  margin-top: 0.3em;
+  width: 1.2em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb {
+  /* vertically centering trick (margin: auto, top: 0, bottom: 0). On iOS and Safari, if you leave
+     * out these values, then it shows the thumb at the top of the spectrum. This is probably because it is
+     * absolutely positioned with only a left value, and not a top. Note, on Chrome it seems to be fine without
+     * this approach.
+    */
+  align-items: center;
+  background-clip: padding-box;
+  background-color: #455a64;
+  border: 0.5em solid rgba(136, 136, 136, 0);
+  border-radius: 3em;
+  bottom: 0;
+  color: #fff;
+  display: flex;
+  height: 0.5em;
+  justify-content: center;
+  left: -10px;
+  margin: auto;
+  position: absolute;
+  top: 0;
+  transition: border 120ms cubic-bezier(0.39, 0.58, 0.57, 1);
+  width: 0.5em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb.tinymce-mobile-thumb-active {
+  border: 0.5em solid rgba(136, 136, 136, 0.39);
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group > div {
+  align-items: center;
+  display: flex;
+  height: 100%;
+  flex: 1;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper {
+  flex-direction: column;
+  justify-content: center;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item {
+  align-items: center;
+  display: flex;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item:not(.tinymce-mobile-serialised-dialog) {
+  height: 100%;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-container {
+  display: flex;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input {
+  background: #ffffff;
+  border: none;
+  border-radius: 0;
+  color: #455a64;
+  flex-grow: 1;
+  font-size: 0.85em;
+  padding-bottom: 0.1em;
+  padding-left: 5px;
+  padding-top: 0.1em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::-webkit-input-placeholder {
+  /* WebKit, Blink, Edge */
+  color: #888;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::placeholder {
+  /* WebKit, Blink, Edge */
+  color: #888;
+}
+/* dropup */
+.tinymce-mobile-dropup {
+  background: white;
+  display: flex;
+  overflow: hidden;
+  width: 100%;
+}
+.tinymce-mobile-dropup.tinymce-mobile-dropup-shrinking {
+  transition: height 0.3s ease-out;
+}
+.tinymce-mobile-dropup.tinymce-mobile-dropup-growing {
+  transition: height 0.3s ease-in;
+}
+.tinymce-mobile-dropup.tinymce-mobile-dropup-closed {
+  flex-grow: 0;
+}
+.tinymce-mobile-dropup.tinymce-mobile-dropup-open:not(.tinymce-mobile-dropup-growing) {
+  flex-grow: 1;
+}
+/* TODO min-height for device size and orientation */
+.tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed) {
+  min-height: 200px;
+}
+@media only screen and (orientation: landscape) {
+  .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed) {
+    min-height: 200px;
+  }
+}
+@media only screen and (min-device-width : 320px) and (max-device-width : 568px) and (orientation : landscape) {
+  .tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed) {
+    min-height: 150px;
+  }
+}
+/* styles menu */
+.tinymce-mobile-styles-menu {
+  font-family: sans-serif;
+  outline: 4px solid black;
+  overflow: hidden;
+  position: relative;
+  width: 100%;
+}
+.tinymce-mobile-styles-menu [role="menu"] {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  position: absolute;
+  width: 100%;
+}
+.tinymce-mobile-styles-menu [role="menu"].transitioning {
+  transition: transform 0.5s ease-in-out;
+}
+.tinymce-mobile-styles-menu .tinymce-mobile-styles-item {
+  border-bottom: 1px solid #ddd;
+  color: #455a64;
+  cursor: pointer;
+  display: flex;
+  padding: 1em 1em;
+  position: relative;
+}
+.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser .tinymce-mobile-styles-collapse-icon::before {
+  color: #455a64;
+  content: "\e314";
+  font-family: 'tinymce-mobile', sans-serif;
+}
+.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-styles-item-is-menu::after {
+  color: #455a64;
+  content: "\e315";
+  font-family: 'tinymce-mobile', sans-serif;
+  padding-left: 1em;
+  padding-right: 1em;
+  position: absolute;
+  right: 0;
+}
+.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-format-matches::after {
+  font-family: 'tinymce-mobile', sans-serif;
+  padding-left: 1em;
+  padding-right: 1em;
+  position: absolute;
+  right: 0;
+}
+.tinymce-mobile-styles-menu .tinymce-mobile-styles-separator,
+.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser {
+  align-items: center;
+  background: #fff;
+  border-top: #455a64;
+  color: #455a64;
+  display: flex;
+  min-height: 2.5em;
+  padding-left: 1em;
+  padding-right: 1em;
+}
+.tinymce-mobile-styles-menu [data-transitioning-destination="before"][data-transitioning-state],
+.tinymce-mobile-styles-menu [data-transitioning-state="before"] {
+  transform: translate(-100%);
+}
+.tinymce-mobile-styles-menu [data-transitioning-destination="current"][data-transitioning-state],
+.tinymce-mobile-styles-menu [data-transitioning-state="current"] {
+  transform: translate(0%);
+}
+.tinymce-mobile-styles-menu [data-transitioning-destination="after"][data-transitioning-state],
+.tinymce-mobile-styles-menu [data-transitioning-state="after"] {
+  transform: translate(100%);
+}
+@font-face {
+  font-family: 'tinymce-mobile';
+  font-style: normal;
+  font-weight: normal;
+  src: url('fonts/tinymce-mobile.woff?8x92w3') format('woff');
+}
+@media (min-device-width: 700px) {
+  .tinymce-mobile-outer-container,
+  .tinymce-mobile-outer-container input {
+    font-size: 25px;
+  }
+}
+@media (max-device-width: 700px) {
+  .tinymce-mobile-outer-container,
+  .tinymce-mobile-outer-container input {
+    font-size: 18px;
+  }
+}
+.tinymce-mobile-icon {
+  font-family: 'tinymce-mobile', sans-serif;
+}
+.mixin-flex-and-centre {
+  align-items: center;
+  display: flex;
+  justify-content: center;
+}
+.mixin-flex-bar {
+  align-items: center;
+  display: flex;
+  height: 100%;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-editor-socket iframe {
+  background-color: #fff;
+  width: 100%;
+}
+.tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon {
+  /* Note, on the iPod touch in landscape, this isn't visible when the navbar appears */
+  background-color: #207ab7;
+  border-radius: 50%;
+  bottom: 1em;
+  color: white;
+  font-size: 1em;
+  height: 2.1em;
+  position: fixed;
+  right: 2em;
+  width: 2.1em;
+  align-items: center;
+  display: flex;
+  justify-content: center;
+}
+@media only screen and (min-device-width:700px) {
+  .tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon {
+    font-size: 1.2em;
+  }
+}
+.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket {
+  height: 300px;
+  overflow: hidden;
+}
+.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket iframe {
+  height: 100%;
+}
+.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-toolstrip {
+  display: none;
+}
+/*
+  Note, that if you don't include this (::-webkit-file-upload-button), the toolbar width gets
+  increased and the whole body becomes scrollable. It's important!
+ */
+input[type="file"]::-webkit-file-upload-button {
+  display: none;
+}
+@media only screen and (min-device-width : 320px) and (max-device-width : 568px) and (orientation : landscape) {
+  .tinymce-mobile-ios-container .tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon {
+    bottom: 50%;
+  }
+}

File diff suppressed because it is too large
+ 8 - 0
public/tinymce/skins/ui/oxide-dark/skin.mobile.min.css


File diff suppressed because it is too large
+ 607 - 0
public/tinymce/skins/ui/oxide/content.css


File diff suppressed because it is too large
+ 615 - 0
public/tinymce/skins/ui/oxide/content.inline.css


File diff suppressed because it is too large
+ 8 - 0
public/tinymce/skins/ui/oxide/content.inline.min.css


File diff suppressed because it is too large
+ 8 - 0
public/tinymce/skins/ui/oxide/content.min.css


+ 29 - 0
public/tinymce/skins/ui/oxide/content.mobile.css

@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) Tiny Technologies, Inc. All rights reserved.
+ * Licensed under the LGPL or a commercial license.
+ * For LGPL see License.txt in the project root for license information.
+ * For commercial licenses see https://www.tiny.cloud/
+ */
+.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection {
+  /* Note: this file is used inside the content, so isn't part of theming */
+  background-color: green;
+  display: inline-block;
+  opacity: 0.5;
+  position: absolute;
+}
+body {
+  -webkit-text-size-adjust: none;
+}
+body img {
+  /* this is related to the content margin */
+  max-width: 96vw;
+}
+body table img {
+  max-width: 95%;
+}
+body {
+  font-family: sans-serif;
+}
+table {
+  border-collapse: collapse;
+}

+ 8 - 0
public/tinymce/skins/ui/oxide/content.mobile.min.css

@@ -0,0 +1,8 @@
+/**
+ * Copyright (c) Tiny Technologies, Inc. All rights reserved.
+ * Licensed under the LGPL or a commercial license.
+ * For LGPL see License.txt in the project root for license information.
+ * For commercial licenses see https://www.tiny.cloud/
+ */
+.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{background-color:green;display:inline-block;opacity:.5;position:absolute}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%}body{font-family:sans-serif}table{border-collapse:collapse}
+/*# sourceMappingURL=content.mobile.min.css.map */

BIN
public/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff


File diff suppressed because it is too large
+ 2672 - 0
public/tinymce/skins/ui/oxide/skin.css


File diff suppressed because it is too large
+ 8 - 0
public/tinymce/skins/ui/oxide/skin.min.css


+ 673 - 0
public/tinymce/skins/ui/oxide/skin.mobile.css

@@ -0,0 +1,673 @@
+/**
+ * Copyright (c) Tiny Technologies, Inc. All rights reserved.
+ * Licensed under the LGPL or a commercial license.
+ * For LGPL see License.txt in the project root for license information.
+ * For commercial licenses see https://www.tiny.cloud/
+ */
+/* RESET all the things! */
+.tinymce-mobile-outer-container {
+  all: initial;
+  display: block;
+}
+.tinymce-mobile-outer-container * {
+  border: 0;
+  box-sizing: initial;
+  cursor: inherit;
+  float: none;
+  line-height: 1;
+  margin: 0;
+  outline: 0;
+  padding: 0;
+  -webkit-tap-highlight-color: transparent;
+  /* TBIO-3691, stop the gray flicker on touch. */
+  text-shadow: none;
+  white-space: nowrap;
+}
+.tinymce-mobile-icon-arrow-back::before {
+  content: "\e5cd";
+}
+.tinymce-mobile-icon-image::before {
+  content: "\e412";
+}
+.tinymce-mobile-icon-cancel-circle::before {
+  content: "\e5c9";
+}
+.tinymce-mobile-icon-full-dot::before {
+  content: "\e061";
+}
+.tinymce-mobile-icon-align-center::before {
+  content: "\e234";
+}
+.tinymce-mobile-icon-align-left::before {
+  content: "\e236";
+}
+.tinymce-mobile-icon-align-right::before {
+  content: "\e237";
+}
+.tinymce-mobile-icon-bold::before {
+  content: "\e238";
+}
+.tinymce-mobile-icon-italic::before {
+  content: "\e23f";
+}
+.tinymce-mobile-icon-unordered-list::before {
+  content: "\e241";
+}
+.tinymce-mobile-icon-ordered-list::before {
+  content: "\e242";
+}
+.tinymce-mobile-icon-font-size::before {
+  content: "\e245";
+}
+.tinymce-mobile-icon-underline::before {
+  content: "\e249";
+}
+.tinymce-mobile-icon-link::before {
+  content: "\e157";
+}
+.tinymce-mobile-icon-unlink::before {
+  content: "\eca2";
+}
+.tinymce-mobile-icon-color::before {
+  content: "\e891";
+}
+.tinymce-mobile-icon-previous::before {
+  content: "\e314";
+}
+.tinymce-mobile-icon-next::before {
+  content: "\e315";
+}
+.tinymce-mobile-icon-large-font::before,
+.tinymce-mobile-icon-style-formats::before {
+  content: "\e264";
+}
+.tinymce-mobile-icon-undo::before {
+  content: "\e166";
+}
+.tinymce-mobile-icon-redo::before {
+  content: "\e15a";
+}
+.tinymce-mobile-icon-removeformat::before {
+  content: "\e239";
+}
+.tinymce-mobile-icon-small-font::before {
+  content: "\e906";
+}
+.tinymce-mobile-icon-readonly-back::before,
+.tinymce-mobile-format-matches::after {
+  content: "\e5ca";
+}
+.tinymce-mobile-icon-small-heading::before {
+  content: "small";
+}
+.tinymce-mobile-icon-large-heading::before {
+  content: "large";
+}
+.tinymce-mobile-icon-small-heading::before,
+.tinymce-mobile-icon-large-heading::before {
+  font-family: sans-serif;
+  font-size: 80%;
+}
+.tinymce-mobile-mask-edit-icon::before {
+  content: "\e254";
+}
+.tinymce-mobile-icon-back::before {
+  content: "\e5c4";
+}
+.tinymce-mobile-icon-heading::before {
+  /* TODO: Translate */
+  content: "Headings";
+  font-family: sans-serif;
+  font-size: 80%;
+  font-weight: bold;
+}
+.tinymce-mobile-icon-h1::before {
+  content: "H1";
+  font-weight: bold;
+}
+.tinymce-mobile-icon-h2::before {
+  content: "H2";
+  font-weight: bold;
+}
+.tinymce-mobile-icon-h3::before {
+  content: "H3";
+  font-weight: bold;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask {
+  align-items: center;
+  display: flex;
+  justify-content: center;
+  background: rgba(51, 51, 51, 0.5);
+  height: 100%;
+  position: absolute;
+  top: 0;
+  width: 100%;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container {
+  align-items: center;
+  border-radius: 50%;
+  display: flex;
+  flex-direction: column;
+  font-family: sans-serif;
+  font-size: 1em;
+  justify-content: space-between;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .mixin-menu-item {
+  align-items: center;
+  display: flex;
+  justify-content: center;
+  border-radius: 50%;
+  height: 2.1em;
+  width: 2.1em;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section {
+  align-items: center;
+  display: flex;
+  justify-content: center;
+  flex-direction: column;
+  font-size: 1em;
+}
+@media only screen and (min-device-width:700px) {
+  .tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section {
+    font-size: 1.2em;
+  }
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon {
+  align-items: center;
+  display: flex;
+  justify-content: center;
+  border-radius: 50%;
+  height: 2.1em;
+  width: 2.1em;
+  background-color: white;
+  color: #207ab7;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon::before {
+  content: "\e900";
+  font-family: 'tinymce-mobile', sans-serif;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section:not(.tinymce-mobile-mask-tap-icon-selected) .tinymce-mobile-mask-tap-icon {
+  z-index: 2;
+}
+.tinymce-mobile-android-container.tinymce-mobile-android-maximized {
+  background: #ffffff;
+  border: none;
+  bottom: 0;
+  display: flex;
+  flex-direction: column;
+  left: 0;
+  position: fixed;
+  right: 0;
+  top: 0;
+}
+.tinymce-mobile-android-container:not(.tinymce-mobile-android-maximized) {
+  position: relative;
+}
+.tinymce-mobile-android-container .tinymce-mobile-editor-socket {
+  display: flex;
+  flex-grow: 1;
+}
+.tinymce-mobile-android-container .tinymce-mobile-editor-socket iframe {
+  display: flex !important;
+  flex-grow: 1;
+  height: auto !important;
+}
+.tinymce-mobile-android-scroll-reload {
+  overflow: hidden;
+}
+:not(.tinymce-mobile-readonly-mode) > .tinymce-mobile-android-selection-context-toolbar {
+  margin-top: 23px;
+}
+.tinymce-mobile-toolstrip {
+  background: #fff;
+  display: flex;
+  flex: 0 0 auto;
+  z-index: 1;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar {
+  align-items: center;
+  background-color: #fff;
+  border-bottom: 1px solid #cccccc;
+  display: flex;
+  flex: 1;
+  height: 2.5em;
+  width: 100%;
+  /* Make it no larger than the toolstrip, so that it needs to scroll */
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group {
+  align-items: center;
+  display: flex;
+  height: 100%;
+  flex-shrink: 1;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group > div {
+  align-items: center;
+  display: flex;
+  height: 100%;
+  flex: 1;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-exit-container {
+  background: #f44336;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-toolbar-scrollable-group {
+  flex-grow: 1;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item {
+  padding-left: 0.5em;
+  padding-right: 0.5em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button {
+  align-items: center;
+  display: flex;
+  height: 80%;
+  margin-left: 2px;
+  margin-right: 2px;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button.tinymce-mobile-toolbar-button-selected {
+  background: #c8cbcf;
+  color: #cccccc;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:first-of-type,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:last-of-type {
+  background: #207ab7;
+  color: #eceff1;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar {
+  /* Note, this file is imported inside .tinymce-mobile-context-toolbar, so that prefix is on everything here. */
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group {
+  align-items: center;
+  display: flex;
+  height: 100%;
+  flex: 1;
+  padding-bottom: 0.4em;
+  padding-top: 0.4em;
+  /* Make any buttons appearing on the left and right display in the centre (e.g. color edges) */
+  /* For widgets like the colour picker, use the whole height */
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog {
+  display: flex;
+  min-height: 1.5em;
+  overflow: hidden;
+  padding-left: 0;
+  padding-right: 0;
+  position: relative;
+  width: 100%;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain {
+  display: flex;
+  height: 100%;
+  transition: left cubic-bezier(0.4, 0, 1, 1) 0.15s;
+  width: 100%;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen {
+  display: flex;
+  flex: 0 0 auto;
+  justify-content: space-between;
+  width: 100%;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen input {
+  font-family: Sans-serif;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container {
+  display: flex;
+  flex-grow: 1;
+  position: relative;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container .tinymce-mobile-input-container-x {
+  -ms-grid-row-align: center;
+      align-self: center;
+  background: inherit;
+  border: none;
+  border-radius: 50%;
+  color: #888;
+  font-size: 0.6em;
+  font-weight: bold;
+  height: 100%;
+  padding-right: 2px;
+  position: absolute;
+  right: 0;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container.tinymce-mobile-input-container-empty .tinymce-mobile-input-container-x {
+  display: none;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next {
+  align-items: center;
+  display: flex;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous::before,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next::before {
+  align-items: center;
+  display: flex;
+  font-weight: bold;
+  height: 100%;
+  padding-left: 0.5em;
+  padding-right: 0.5em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous.tinymce-mobile-toolbar-navigation-disabled::before,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next.tinymce-mobile-toolbar-navigation-disabled::before {
+  visibility: hidden;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item {
+  color: #cccccc;
+  font-size: 10px;
+  line-height: 10px;
+  margin: 0 2px;
+  padding-top: 3px;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item.tinymce-mobile-dot-active {
+  color: #c8cbcf;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-font::before,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-heading::before {
+  margin-left: 0.5em;
+  margin-right: 0.9em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-font::before,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-heading::before {
+  margin-left: 0.9em;
+  margin-right: 0.5em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider {
+  display: flex;
+  flex: 1;
+  margin-left: 0;
+  margin-right: 0;
+  padding: 0.28em 0;
+  position: relative;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container {
+  align-items: center;
+  display: flex;
+  flex-grow: 1;
+  height: 100%;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container .tinymce-mobile-slider-size-line {
+  background: #cccccc;
+  display: flex;
+  flex: 1;
+  height: 0.2em;
+  margin-bottom: 0.3em;
+  margin-top: 0.3em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container {
+  padding-left: 2em;
+  padding-right: 2em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container {
+  align-items: center;
+  display: flex;
+  flex-grow: 1;
+  height: 100%;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container .tinymce-mobile-slider-gradient {
+  background: linear-gradient(to right, hsl(0, 100%, 50%) 0%, hsl(60, 100%, 50%) 17%, hsl(120, 100%, 50%) 33%, hsl(180, 100%, 50%) 50%, hsl(240, 100%, 50%) 67%, hsl(300, 100%, 50%) 83%, hsl(0, 100%, 50%) 100%);
+  display: flex;
+  flex: 1;
+  height: 0.2em;
+  margin-bottom: 0.3em;
+  margin-top: 0.3em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-black {
+  /* Not part of theming */
+  background: black;
+  height: 0.2em;
+  margin-bottom: 0.3em;
+  margin-top: 0.3em;
+  width: 1.2em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-white {
+  /* Not part of theming */
+  background: white;
+  height: 0.2em;
+  margin-bottom: 0.3em;
+  margin-top: 0.3em;
+  width: 1.2em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb {
+  /* vertically centering trick (margin: auto, top: 0, bottom: 0). On iOS and Safari, if you leave
+     * out these values, then it shows the thumb at the top of the spectrum. This is probably because it is
+     * absolutely positioned with only a left value, and not a top. Note, on Chrome it seems to be fine without
+     * this approach.
+    */
+  align-items: center;
+  background-clip: padding-box;
+  background-color: #455a64;
+  border: 0.5em solid rgba(136, 136, 136, 0);
+  border-radius: 3em;
+  bottom: 0;
+  color: #fff;
+  display: flex;
+  height: 0.5em;
+  justify-content: center;
+  left: -10px;
+  margin: auto;
+  position: absolute;
+  top: 0;
+  transition: border 120ms cubic-bezier(0.39, 0.58, 0.57, 1);
+  width: 0.5em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb.tinymce-mobile-thumb-active {
+  border: 0.5em solid rgba(136, 136, 136, 0.39);
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group > div {
+  align-items: center;
+  display: flex;
+  height: 100%;
+  flex: 1;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper {
+  flex-direction: column;
+  justify-content: center;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item {
+  align-items: center;
+  display: flex;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item:not(.tinymce-mobile-serialised-dialog) {
+  height: 100%;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-container {
+  display: flex;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input {
+  background: #ffffff;
+  border: none;
+  border-radius: 0;
+  color: #455a64;
+  flex-grow: 1;
+  font-size: 0.85em;
+  padding-bottom: 0.1em;
+  padding-left: 5px;
+  padding-top: 0.1em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::-webkit-input-placeholder {
+  /* WebKit, Blink, Edge */
+  color: #888;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::placeholder {
+  /* WebKit, Blink, Edge */
+  color: #888;
+}
+/* dropup */
+.tinymce-mobile-dropup {
+  background: white;
+  display: flex;
+  overflow: hidden;
+  width: 100%;
+}
+.tinymce-mobile-dropup.tinymce-mobile-dropup-shrinking {
+  transition: height 0.3s ease-out;
+}
+.tinymce-mobile-dropup.tinymce-mobile-dropup-growing {
+  transition: height 0.3s ease-in;
+}
+.tinymce-mobile-dropup.tinymce-mobile-dropup-closed {
+  flex-grow: 0;
+}
+.tinymce-mobile-dropup.tinymce-mobile-dropup-open:not(.tinymce-mobile-dropup-growing) {
+  flex-grow: 1;
+}
+/* TODO min-height for device size and orientation */
+.tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed) {
+  min-height: 200px;
+}
+@media only screen and (orientation: landscape) {
+  .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed) {
+    min-height: 200px;
+  }
+}
+@media only screen and (min-device-width : 320px) and (max-device-width : 568px) and (orientation : landscape) {
+  .tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed) {
+    min-height: 150px;
+  }
+}
+/* styles menu */
+.tinymce-mobile-styles-menu {
+  font-family: sans-serif;
+  outline: 4px solid black;
+  overflow: hidden;
+  position: relative;
+  width: 100%;
+}
+.tinymce-mobile-styles-menu [role="menu"] {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  position: absolute;
+  width: 100%;
+}
+.tinymce-mobile-styles-menu [role="menu"].transitioning {
+  transition: transform 0.5s ease-in-out;
+}
+.tinymce-mobile-styles-menu .tinymce-mobile-styles-item {
+  border-bottom: 1px solid #ddd;
+  color: #455a64;
+  cursor: pointer;
+  display: flex;
+  padding: 1em 1em;
+  position: relative;
+}
+.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser .tinymce-mobile-styles-collapse-icon::before {
+  color: #455a64;
+  content: "\e314";
+  font-family: 'tinymce-mobile', sans-serif;
+}
+.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-styles-item-is-menu::after {
+  color: #455a64;
+  content: "\e315";
+  font-family: 'tinymce-mobile', sans-serif;
+  padding-left: 1em;
+  padding-right: 1em;
+  position: absolute;
+  right: 0;
+}
+.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-format-matches::after {
+  font-family: 'tinymce-mobile', sans-serif;
+  padding-left: 1em;
+  padding-right: 1em;
+  position: absolute;
+  right: 0;
+}
+.tinymce-mobile-styles-menu .tinymce-mobile-styles-separator,
+.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser {
+  align-items: center;
+  background: #fff;
+  border-top: #455a64;
+  color: #455a64;
+  display: flex;
+  min-height: 2.5em;
+  padding-left: 1em;
+  padding-right: 1em;
+}
+.tinymce-mobile-styles-menu [data-transitioning-destination="before"][data-transitioning-state],
+.tinymce-mobile-styles-menu [data-transitioning-state="before"] {
+  transform: translate(-100%);
+}
+.tinymce-mobile-styles-menu [data-transitioning-destination="current"][data-transitioning-state],
+.tinymce-mobile-styles-menu [data-transitioning-state="current"] {
+  transform: translate(0%);
+}
+.tinymce-mobile-styles-menu [data-transitioning-destination="after"][data-transitioning-state],
+.tinymce-mobile-styles-menu [data-transitioning-state="after"] {
+  transform: translate(100%);
+}
+@font-face {
+  font-family: 'tinymce-mobile';
+  font-style: normal;
+  font-weight: normal;
+  src: url('fonts/tinymce-mobile.woff?8x92w3') format('woff');
+}
+@media (min-device-width: 700px) {
+  .tinymce-mobile-outer-container,
+  .tinymce-mobile-outer-container input {
+    font-size: 25px;
+  }
+}
+@media (max-device-width: 700px) {
+  .tinymce-mobile-outer-container,
+  .tinymce-mobile-outer-container input {
+    font-size: 18px;
+  }
+}
+.tinymce-mobile-icon {
+  font-family: 'tinymce-mobile', sans-serif;
+}
+.mixin-flex-and-centre {
+  align-items: center;
+  display: flex;
+  justify-content: center;
+}
+.mixin-flex-bar {
+  align-items: center;
+  display: flex;
+  height: 100%;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-editor-socket iframe {
+  background-color: #fff;
+  width: 100%;
+}
+.tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon {
+  /* Note, on the iPod touch in landscape, this isn't visible when the navbar appears */
+  background-color: #207ab7;
+  border-radius: 50%;
+  bottom: 1em;
+  color: white;
+  font-size: 1em;
+  height: 2.1em;
+  position: fixed;
+  right: 2em;
+  width: 2.1em;
+  align-items: center;
+  display: flex;
+  justify-content: center;
+}
+@media only screen and (min-device-width:700px) {
+  .tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon {
+    font-size: 1.2em;
+  }
+}
+.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket {
+  height: 300px;
+  overflow: hidden;
+}
+.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket iframe {
+  height: 100%;
+}
+.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-toolstrip {
+  display: none;
+}
+/*
+  Note, that if you don't include this (::-webkit-file-upload-button), the toolbar width gets
+  increased and the whole body becomes scrollable. It's important!
+ */
+input[type="file"]::-webkit-file-upload-button {
+  display: none;
+}
+@media only screen and (min-device-width : 320px) and (max-device-width : 568px) and (orientation : landscape) {
+  .tinymce-mobile-ios-container .tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon {
+    bottom: 50%;
+  }
+}

File diff suppressed because it is too large
+ 8 - 0
public/tinymce/skins/ui/oxide/skin.mobile.min.css


+ 19 - 0
scripts/verify-commit-msg.js

@@ -0,0 +1,19 @@
+const chalk = require('chalk');
+
+const msgPath = process.env.GIT_PARAMS;
+const msg = require('fs').readFileSync(msgPath, 'utf-8').trim();
+
+const commitRE = /^(revert: )?(feat|fix|polish|docs|style|refactor|perf|test|workflow|ci|chore|types|build)(\(.+\))?: .{1,50}/;
+
+if (!commitRE.test(msg)) {
+    console.log();
+    console.error(
+        `  ${chalk.bgRed.white(' ERROR ')} ${chalk.red('invalid commit message format.')}\n\n${
+            chalk.red('  Proper commit message format is required for automated changelog generation. Examples:\n\n')
+        }    ${chalk.green('feat(compiler): add \'comments\' option')}\n`
+    + `    ${chalk.green('fix(v-model): handle events on blur (close #28)')}\n\n${
+        chalk.red('  See https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md for more details.\n')
+    }${chalk.red(`  You can also use ${chalk.cyan('npm run commit')} to interactively generate a commit message.\n`)}`,
+    );
+    process.exit(1);
+}

+ 5 - 0
src/app.vue

@@ -0,0 +1,5 @@
+<template>
+    <div>
+        <router-view />
+    </div>
+</template>

+ 34 - 0
src/apps/account/api.js

@@ -0,0 +1,34 @@
+import request from '@/common/utils/request';
+
+const USER = '/server/user';
+
+export default {
+    // 用户登录
+    accountLogin(parmas) {
+        return request.post('/login/check', parmas);
+    },
+    // 退出登录
+    accountLogout(parmas) {
+        return request.post(`${USER}/logout`, { userId: parmas });
+    },
+    // 登录后获取用户信息
+    getUserInfo() {
+        return request.post(`${USER}/userInfo`);
+    },
+    // 修改密码
+    changePassword(parmas) {
+        return request.post(`${USER}/updatePassword`, parmas);
+    },
+    // 重置密码
+    resetPassword(parmas) {
+        return request.post(`${USER}/resetPassword`, { id: parmas });
+    },
+    // 查询组织列表
+    queryOrgListByUserId() {
+        return request.post(`${USER}/queryOrgListByUserId`);
+    },
+    // 修改账号
+    updateAccount(parmas) {
+        return request.post(`${USER}/updateAccount`, parmas);
+    },
+};

+ 334 - 0
src/apps/account/components/message-remind.vue

@@ -0,0 +1,334 @@
+<template>
+    <el-dialog
+        v-if="show"
+        title="消息提醒"
+        width="80%"
+        top="5vh"
+        :visible.sync="show"
+        :append-to-body="true"
+        :close-on-click-modal="false"
+        :close-on-press-escape="false"
+        :show-close="true"
+        :before-close="handleClose"
+        center
+    >
+        <el-row>
+            <el-col :span="7">
+                <el-button type="danger" size="small" :loading="flag" @click="handleDelete">
+                    批量删除
+                </el-button>
+                <el-button type="primary" size="small" plain @click="handleMessageBatcheRead">
+                    全部标为已读
+                </el-button>
+            </el-col>
+            <el-col :span="17">
+                <search-bar :data="searchData" @search="handleSearchFrom" />
+            </el-col>
+        </el-row>
+
+        <el-divider />
+
+        <el-table v-loading="loading" :data="tableData" stripe height="50vh" style="width: 100%" @selection-change="handleSelectionChange">
+            <el-table-column type="selection" width="30" />
+            <el-table-column prop="title" label="标题">
+                <template slot-scope="scope">
+                    <el-link :underline="false" @click="goDetail(scope.row)">
+                        [
+                        <span v-if="scope.row.type === 1">公告通知</span>
+                        <span v-if="scope.row.type === 2">流程审批</span>
+                        <span v-if="scope.row.type === 3">工作提醒</span>
+                        <span v-if="scope.row.type === 4">系统通知</span>
+                        <span v-if="scope.row.type === 5">工作汇报</span>
+                        <span v-if="scope.row.type === 6">党建纪检</span>
+                        <span v-if="scope.row.type === 7">工作动态</span>
+                        ] {{ scope.row.context }}
+                    </el-link>
+                </template>
+            </el-table-column>
+            <el-table-column prop="status" label="状态" width="100">
+                <template slot-scope="scope">
+                    <span v-if="scope.row.status === 0" type="text" class="text-danger">未读</span>
+                    <span v-if="scope.row.status === 1" type="text" class="text-success">已读</span>
+                </template>
+            </el-table-column>
+            <el-table-column prop="createTime" label="时间" width="200" />
+        </el-table>
+
+        <div class="page">
+            <el-pagination
+                class="page-position"
+                background
+                layout="prev, pager, next, sizes, total, jumper"
+                :current-page="page"
+                :page-sizes="[10,15,25,50,100]"
+                :page-size="size"
+                :total="total"
+                @size-change="handleSizeChange"
+                @current-change="handleCurrentChange"
+            />
+        </div>
+
+        <el-dialog
+            title="系统通知"
+            top="30vh"
+            :visible.sync="dialogVisible"
+            :append-to-body="true"
+            width="30%"
+            center
+        >
+            <!-- <span>{{ systemNotice.createTime }}</span> -->
+            <span class="notice-text">{{ systemNotice.content }}</span>
+            <!-- <span slot="footer" class="dialog-footer">
+                <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
+            </span> -->
+        </el-dialog>
+    </el-dialog>
+</template>
+<script>
+import Vue from 'vue';
+import Component from 'vue-class-component';
+import api from '@/apps/home/api';
+import journalApi from '@/apps/daily-record/api';
+import appVersionApi from '@/apps/app-version/api';
+import SearchBar from '@/common/components/search/search-bar.vue';
+
+@Component({
+    props: {
+        show: {
+            type: Boolean,
+            default: false,
+        },
+    },
+    components: {
+        SearchBar,
+    },
+})
+export default class MessageRemind extends Vue {
+    loading = false;
+
+    flag = false;
+
+    tableData = [];
+
+    page = 1;
+
+    size = 10;
+
+    total = 0;
+
+    searchData = [
+        {
+            type: 'input',
+            name: 'context',
+            placeholder: '请输入标题',
+        },
+        {
+            type: 'select',
+            name: 'type',
+            placeholder: '请选择类型',
+            data: [
+                { name: '公告通知', value: '1' },
+                { name: '流程审批', value: '2' },
+                { name: '工作提醒', value: '3' },
+                { name: '系统通知', value: '4' },
+                { name: '工作汇报', value: '5' },
+                { name: '党建纪检', value: '6' },
+                { name: '工作动态', value: '7' },
+            ],
+        },
+        {
+            type: 'select',
+            name: 'status',
+            placeholder: '请选择状态',
+            data: [
+                { name: '未读', value: '0' },
+                { name: '已读', value: '1' },
+            ],
+        },
+    ]
+
+    searchDataValue = [];
+
+    selectIds = [];
+
+    dialogVisible = false;
+
+    systemNotice = {};
+
+    handleClose() {
+        this.$emit('update:show', false);
+    }
+
+    mounted() {
+        this.searchDataValue = JSON.parse(JSON.stringify(this.searchData));
+        this.getListData();
+    }
+
+    goDetail(item) {
+        console.log(item);
+        if (item.status === 0) { // 未读标记已读
+            api.setMessageStatus(item.id).then(() => {
+                this.$commonvue.$emit('messageunreadnum'); // 发送未读消息更新事件
+            }).catch();
+            if (item.type === 5) {
+                const parmas = {
+                    workDailyId: item.busId,
+                    type: 0,
+                };
+                journalApi.updateReadState(parmas).then(() => {}).catch(() => {});
+            }
+        }
+        switch (item.type) {
+            case 1: // 公告通知
+                this.$router.push(`/notice/detail/${item.busId}`);
+                break;
+            case 2: // 工作通知
+                this.$router.push(`/apply/info/${item.busId}`);
+                break;
+            case 3: // 工作提醒
+                if (item.busType === 3) {
+                    this.$router.push('/apply/handle');
+                } else if (item.busType === 5) {
+                    this.$router.push(`/apply/info/${item.busId}`);
+                } else if (item.busType === 8) {
+                    this.$router.push(`/marketing/edit/${item.busId}`);
+                } else if (item.busType === 12) {
+                    this.$router.push(`/salary/record/log/${item.busId}`);
+                } else if (item.busType === 14) {
+                    console.log('rote', `/important/record/detail/${item.busId}`);
+                    this.$router.push(`/important/record/detail/${item.busId}`);
+                } else if (item.busType === 13) { // 工作动态报送
+                    this.$router.push(`/work/news/detail/1/${item.busId}`);
+                }
+                break;
+            case 4: // 系统通知
+                this.dialogVisible = true;
+                this.getAppVersionNoticeDetail(item.busId);
+                this.getListData();
+                break;
+            case 5: // 工作汇报
+                this.$router.push(`/work/report/detail/1/${item.busId}`);
+                break;
+            case 6: // 公告通知
+                this.$router.push(`/article/detail/${item.busId}`);
+                break;
+            case 7: // 工作动态
+                this.$router.push(`/work/news/detail/1/${item.busId}`);
+                break;
+            default:
+                break;
+        }
+        if (item.busType === 1 || item.busType === 2 || item.busType === 3
+        || item.busType === 4 || item.busType === 6 || item.busType === 7
+        || item.busType === 8 || item.busType === 11 || item.busType === 12 || item.busType === 14) {
+            this.$emit('update:show', false);
+        }
+        if(item.busType === 0){
+            this.getListData();
+        }
+    }
+
+    getAppVersionNoticeDetail(id) {
+        appVersionApi.getAppVersionNoticeDetail(id).then((res) => {
+            this.systemNotice = { ...res.data };
+        }).catch(() => {});
+    }
+
+    handleSearchFrom(obj) {
+        this.page = 1;
+        this.searchDataValue = obj;
+        this.getListData();
+    }
+
+    handleSizeChange(val) {
+        this.size = val;
+        this.getListData();
+    }
+
+    handleCurrentChange(val) {
+        this.page = val;
+        this.getListData();
+    }
+
+    getListData() {
+        this.loading = true;
+        const data = {
+            page: this.page,
+            size: this.size,
+        };
+        this.searchDataValue.forEach((item) => {
+            if (item.name === 'context' && item.value) {
+                data.context = item.value;
+            } else if (item.name === 'type' && item.value) {
+                data.type = item.value;
+            } else if (item.name === 'status' && item.value) {
+                data.status = item.value;
+            }
+        });
+        api.messageListData(data).then((res) => {
+            this.total = res.data.total;
+            this.tableData = res.data.list;
+            this.loading = false;
+        }).catch(() => {}).finally(() => {
+            this.loading = false;
+        });
+    }
+
+    handleSelectionChange(val) {
+        const ids = [];
+        val.forEach((item) => {
+            ids.push(item.id);
+        });
+        this.selectIds = ids;
+    }
+
+    handleDelete() {
+        if (this.selectIds.length === 0) {
+            this.$message.error('未选择要删除的数据');
+            return;
+        }
+        if (this.flag === false) {
+            this.$confirm('确定要删除选中的记录吗?', '提示', {
+                confirmButtonText: '确定',
+                cancelButtonText: '取消',
+                type: 'warning',
+            }).then(() => {
+                this.flag = true;
+                const ids = this.selectIds.join(',');
+                api.messageBatcheDelete(ids).then(() => {
+                    this.$message.success('删除成功!');
+                    this.$commonvue.$emit('messageunreadnum'); // 发送未读消息更新事件
+                    this.flag = false;
+                    if (this.tableData.length > 1) {
+                        this.getListData();
+                    } else {
+                        this.page = this.page > 1 ? this.page - 1 : 1;
+                        this.getListData();
+                    }
+                }).catch(() => {
+                    this.flag = false;
+                });
+            }).catch(() => {});
+        }
+    }
+
+    handleMessageBatcheRead() {
+        const parmas = {
+            type: 0,
+        };
+        api.messageBatcheRead(parmas).then(() => {
+            this.$commonvue.$emit('messageunreadnum'); // 发送未读消息更新事件
+            this.getListData();
+        }).catch(() => {});
+    }
+}
+</script>
+<style lang="scss" scoped>
+    .notice-text {
+        font-size: 14px;
+        font-family: 'Microsoft YaHei';
+        word-wrap: break-word;
+        word-break: break-all;
+        white-space: pre-line;
+    }
+</style>

+ 224 - 0
src/apps/account/components/page-aside.vue

@@ -0,0 +1,224 @@
+<template>
+    <el-scrollbar :style="asidebackground">
+        <div class="aside-header">
+            <span style="font-size:23px;color:#fff;font-weight:blod;">
+                京宜安科技
+            </span>
+            <!-- <img src="../../../common/assets/icons/logo.png"> -->
+        </div>
+        <el-menu
+            class="el-menu-vertical-demo"
+            :default-active="activeRouterIndex"
+            router
+            :background-color="backgroundcolor"
+            text-color="#fff"
+            active-text-color="#fff"
+            :collapse="false"
+            :unique-opened="true"
+        >
+            <!-- <el-menu-item v-if="userType !== 3" index="http://jd100.chaoxing.com" style="padding:0;height:auto;">
+                <el-link :underline="false" href="http://jd100.chaoxing.com" target="_blank" style="margin:0;">
+                    <el-image :src="news" style="width:100%;" />
+                </el-link>
+            </el-menu-item>
+            <el-menu-item v-if="userType !== 3" index="http://djk.chaoxing.com/index_9179.html" style="padding:0;height:auto;">
+                <el-link :underline="false" href="http://djk.chaoxing.com/index_9179.html" target="_blank" style="margin:0;">
+                    <el-image :src="sishi" style="width:100%;" />
+                </el-link>
+            </el-menu-item> -->
+            <el-menu-item index="/home">
+                <i class="el-icon-s-home" />
+                <span slot="title">
+                    系统首页
+                </span>
+            </el-menu-item>
+            <template v-for="item in list">
+                <template v-if="item.sub">
+                    <el-submenu :key="item.id" :index="item.id+item.name">
+                        <template slot="title">
+                            <i :class="item.icon" />
+                            <span>
+                                {{ item.name }}
+                            </span>
+                        </template>
+                        <template v-for="v in item.sub">
+                            <!-- <template v-if="v.sub">
+                                <el-submenu :key="v.id" :index="v.id+v.name">
+                                    <template slot="title">
+                                        <span>{{ item.name }}</span>
+                                    </template>
+                                    <template v-for="vv in v.sub">
+                                        <el-menu-item :key="vv.id" :index="vv.url">
+                                            {{ v.name }}
+                                        </el-menu-item>
+                                    </template>
+                                </el-submenu>
+                            </template>
+                            <template v-else> -->
+                            <el-menu-item :key="v.id" :index="v.url">
+                                {{ v.name }}
+                            </el-menu-item>
+                            <!-- </template> -->
+                        </template>
+                    </el-submenu>
+                </template>
+                <template v-else>
+                    <el-menu-item :key="item.id" :index="item.url">
+                        <i :class="item.icon" />
+                        <span slot="title">
+                            {{ item.name }}
+                        </span>
+                    </el-menu-item>
+                </template>
+            </template>
+        </el-menu>
+    </el-scrollbar>
+</template>
+
+<script>
+import Vue from 'vue';
+import { mapState } from 'vuex';
+import Component from 'vue-class-component';
+import { construct } from '@aximario/json-tree';
+import newspath from '@/apps/account/images/news.jpg';
+import sspath from '@/apps/account/images/sishi.jpg';
+
+@Component({
+    props: {
+        activeIndex: {
+            type: String,
+            default: '',
+        },
+    },
+    computed: {
+        activeRouterIndex() {
+            if (this.activeIndex !== '') {
+                return this.activeIndex;
+            }
+            return this.$router.currentRoute.path;
+        },
+        asidebackground: {
+            get() {
+                return {
+                    height: '100%',
+                    background: this.backgroundcolor,
+                };
+            },
+        },
+        ...mapState('account', [
+            'userRulesList',
+            'userInfo',
+        ]),
+    },
+})
+export default class PageAside extends Vue {
+    // backgroundcolor = '#304156'; // 背景色
+    // backgroundcolor = '#00152b'; // 背景色
+    // backgroundcolor = '#3c444d'; // 背景色
+     backgroundcolor = '#2A5294';
+
+    news = newspath;
+
+    sishi = sspath;
+
+    list = [];
+
+    flag = true;
+
+    userType = 3;
+
+    mounted() {
+        this.$commonvue.$on('ruleschange', () => {
+            this.getRulesData();
+        });
+
+        // 获取用户类型
+        if (!this.userInfo.userId) {
+            const timer = setInterval(() => {
+                if (this.userInfo.userId) {
+                    this.userType = this.userInfo.type;
+                    clearInterval(timer);
+                }
+            }, 200);
+        } else {
+            this.userType = this.userInfo.type;
+        }
+    }
+
+    beforeDestroy() {
+        this.$commonvue.$off('ruleschange');
+    }
+
+    // 获取菜单数据,格式化数据,加锁以防多次重复调用,产生错误
+    getRulesData() {
+        if (this.flag) {
+            this.flag = false;
+            // 刷选出符合要求的菜单项
+            const menus = [];
+            if (this.userRulesList && this.userRulesList.length > 0) {
+                this.userRulesList.forEach((item) => {
+                    if (item.parentId === 0 && item.btn === 0) {
+                        menus.push(item);
+                        this.userRulesList.forEach((item2) => {
+                            if (item2.parentId === item.id && item2.btn === 0) {
+                                menus.push(item2);
+                                this.userRulesList.forEach((item3) => {
+                                    if (item3.parentId === item2.id && item3.btn === 0) {
+                                        menus.push(item3);
+                                    }
+                                });
+                            }
+                        });
+                    }
+                });
+            }
+
+            // 转树形结构
+            const menutree = construct(menus, {
+                id: 'id',
+                pid: 'parentId',
+                children: 'sub',
+            });
+
+            this.list = JSON.parse(JSON.stringify(menutree));
+            this.flag = true;
+        }
+    }
+}
+
+</script>
+
+<style scoped>
+    .aside-header{
+        border-bottom: 1px solid #5c656e;
+        text-align: center;
+        height: 60px;
+        line-height: 60px;
+    }
+    .aside-header img{
+        width: 170px;
+        height: 36px;
+        vertical-align: middle;
+    }
+    .el-submenu .el-menu-item {
+        height: 44px;
+        line-height: 44px;
+        padding: 0 45px;
+        min-width: 200px;
+        padding-left: 53px !important;
+        /* background-color: #5a7df2 !important; */
+        background-color: rgba(255, 255, 255, 0.08) !important;
+
+    }
+    .el-submenu .el-menu-item:hover{
+        /* background-color: #020224 !important; */
+        background-color: rgba(255, 255, 255, 0.3) !important;
+    }
+    .el-menu-item.is-active{
+        /* border-right: 3px solid #EFEFEF; */
+        background-color: rgba(255, 255, 255, 0.2) !important;;
+    }
+    .el-menu-item i,.el-submenu__title i {
+        color: #fff;
+    }
+</style>

+ 235 - 0
src/apps/account/components/page-header.vue

@@ -0,0 +1,235 @@
+<template>
+    <div>
+        <el-menu class="el-menu-demo" mode="horizontal">
+            <!-- <el-menu-item index="5">
+                <el-link :underline="false" href="http://jd100.chaoxing.com" target="_blank">
+                    <el-image :src="news" style="width:69px;" />
+                </el-link>
+            </el-menu-item> -->
+            <!-- <el-submenu v-if="userType !== 1" index="1" class="org-list">
+                <template slot="title">
+                    {{ orgName }}
+                </template>
+                <el-menu-item v-for="item in orgList" :key="item.id" :index="item.id+item.name" @click="handleSwitchOrg(item)">
+                    {{ item.name }}
+                </el-menu-item>
+            </el-submenu> -->
+            <el-submenu index="2" class="pull-right">
+                <template slot="title">
+                    <!-- <el-avatar :size="40" class="approval-avatar-color">
+                        {{ shortName }}
+                    </el-avatar> -->
+                    欢迎 , {{ username }}
+                </template>
+                <el-menu-item v-if="userType === 0" index="account" @click="handlePersonalShow">
+                    个人信息
+                </el-menu-item>
+                <el-menu-item index="modifypass" @click="handleDialogShow">
+                    修改密码
+                </el-menu-item>
+                <el-menu-item index="logout" @click="handleLogout">
+                    退出登录
+                </el-menu-item>
+            </el-submenu>
+            <el-menu-item v-if="userType === 0" index="3" class="pull-right" @click="messageVisible = true">
+                <i v-if="messageUnreadNums === 0" class="el-icon-message-solid" style="color: #909399;" />
+                <el-badge v-if="messageUnreadNums > 0" :value="messageUnreadNums" :max="99" class="message-dot">
+                    <i class="el-icon-message-solid" />
+                </el-badge>
+            </el-menu-item>
+            <!-- <el-menu-item v-if="userType !== 3" index="4" class="pull-right">
+                <el-link :underline="false" href="https://www.dr-ipmc.org.cn" target="_blank">
+                    官网
+                </el-link>
+            </el-menu-item> -->
+            <el-menu-item v-if="userType === 0" index="5" class="pull-right" @click="goEmail()">
+                <i v-if="emailUnreadNums === 0" class="el-icon-message" style="color: #909399;" />
+                <el-badge v-if="emailUnreadNums > 0" :value="emailUnreadNums" :max="99" class="message-dot">
+                    <i class="el-icon-message" />
+                </el-badge>
+            </el-menu-item>
+        </el-menu>
+        <reset-password v-if="login" :dialog-form-visible.sync="dialogFormVisible" />
+        <personal-info :dialog-form-visible.sync="personalVisible" :personal-info="personalInfo" />
+        <message-remind v-if="messageVisible" :show.sync="messageVisible" />
+    </div>
+</template>
+
+<script>
+import Vue from 'vue';
+import { mapActions, mapState } from 'vuex';
+import Component from 'vue-class-component';
+import messageApi from '@/apps/home/api';
+import emailApi from '@/apps/email/api';
+import newspath from '@/apps/account/images/news.jpg';
+import ResetPassword from './reset-password.vue';
+import PersonalInfo from './personal-info.vue';
+import MessageRemind from './message-remind.vue';
+
+@Component({
+    inject: ['reload'],
+    props: {
+        username: {
+            type: String,
+            default: '',
+        },
+        shortName: {
+            type: String,
+            default: '',
+        },
+        getUserInfo: Function,
+    },
+    components: {
+        ResetPassword,
+        PersonalInfo,
+        MessageRemind,
+    },
+    methods: {
+        ...mapActions('account', [
+            'accountLogout',
+            'fetchResetUserInfo',
+            'getUserOrgs',
+        ]),
+    },
+    computed: {
+        ...mapState('account', [
+            'userInfo',
+        ]),
+    },
+})
+export default class PageHeader extends Vue {
+    loading = false;
+
+    news = newspath;
+
+    dialogFormVisible = false;
+
+    messageVisible = false;
+
+    personalVisible = false;
+
+    orgId = this.$cookies.get('orgId');
+
+    orgList = [];
+
+    userType = 1;
+
+    orgName = this.$cookies.get('orgName');
+
+    personalInfo = {};
+
+    messageUnreadNums = 0;
+
+    login = false;
+
+    emailUnreadNums = 0;
+
+    mounted() {
+        if (this.orgId === 0 || this.orgId === null) {
+            this.orgList = [];
+        } else {
+            // this.getUserOrgList();
+            this.messageUnread();
+
+            // 接收未读消息变化时间
+            this.$commonvue.$on('messageunreadnum', () => {
+                this.messageUnread();
+            });
+        }
+        // 获取用户类型
+        if (!this.userInfo.userId) {
+            const timer = setInterval(() => {
+                if (this.userInfo.userId) {
+                    this.userType = this.userInfo.type;
+                    clearInterval(timer);
+                }
+            }, 200);
+        } else {
+            this.userType = this.userInfo.type;
+        }
+        setTimeout(() => {
+            this.login = true;
+        }, 1000);
+
+        this.getEmailMessage();
+        setInterval(() => {
+            this.getEmailMessage();
+        }, 20000);
+    }
+
+    getEmailMessage() {
+        emailApi.newMessage().then((res) => {
+            this.emailUnreadNums = res.data.unseenCount;
+        }).catch(() => {}).finally(() => {});
+    }
+
+    goEmail() {
+        emailApi.login().then((res) => {
+            this.loading = false;
+            window.open(res.data.url);
+        }).catch(() => {}).finally(() => {
+            this.loading = false;
+        });
+    }
+
+    // 获取未读消息条数
+    messageUnread() {
+        messageApi.unreadMessageNum().then((res) => {
+            this.messageUnreadNums = res.data;
+        }).catch(() => {});
+    }
+
+    getUserOrgList() {
+        this.getUserOrgs().then((res) => {
+            this.orgList = res.data;
+        }).catch(() => {});
+    }
+
+    handleSwitchOrg(val) {
+        this.$cookies.set('orgId', val.id);
+        this.$cookies.set('orgName', val.name);
+        this.$router.push('/');
+        this.reload();
+        this.getUserInfo();
+    }
+
+    handleLogout() {
+        this.accountLogout().then(() => {
+            this.$store.dispatch('fetchResetUserInfo', {});
+        }).catch(() => {});
+        localStorage.clear();
+        setTimeout(() => {
+            // this.$message.success('退出成功!');
+            this.$cookies.keys().forEach(cookie => this.$cookies.remove(cookie));
+            window.location.href = '/login'; // 刷新页面
+            // this.$router.push('/login');
+        }, 500);
+    }
+
+    handleDialogShow() {
+        this.dialogFormVisible = true;
+    }
+
+    handlePersonalShow() {
+        this.personalVisible = true;
+        this.personalInfo = { ...this.userInfo };
+    }
+}
+</script>
+<style lang="scss" scoped>
+.el-badge__content.is-fixed {
+    top: 15px;
+    right: 10px;
+}
+.org-list {
+    margin-left: 5px;
+}
+.message-dot {
+    height: 15px;
+    line-height: 15px;
+    width: 22px;
+}
+.approval-avatar-color{
+    background-color: #409EFF;
+}
+</style>

+ 105 - 0
src/apps/account/components/personal-info.vue

@@ -0,0 +1,105 @@
+<template>
+    <el-dialog
+        v-if="dialogFormVisible"
+        title="个人信息"
+        width="45%"
+        :visible.sync="dialogFormVisible"
+        :append-to-body="true"
+        :close-on-click-modal="false"
+        :close-on-press-escape="false"
+        :show-close="false"
+        center
+    >
+        <el-form ref="form" :model="personalInfo" :rules="rules" label-width="80px">
+            <el-form-item label="姓名" prop="name">
+                <p class="el-input__inner text-input">
+                    {{ personalInfo.name }}
+                </p>
+            </el-form-item>
+            <el-form-item label="账号" prop="account">
+                <el-input v-model="personalInfo.account" type="text" placeholder="请填写账号" autocomplete="off" />
+            </el-form-item>
+            <el-form-item label="工号" prop="code">
+                <p class="el-input__inner text-input">
+                    {{ personalInfo.code }}
+                </p>
+            </el-form-item>
+            <el-form-item label="手机号" prop="phone">
+                <p class="el-input__inner text-input">
+                    {{ personalInfo.phone }}
+                </p>
+            </el-form-item>
+        </el-form>
+        <span slot="footer" class="dialog-footer">
+            <el-button @click="handleCancelDialog">取 消</el-button>
+            <el-button type="primary" :loading="loading" @click="handleUpdateAccount('form')">确 定</el-button>
+        </span>
+    </el-dialog>
+</template>
+<script>
+import Vue from 'vue';
+import { mapState } from 'vuex';
+import Component from 'vue-class-component';
+import { validateAccount } from '@/common/utils/validate';
+import accountApi from '../api';
+
+@Component({
+    props: {
+        dialogFormVisible: {
+            type: Boolean,
+            default: false,
+        },
+        personalInfo: Object,
+    },
+    computed: {
+        ...mapState('account', [
+            'userInfo',
+        ]),
+    },
+})
+export default class PersonalInfo extends Vue {
+    loading = false;
+
+    rules = {
+        account: [
+            { required: true, validator: validateAccount, trigger: ['blur', 'change'] },
+        ],
+    }
+
+    handleUpdateAccount(formName) {
+        const { account } = this.personalInfo;
+        const parmas = {
+            account,
+        };
+        if (account === '' || account === null) {
+            this.$message.error('请填写账号!');
+            return;
+        }
+        if (account === this.userInfo.account) {
+            this.$message.error('与原账户相同,未作出修改!');
+            return;
+        }
+        this.$refs[formName].validate((valid) => {
+            if (valid) {
+                this.loading = true;
+                accountApi.updateAccount(parmas).then(() => {
+                    this.$message.success('账号修改成功!');
+                    this.$emit('update:dialogFormVisible', false);
+                    this.$commonvue.$emit('on-update-account');
+                    this.loading = false;
+                }).catch(() => {}).finally(() => {
+                    this.loading = false;
+                });
+            }
+        });
+    }
+
+    handleCancelDialog() {
+        this.$emit('update:dialogFormVisible', false);
+    }
+
+    handleResetForm(formName) {
+        this.$refs[formName].resetFields();
+    }
+}
+</script>

+ 147 - 0
src/apps/account/components/reset-password.vue

@@ -0,0 +1,147 @@
+<template>
+    <el-dialog
+        v-if="dialogFormVisible"
+        title="修改密码"
+        width="35%"
+        :visible.sync="dialogFormVisible"
+        :append-to-body="true"
+        :close-on-click-modal="false"
+        :close-on-press-escape="false"
+        :show-close="false"
+        center
+    >
+        <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+            <el-form-item label="原始密码" prop="password">
+                <el-input v-model="form.password" type="password" size="small" autocomplete="off" />
+            </el-form-item>
+            <el-form-item label="新密码" prop="newPassword">
+                <el-input v-model="form.newPassword" type="password" size="small" show-password autocomplete="off" />
+            </el-form-item>
+            <el-form-item label="确认密码" prop="checkPass">
+                <el-input v-model="form.checkPass" type="password" size="small" show-password autocomplete="off" />
+            </el-form-item>
+        </el-form>
+        <span v-if="isFirstLogin" slot="footer" class="dialog-footer">
+            <el-button type="primary" :loading="loading" @click="handleChangePassword('form')">确 定</el-button>
+        </span>
+        <span v-else slot="footer" class="dialog-footer">
+            <el-button @click="handleCancelDialog">取 消</el-button>
+            <el-button type="primary" :loading="loading" @click="handleChangePassword('form')">确 定</el-button>
+        </span>
+    </el-dialog>
+</template>
+<script>
+import Vue from 'vue';
+import Component from 'vue-class-component';
+import { mapState } from 'vuex';
+import _string from 'lodash/string';
+import accountApi from '@/apps/account/api';
+import { aesEncrypt } from '@/common/utils/secret';
+import { validatePassword } from '@/common/utils/validate';
+
+@Component({
+    props: {
+        dialogFormVisible: {
+            type: Boolean,
+            default: false,
+        },
+    },
+    computed: {
+        ...mapState('account', [
+            'userInfo',
+        ]),
+    },
+})
+export default class ResetPassword extends Vue {
+    loading = false;
+
+    login = 0;
+
+    form = {
+        password: '',
+        newPassword: '',
+        checkPass: '',
+    };
+
+    isFirstLogin = false;
+
+    rules = {
+        password: [
+            { required: true, message: '请输入原始密码', trigger: 'blur' },
+        ],
+        newPassword: [
+            { required: true, validator: validatePassword, trigger: 'blur' },
+        ],
+        checkPass: [
+            { required: true, validator: validatePassword, trigger: 'blur' },
+        ],
+    }
+
+    mounted() {
+        this.login = this.$cookies.get('login');
+        if (Number(this.login) === 1) {
+            this.isFirstLogin = false;
+        } else if (Number(this.login) === 0) {
+            this.isFirstLogin = true;
+        }
+    }
+
+    handleChangePassword(formName) {
+        if (this.form.password !== '' && this.form.newPassword !== '' && this.form.password === this.form.newPassword) {
+            this.$message({
+                type: 'error',
+                message: '新密码不可与原始密码相同,请修改!',
+            });
+            return;
+        }
+        if (this.form.newPassword !== this.form.checkPass) {
+            this.$message({
+                type: 'error',
+                message: '两次输入的密码不一致,请确认!',
+            });
+            return;
+        }
+        const parmas = {
+            password: aesEncrypt(_string.trim(this.form.password)),
+            newPassword: aesEncrypt(_string.trim(this.form.newPassword)),
+        };
+        this.$refs[formName].validate((valid) => {
+            if (valid) {
+                this.loading = true;
+                accountApi.changePassword(parmas).then((res) => {
+                    this.$message({
+                        type: 'success',
+                        message: '密码修改成功!请妥善保管好您的密码!',
+                    });
+                    this.$emit('update:dialogFormVisible', false);
+                    this.$cookies.set('token', res.data.code);
+                    this.$cookies.set('login', 1);
+                    this.loading = false;
+
+
+                    window.location.href = '/login'; // 刷新页面
+                }).catch(() => {}).finally(() => {
+                    this.loading = false;
+                });
+            } else {
+                this.loading = false;
+            }
+        });
+    }
+
+    handleCancelDialog() {
+        // if (this.login === 1 || this.login === '1') {
+        //     this.$emit('update:dialogFormVisible', false);
+        //     this.handleResetForm('form');
+        // } else {
+        //     this.$message.warning('请先修改密码!');
+        // }
+        this.$emit('update:dialogFormVisible', false);
+        this.handleResetForm('form');
+    }
+
+    handleResetForm(formName) {
+        this.$refs[formName].resetFields();
+    }
+}
+</script>

BIN
src/apps/account/images/background.jpg


BIN
src/apps/account/images/background_old.jpg


BIN
src/apps/account/images/mima-shuruzhong@2x.png


BIN
src/apps/account/images/mima-weishuru@2x.png


BIN
src/apps/account/images/news.jpg


BIN
src/apps/account/images/shouji-shuruzhong@2x.png


BIN
src/apps/account/images/shouji@2x.png


BIN
src/apps/account/images/sishi.jpg


BIN
src/apps/account/images/yonghu-shuruzhong@2x.png


BIN
src/apps/account/images/yonghu-weishuru@2x.png


+ 18 - 0
src/apps/account/router.js

@@ -0,0 +1,18 @@
+export default {
+    path: '/',
+    redirect: '/login',
+    component: () => import(/* webpackChunkName: "account" */ '@/app.vue'),
+    children: [
+        {
+            name: 'login',
+            path: 'login',
+            component: () => import(/* webpackChunkName: "account" */ './views/login.vue'),
+        },
+        {
+            name: 'main',
+            path: 'main',
+            component: () => import(/* webpackChunkName: "account" */ '@/apps/account/views/main.vue'),
+            redirect: '/login',
+        },
+    ],
+};

+ 137 - 0
src/apps/account/store/account.js

@@ -0,0 +1,137 @@
+import utils from '@/common/utils/utils';
+import accountApi from '../api';
+
+export const SET_USERINFO = 'setUserInfo';
+export const FETCH_USERINFO = 'fetchUserInfo';
+export const ACCOUNT_LOGIN = 'accountLogin';
+export const ACCOUNT_LOGOUT = 'accountLogout';
+export const GET_USER_ORGS = 'getUserOrgs';
+export const SET_USER_ORGS = 'setUserOrgs';
+export const RESET_USERINFO = 'resetUserInfo';
+export const FETCH_RESET_USERINFO = 'fetchResetUserInfo';
+export const UPDATE_ACCOUNT_INFO = 'updateAccountInfo';
+export const UPDATE_ACCOUNT = 'updateAccount';
+
+export default {
+    namespaced: true,
+
+    state: {
+        userInfo: {
+            userId: null,
+            name: '',
+            account: '',
+            code: null,
+            phone: null,
+            type: 0, // 0=普通用户 1=超级管理员 2=机构管理员
+        },
+        userOrgs: [],
+        userDepList: [],
+        userJobList: [],
+        userRolesList: [],
+        userRulesList: [],
+        allRulesList: [], // 所有菜单权限
+    },
+
+    // getters: {
+    //     userInfo: state => state.userInfo,
+    //     userOrgs: state => state.userOrgs,
+    //     userDepList: state => state.userDepList,
+    //     userJobList: state => state.userJobList,
+    //     userRolesList: state => state.userRolesList,
+    //     userRulesList: state => state.userRulesList,
+    // },
+
+    mutations: {
+        [SET_USERINFO](state, payload) {
+            if (payload) {
+                state.userInfo = {
+                    userId: payload.user.id,
+                    name: payload.user.name,
+                    account: payload.user.account,
+                    code: payload.user.code,
+                    phone: payload.user.phone,
+                };
+                state.userDepList = payload.depList;
+                state.userJobList = payload.jobList;
+                state.userRolesList = payload.rolesList;
+                state.userRulesList = payload.rulesList;
+                state.allRulesList = payload.allRulesList;
+
+                let userType = 0;
+                for (const o in state.userRolesList) { // eslint-disable-line
+                    if (state.userRolesList[o].type === 1) {
+                        userType = 1;
+                        break;
+                    } else if (state.userRolesList[o].type === 2) {
+                        userType = 2;
+                        break;
+                    } else if (state.userRolesList[o].type === 4) {
+                        userType = 3;
+                        break;
+                    }
+                }
+                state.userInfo.type = userType;
+                utils.setLocalStorage('user', JSON.stringify(state));
+                localStorage.setItem('name', payload.user.name);
+            } else {
+                state.userInfo = {
+                    userId: null,
+                    name: '',
+                    account: '',
+                };
+                state.userDepList = [];
+                state.userJobList = [];
+                state.userRolesList = [];
+                state.userRulesList = [];
+                localStorage.clear();
+            }
+        },
+        [SET_USER_ORGS](state, payload) {
+            if (payload) {
+                state.userOrgs = payload;
+            } else {
+                state.userOrgs = [];
+            }
+            if (state.userInfo.userId) {
+                utils.setLocalStorage('user', JSON.stringify(state));
+            }
+        },
+        [RESET_USERINFO](state) {
+            Object.assign({}, state);
+        },
+        [UPDATE_ACCOUNT_INFO](state, payload) {
+            state.userInfo.account = payload;
+            utils.setLocalStorage('user', JSON.stringify(state));
+        },
+    },
+
+    actions: {
+        [FETCH_USERINFO](context) {
+            return accountApi.getUserInfo().then((res) => {
+                context.commit(SET_USERINFO, res.data);
+                return res.data;
+            });
+        },
+        [ACCOUNT_LOGIN](context, data) {
+            return accountApi.accountLogin(data).then((res) => {
+                context.commit(SET_USER_ORGS, res.data.orgInfo);
+                return res;
+            });
+        },
+        [ACCOUNT_LOGOUT]({ commit }) {
+            return accountApi.accountLogout().then((res) => {
+                commit(SET_USERINFO, {});
+                return res;
+            });
+        },
+        [FETCH_RESET_USERINFO]({ commit }) {
+            commit(RESET_USERINFO, {});
+        },
+        [GET_USER_ORGS](context) {
+            return accountApi.queryOrgListByUserId().then((res) => {
+                context.commit(SET_USER_ORGS, res.data);
+                return res;
+            });
+        },
+    },
+};

+ 124 - 0
src/apps/account/style.scss

@@ -0,0 +1,124 @@
+body{
+    //background-color: #E9EEF3;
+    background: #e7e8eb;
+}
+.color-info{
+    color: #909399!important;
+}
+.bg-white{
+    background-color: #FFFFFF!important;
+}
+.el-header{
+    position: fixed;
+    z-index: 100;
+    top: 0;
+    right: 0;
+    height: 60px;
+    line-height: 60px;
+    padding: 0!important;
+    color: #222222;
+    width: calc(100% - 210px);
+    background-color: #FFFFFF;
+}
+
+.el-menu--horizontal>.el-menu-item,.el-header .is-active,.el-menu--horizontal>.el-submenu.is-active .el-submenu__title ,.el-menu--horizontal>.el-submenu .el-submenu__title {
+    border-bottom: 0 !important;
+}
+
+.el-footer{
+    background-color: #FFFFFF;
+    line-height: 50px;
+    height: 50px !important;
+    padding-left: 210px !important;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+
+.el-aside {
+    position: fixed;
+    z-index: 1000;
+    top: 0;
+    left: 0;
+    right: 0px;
+    bottom: 0px;
+    // margin-top: 1px;
+    background-color: #FFFFFF;
+    border-right: solid 1px #e6e6e6;
+    .el-scrollbar {
+        .el-scrollbar__wrap {
+            overflow-x: hidden!important;
+        }
+    }
+}
+.el-aside .el-menu {
+    border-right: 0;
+}
+.el-container{
+    min-height: 100vh;
+}
+.el-main {
+    padding: 75px 15px 15px 225px!important;
+}
+.main-box{
+    background-color: #FFFFFF;
+    overflow: hidden;
+    padding: 15px;
+}
+.nav {
+    height: 40px;
+    display: flex;
+    align-items: center;
+    background: #fff;
+    border-radius: 4px;
+    padding-left: 25px;
+    margin-bottom: 3px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1)
+}
+// 分页样式
+.page-position {
+    text-align: center;
+    margin-top: 2%;
+}
+
+.el-table th {
+    background: rgb(248, 248, 249);
+    color: rgb(62, 62, 62);
+}
+.el-link{
+    margin: 0 6px;
+}
+
+#flowtab .el-tabs__nav-scroll {
+    display: flex;
+    justify-content: center;
+}
+// p标签样式初始化
+.text-input {
+    margin: 0;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    .disabled {
+        pointer-events: none;
+        filter: alpha(opacity=50); /*IE滤镜,透明度50%*/
+        -moz-opacity: 0.5; /*Firefox私有,透明度50%*/
+        opacity: 0.5; /*其他,透明度50%*/
+    }
+}
+.textarea-input {
+    margin: 0;
+    min-height: 100px;
+}
+// no data
+.no-data {
+    margin-top: 35px;
+    text-align:center;
+    .no-data-tips{
+        color:#CDCECE;
+    }
+}
+div:focus, span:focus {
+    outline: none;
+}
+

+ 135 - 0
src/apps/account/views/login.js

@@ -0,0 +1,135 @@
+import Vue from 'vue';
+import { mapActions } from 'vuex';
+import Component from 'vue-class-component';
+import _string from 'lodash/string';
+// import accountApi from '../api';
+import bannerApi from '@/apps/banner/api';
+// import utils from '@/common/utils/utils';
+import { aesEncrypt } from '@/common/utils/secret';
+import logoIcon from '@/common/assets/icons/logo.png';
+import logotopIcon from '@/common/assets/icons/logotop.png';
+import userUnIcons from '../images/yonghu-weishuru@2x.png';
+import userIcons from '../images/yonghu-shuruzhong@2x.png';
+import passwordUnIcons from '../images/mima-weishuru@2x.png';
+import passwordIcons from '../images/mima-shuruzhong@2x.png';
+import phoneUnIcons from '../images/shouji@2x.png';
+import phoneIcons from '../images/shouji-shuruzhong@2x.png';
+import bannerImg from '../images/background.jpg';
+
+@Component({
+    methods: {
+        ...mapActions('account', [
+            'accountLogin',
+        ]),
+    },
+})
+
+export default class Login extends Vue {
+    bannerHeight = '';
+
+    cur = 0;
+
+    logo = logoIcon;
+
+    logotop = logotopIcon;
+
+    icons = {
+        userUnIcon: userUnIcons,
+        userIcon: userIcons,
+        passwordUnIcon: passwordUnIcons,
+        passwordIcon: passwordIcons,
+        phoneUnIcon: phoneUnIcons,
+        phoneIcon: phoneIcons,
+    }
+
+    icon = {
+        userIcon: this.icons.userUnIcon,
+        passwordIcon: this.icons.passwordUnIcon,
+        phoneIcon: this.icons.phoneUnIcon,
+    }
+
+    loginForm = {
+        account: '',
+        password: '',
+    }
+
+    banners = [
+        { img: bannerImg },
+    ];
+
+    created() {
+        // document.onkeypress = (e) => {
+        //     const keycode = document.all ? e.keyCode : e.which;
+        //     if (this.$route.path === '/login' && keycode === 13) {
+        //         this.handleAccountLogin();
+        //     }
+        // };
+        // if (utils.IEVersion() !== -1 && utils.IEVersion() !== 'edge') {
+        //     this.$confirm('IE浏览器兼容性较差,为了您的体验,请勿使用IE浏览器', '提示', {
+        //         confirmButtonText: '确定',
+        //         type: 'warning',
+        //         center: true,
+        //         showCancelButton: false,
+        //     });
+        // }
+        bannerApi.bannerLoginImg().then((res) => {
+            this.banners = res.data;
+        }).catch(() => {});
+    }
+
+    mounted() {
+        this.$cookies.isKey('token');
+        if (this.$cookies.isKey('token')) {
+            this.$router.push({ path: '/home' });
+        }
+        this.bannerHeight = document.documentElement.clientHeight;
+        window.onresize = () => {
+            this.bannerHeight = document.documentElement.clientHeight;
+        };
+    }
+
+    handleAccountLogin() {
+        const parmas = {
+            account: _string.trim(this.loginForm.account),
+            password: aesEncrypt(_string.trim(this.loginForm.password)),
+        };
+        if (_string.trim(this.loginForm.account) === '' || _string.trim(this.loginForm.password) === '') {
+            this.$message.error('账号和密码不能为空');
+        } else {
+            this.accountLogin(parmas).then((res) => {
+                // this.$message.success('登录成功!');
+                this.$cookies.set('token', res.data.tokenInfo.code);
+                this.$cookies.set('userId', res.data.tokenInfo.userId);
+                if (res.data.orgInfo.length > 0) {
+                    this.$cookies.set('orgId', res.data.orgInfo[0].id);
+                    this.$cookies.set('orgName', res.data.orgInfo[0].name);
+                }
+                this.$router.push({ path: '/home' });
+            }).catch(() => {});
+        }
+    }
+
+    handleChangeUserIcon() {
+        this.icon.userIcon = this.icons.userIcon;
+    }
+
+    handleChangeUnUserIcon() {
+        this.icon.userIcon = this.icons.userUnIcon;
+    }
+
+    handleChangePasswordIcon() {
+        this.icon.passwordIcon = this.icons.passwordIcon;
+    }
+
+    handleChangeUnPasswordIcon() {
+        this.icon.passwordIcon = this.icons.passwordUnIcon;
+    }
+
+    handleChangePhoneIcon() {
+        this.icon.phoneIcon = this.icons.phoneIcon;
+    }
+
+    handleChangeUnPhoneIcon() {
+        this.icon.phoneIcon = this.icons.phoneUnIcon;
+    }
+}

+ 125 - 0
src/apps/account/views/login.scss

@@ -0,0 +1,125 @@
+.login-layout {
+    .login-img {
+        width: 100%;
+        height: 100%;
+        object-fit: cover;
+    }
+    .login-div {
+        position: absolute;
+        top: 30%;
+        left: 60%;
+        padding: 30px;
+        z-index: 999;
+        width: 360px;
+        height: 360px;
+        color: rgba(80, 80, 80, 1);
+        background-color: rgba(255, 255, 255, 1);
+        border-radius: 5px;
+        font-size: 14px;
+        line-height: 150%;
+        // border: rgba(220, 225, 230, 1) solid 1px;
+        text-align: center;
+    }
+    .login-header {
+        width: 300px;
+        height: 40px;
+        line-height: 40px;
+        margin: 40px auto;
+    }
+    .login-header .login-btn {
+        width: 148px;
+        height: 40px;
+        line-height: 40px;
+        color: #949494;
+        float: left;
+        font-size: 18px;
+        font-family: 'Microsoft YaHei';
+        cursor: pointer;
+    }
+    .login-header .login-btn.cur {
+        color: #5A7DF2;
+        font-size: 20px;
+        font-weight: 500;
+        border-bottom: 2px solid #5A7DF2;
+    }
+    .login-body {
+        height: 240px;
+    }
+    .login-input {
+        width: 300px;
+        border-bottom: 1px solid #e5e5e5;
+        margin: 0 auto 33px auto;
+    }
+    .login-input img {
+        width: 30px;
+        height: 30px;
+        vertical-align: middle;
+    }
+    .login-input .full-input {
+        width: 260px;
+        height: 40px;
+        font-size: 16px;
+        line-height: 44px;
+        padding-left: 8px;
+        border: 0;
+    }
+    .login-input .unfull-input {
+        width: 140px;
+        height: 40px;
+        font-size: 16px;
+        line-height: 44px;
+        padding-left: 9px;
+        border: 0;
+    }
+    .login-input .login-sms {
+        width: 118px;
+        height: 30px;
+        line-height: 30px;
+        background:#DAEFFF;
+        border-radius: 3px;
+        font-size: 10px;
+        font-family: PingFang SC;
+        font-weight:500;
+        color:rgba(153,153,153,1);
+        display: inline-block;
+        text-decoration: none;
+    }
+    .login-input .cur {
+        color: #b1b1b1;
+        border: 1px solid #b1b1b1;
+    }
+    .login-input1 {
+        width: 300px;
+        height: 44px;
+        margin: 30px auto 0 auto;
+    }
+    .login-input1 .forget {
+        color: #949494;
+        text-decoration: none;
+    }
+    .login-submit{
+        width: 300px;
+        height: 40px;
+        color: rgba(255, 255, 255, 1);
+        background-color: #2A5295;
+        // background-color: #DB2117;
+        border-radius: 3px;
+        font-size: 16px;
+        text-align: center;
+        border: 0;
+        outline: none;
+    }
+}
+
+.login-header{
+    position: relative;
+    text-align: center;
+}
+.logo-top{
+    position: absolute;
+    width: 150px;
+    height: 150px;
+    top:-150px;
+    left: 75px;
+    z-index: 100;
+}

+ 90 - 0
src/apps/account/views/login.vue

@@ -0,0 +1,90 @@
+<template>
+    <div class="login-layout">
+        <el-carousel :height="bannerHeight + 'px'" arrow="never">
+            <el-carousel-item v-for="item in banners" :key="item.id">
+                <img class="login-img" :src="item.img" alt="">
+            </el-carousel-item>
+        </el-carousel>
+        <div class="login-div">
+            <div class="login-header">
+                <!-- <div class="login-btn" :class="{ cur:cur == 0 }" @click="cur=0">
+                    密码登录
+                </div>
+                <div class="login-btn" :class="{ cur:cur == 1 }" @click="cur=1">
+                    验证码登录
+                </div> -->
+                <img :src="logotop" class="logo-top">
+                <div class="">
+                    <img :src="logo" style="width: 300px;">
+                </div>
+            </div>
+            <div class="login-body">
+                <div v-show="cur==0" class="login-ss">
+                    <div class="login-input">
+                        <img :src="icon.userIcon">
+                        <input
+                            v-model="loginForm.account"
+                            class="full-input"
+                            type="text"
+                            placeholder="请输入账号/手机号/工号"
+                            @blur="handleChangeUnUserIcon"
+                            @focus="handleChangeUserIcon"
+                            @keyup.enter="handleAccountLogin"
+                        >
+                    </div>
+                    <div class="login-input">
+                        <img :src="icon.passwordIcon">
+                        <input
+                            v-model="loginForm.password"
+                            class="full-input"
+                            type="password"
+                            placeholder="请输入密码"
+                            @blur="handleChangeUnPasswordIcon"
+                            @focus="handleChangePasswordIcon"
+                            @keyup.enter="handleAccountLogin"
+                        >
+                    </div>
+                    <div class="login-footer">
+                        <el-button class="login-submit" @click="handleAccountLogin">
+                            登 录
+                        </el-button>
+                    </div>
+                    <!-- <div class="login-input1 text-right">
+                        <a class="forget" href="">忘记密码?</a>
+                    </div> -->
+                </div>
+                <!-- <div v-show="cur==1" class="login-ss">
+                    <div class="login-input">
+                        <img :src="icon.phoneIcon">
+                        <input
+                            class="full-input"
+                            type="text"
+                            placeholder="请输入手机号"
+                            @blur="handleChangeUnPhoneIcon"
+                            @focus="handleChangePhoneIcon"
+                        >
+                    </div>
+                    <div class="login-input">
+                        <img :src="icon.passwordIcon">
+                        <input
+                            class="unfull-input"
+                            type="text"
+                            placeholder="请输入验证码"
+                            @blur="handleChangeUnPasswordIcon"
+                            @focus="handleChangePasswordIcon"
+                        >
+                        <a class="login-sms" href="javascript:;"><span>获取验证码</span></a>
+                    </div>
+                    <div class="login-footer">
+                        <el-button class="login-submit">
+                            登 录
+                        </el-button>
+                    </div>
+                </div> -->
+            </div>
+        </div>
+    </div>
+</template>
+
+<script src="./login.js" />
+<style lang="scss" scoped src="./login.scss" />

+ 206 - 0
src/apps/account/views/main.vue

@@ -0,0 +1,206 @@
+<template>
+    <el-container>
+        <el-aside width="210px">
+            <page-aside :active-index="activeIndex" />
+        </el-aside>
+        <reset-password :dialog-form-visible.sync="dialogFormVisible" />
+        <el-container>
+            <el-header>
+                <page-header v-if="isRouterAlive" :username="username" :short-name="shortName" :get-user-info="getUserInfo" />
+            </el-header>
+            <el-main>
+                <router-view v-if="isRouterAlive" :key="$route.fullPath" />
+            </el-main>
+            <el-footer>Copyright &copy; 2023 - {{ getFullYear.getFullYear() }} 京宜安一体化协同办公系统. All rights reserved.</el-footer>
+        </el-container>
+    </el-container>
+</template>
+
+<script>
+import Vue from 'vue';
+import { mapActions, mapState } from 'vuex';
+import Component from 'vue-class-component';
+import utils from '@/common/utils/utils';
+import PageHeader from '../components/page-header.vue';
+import PageAside from '../components/page-aside.vue';
+import ResetPassword from '../components/reset-password.vue';
+
+@Component({
+    methods: {
+        ...mapActions('account', [
+            'fetchUserInfo',
+        ]),
+    },
+    computed: {
+        ...mapState('account', [
+            'userInfo',
+        ]),
+    },
+    components: {
+        PageHeader,
+        PageAside,
+        ResetPassword,
+    },
+    provide() {
+        return {
+            reload: this.reload,
+        };
+    },
+})
+
+export default class Main extends Vue {
+    isRouterAlive = true;
+
+    dialogFormVisible = false;
+
+    activeIndex = '';
+
+    username = '';
+
+    shortName = '';
+
+    socket = '';
+
+    getFullYear = new Date();
+
+    reload() {
+        this.isRouterAlive = false;
+        this.$nextTick(() => {
+            this.isRouterAlive = true;
+        });
+    }
+
+    created() {
+        this.getUserInfo();
+        this.wsinit();
+        this.$commonvue.$on('on-update-account', () => {
+            this.getUserInfo();
+        });
+
+        // 解决非菜单跳转菜单高亮问题
+        this.$router.afterEach((to) => {
+            if (this.$checkPermission([to.path])) {
+                this.activeIndex = to.path;
+            }
+        });
+        // if (utils.IEVersion() !== -1 && utils.IEVersion() !== 'edge') {
+        //     this.$confirm('IE浏览器兼容性较差,为了您的体验,请勿使用IE浏览器', '提示', {
+        //         confirmButtonText: '确定',
+        //         type: 'warning',
+        //         center: true,
+        //         showCancelButton: false,
+        //     });
+        // }
+    }
+
+    mounted() {
+        this.isCheckToken();
+    }
+
+    isCheckToken() {
+        const time = 1000;
+        let temp = 1;
+        let timer = null;
+        timer = setInterval(() => {
+            const token = this.$cookies.get('token');
+            if (temp === 1) {
+                if (token === undefined || token === '' || token === null) {
+                    this.$message.error('登录信息已失效,请重新登录!');
+                    setTimeout(() => {
+                        this.$router.push({ path: '/login' });
+                    }, 3000);
+                    temp = 0;
+                }
+            }
+        }, time);
+        if (temp === 0) {
+            clearInterval(timer);
+        }
+    }
+
+    getUserInfo() {
+        this.fetchUserInfo().then((data) => {
+            this.$cookies.set('login', data.user.login);
+            this.username = localStorage.getItem('name');
+            this.shortName = utils.getShortName(localStorage.getItem('name'));
+            const { login } = data.user;
+            if (login === 0) {
+                this.dialogFormVisible = true;
+                this.$router.push({ path: '/home' });
+                this.activeIndex = '/home';
+            } else {
+                this.dialogFormVisible = false;
+            }
+
+            this.$commonvue.$emit('ruleschange');
+        }).catch(() => {});
+    }
+
+    wsinit() {
+        if (typeof (WebSocket) === 'undefined') {
+            this.$message.error('您的浏览器不支持socket');
+        } else {
+            const userId = this.$cookies.get('userId');
+            if (userId && userId !== undefined) {
+                // const wsurl = `${process.env.VUE_APP_WSURL}/webSocket/scaleServer/${userId}`;
+                const wsurl = process.env.VUE_APP_WSURL;
+                // 实例化socket
+                this.socket = new WebSocket(wsurl);
+                // 监听socket连接
+                this.socket.onopen = this.onOpen;
+                // 监听socket错误信息
+                this.socket.onerror = this.onError;
+                // 监听socket关闭信息
+                this.socket.onclose = this.onClose;
+                // 监听socket消息
+                this.socket.onmessage = this.onMessage;
+            } else {
+                window.location.href = '/login'; // 刷新页面
+            }
+        }
+    }
+
+    onOpen() {
+        this.wssend({ type: 'login', userId: this.$cookies.get('userId') });
+        // 60s发送一次心跳,保活
+        setInterval(() => {
+            this.wssend({ type: 'ping' });
+        }, 30000);
+    }
+
+    onError() {
+        setTimeout(() => {
+            this.wsinit(); // 断线重连
+        }, 30000);
+    }
+
+    onMessage(msg) {
+        const data = JSON.parse(msg.data);
+        switch (data.type) {
+            case 'unlogin': // 未登录,发送登录
+                this.wssend({ type: 'login', userId: this.$cookies.get('userId') });
+                break;
+            case 'msgcount':
+                this.$commonvue.$emit('newmessagereload'); // 更新首页
+                this.$commonvue.$emit('messageunreadnum'); // 更新未读消息数
+                break;
+            default:
+                break;
+        }
+    }
+
+    onClose() {
+        // this.wsinit(); // 断线重连
+        setTimeout(() => {
+            this.wsinit(); // 断线重连
+        }, 30000);
+    }
+
+    // 发送消息
+    wssend(params) {
+        this.socket.send(JSON.stringify(params));
+    }
+}
+</script>
+
+<style lang="scss" src="../style.scss" />

+ 59 - 0
src/apps/app-modules/api.js

@@ -0,0 +1,59 @@
+import request from '@/common/utils/request';
+
+const MODULE = '/server/appIcon';
+const MODULECATE = '/server/appIconCate';
+
+export default {
+    // APP模块 列表
+    getModuleList(parmas) {
+        return request.post(`${MODULE}/list`, parmas);
+    },
+    // APP模块 详情
+    getModuleDetail(parmas) {
+        return request.post(`${MODULE}/detail`, { id: parmas });
+    },
+    // APP模块 修改
+    updateModuleInfo(parmas) {
+        return request.post(`${MODULE}/update`, parmas);
+    },
+    // APP模块 添加
+    addModuleInfo(parmas) {
+        return request.post(`${MODULE}/save`, parmas);
+    },
+    // APP模块 删除
+    deleteModule(parmas) {
+        return request.post(`${MODULE}/del`, { id: parmas });
+    },
+    // 启用禁用APP模块
+    disableOrEnableModule(parmas) {
+        return request.post(`${MODULE}/changeStatus`, parmas);
+    },
+    // APP模块 分组 列表
+    getModuleCateList(parmas) {
+        return request.post(`${MODULECATE}/list`, parmas);
+    },
+    // APP模块 分组 详情
+    getModuleCateDetail(parmas) {
+        return request.post(`${MODULECATE}/detail`, { id: parmas });
+    },
+    // APP模块 分组 修改
+    updateModuleCateInfo(parmas) {
+        return request.post(`${MODULECATE}/update`, parmas);
+    },
+    // APP模块 分组 添加
+    addModuleCateInfo(parmas) {
+        return request.post(`${MODULECATE}/save`, parmas);
+    },
+    // APP模块 分组 删除
+    deleteModuleGroupInfo(parmas) {
+        return request.post(`${MODULECATE}/del`, { id: parmas });
+    },
+    // 分组 & 模块 列表
+    getModuleGroupList() {
+        return request.post(`${MODULECATE}/listDetail`);
+    },
+    // 启用禁用模块分组
+    disableOrEnableGroup(parmas) {
+        return request.post(`${MODULECATE}/changeStatus`, parmas);
+    },
+};

+ 124 - 0
src/apps/app-modules/components/module-group-create.vue

@@ -0,0 +1,124 @@
+<template>
+    <el-dialog
+        v-if="dialogFormVisible"
+        title="App模块分组"
+        width="35%"
+        :visible.sync="dialogFormVisible"
+        :append-to-body="true"
+        :close-on-click-modal="false"
+        :close-on-press-escape="false"
+        :show-close="false"
+        center
+    >
+        <el-form ref="form" :model="groupForm" :rules="rules" label-width="80px">
+            <el-form-item label="分组名称" prop="name">
+                <el-input
+                    v-model="groupForm.name"
+                    type="text"
+                    maxlength="20"
+                    show-word-limit
+                    size="small"
+                    autocomplete="off"
+                    placeholder="请输入分组名称"
+                />
+            </el-form-item>
+            <!-- <el-form-item label="状态" prop="enable">
+                <el-radio v-model="groupForm.enable" label="0">
+                    禁用
+                </el-radio>
+                <el-radio v-model="groupForm.enable" label="1">
+                    启用
+                </el-radio>
+            </el-form-item> -->
+        </el-form>
+        <span slot="footer" class="dialog-footer">
+            <el-button type="primary" :loading="loading" @click="handleAddOrEditModuleGroup('form')">确 定</el-button>
+            <el-button @click="handleCloseDialog">取 消</el-button>
+        </span>
+    </el-dialog>
+</template>
+<script>
+import Vue from 'vue';
+import Component from 'vue-class-component';
+import moduleApi from '../api';
+
+@Component({
+    props: {
+        dialogFormVisible: {
+            type: Boolean,
+            default: false,
+        },
+        getModuleGroupList: Function,
+        groupForm: Object,
+    },
+})
+export default class ModuleGroupCreate extends Vue {
+    loading = false;
+
+    form = {
+        ...this.groupForm,
+    };
+
+    rules = {
+        name: [
+            { required: true, message: '请输入分组名称', trigger: 'blur' },
+        ],
+    }
+
+    created() {
+        this.getGroupForm();
+    }
+
+    getGroupForm() {
+        this.form = {
+            ...this.groupForm,
+        };
+    }
+
+    handleAddOrEditModuleGroup(formName) {
+        if (this.groupForm.name === '其他') {
+            this.$message.error('不可与系统默认分组名称相同!!!');
+            return;
+        }
+        this.$refs[formName].validate((valid) => {
+            if (valid) {
+                this.loading = true;
+                if (this.groupForm.id) {
+                    this.getGroupForm();
+                    moduleApi.updateModuleCateInfo(this.form).then(() => {
+                        this.loading = false;
+                        this.$message.success('编辑成功!');
+                        this.$emit('update:dialogFormVisible', false);
+                        this.getModuleGroupList();
+                        this.handleResetForm('form');
+                    }).catch(() => {}).finally(() => {
+                        this.loading = false;
+                    });
+                } else {
+                    this.getGroupForm();
+                    moduleApi.addModuleCateInfo(this.form).then(() => {
+                        this.loading = false;
+                        this.$message.success('保存成功!');
+                        this.$emit('update:dialogFormVisible', false);
+                        this.getModuleGroupList();
+                        this.handleResetForm('form');
+                    }).catch(() => {}).finally(() => {
+                        this.loading = false;
+                    });
+                }
+            } else {
+                this.loading = false;
+            }
+        });
+    }
+
+    handleResetForm(formName) {
+        this.$refs[formName].resetFields();
+    }
+
+    handleCloseDialog() {
+        this.$emit('update:dialogFormVisible', false);
+        this.handleResetForm('form');
+    }
+}
+</script>

+ 22 - 0
src/apps/app-modules/router.js

@@ -0,0 +1,22 @@
+export default {
+    path: '/',
+    component: () => import(/* webpackChunkName: "main" */ '@/apps/account/views/main.vue'),
+    children: [
+        {
+            name: 'module.add',
+            path: 'module/add',
+            component: () => import(/* webpackChunkName: "module" */ './views/module-edit.vue'),
+        },
+        {
+            name: 'module.edit',
+            path: 'module/edit/:id',
+            props: true,
+            component: () => import(/* webpackChunkName: "module" */ './views/module-edit.vue'),
+        },
+        {
+            name: 'module.manage',
+            path: 'module/manage',
+            component: () => import(/* webpackChunkName: "module" */ './views/module-manage.vue'),
+        },
+    ],
+};

+ 212 - 0
src/apps/app-modules/views/module-edit.vue

@@ -0,0 +1,212 @@
+<template>
+    <div>
+        <el-breadcrumb class="nav" separator="/">
+            <i class="el-icon-s-home" />&nbsp;
+            <el-breadcrumb-item :to="{ path: '/' }">
+                首页
+            </el-breadcrumb-item>
+            <el-breadcrumb-item>
+                系统管理
+            </el-breadcrumb-item>
+            <el-breadcrumb-item>
+                App模块管理
+            </el-breadcrumb-item>
+        </el-breadcrumb>
+        <el-card class="box-card">
+            <div slot="header" class="clearfix">
+                <el-page-header :content="title" @back="handleGoBack" />
+            </div>
+            <el-col :span="12">
+                <el-form ref="form" :model="form" :rules="rules" label-width="120px">
+                    <el-form-item label="名称" prop="name">
+                        <el-input v-model="form.name" maxlength="50" show-word-limit placeholder="请输入名称" />
+                    </el-form-item>
+                    <el-form-item label="标识(mode)" prop="mode">
+                        <el-input v-model="form.mode" maxlength="20" show-word-limit placeholder="请输入唯一标识" />
+                    </el-form-item>
+                    <el-form-item label="分组" prop="cateId">
+                        <el-select
+                            v-model="form.cateId"
+                            collapse-tags
+                            clearable
+                            placeholder="请选择"
+                        >
+                            <el-option
+                                v-for="item in moduleGroupOptions"
+                                :key="item.id"
+                                :label="item.name"
+                                :value="item.id"
+                            />
+                        </el-select>
+                    </el-form-item>
+                    <el-form-item label="图标" prop="icon" style="height: 40px;">
+                        <el-row :gutter="15" type="flex" align="center">
+                            <el-col v-if="form.icon" style="width: 50px;">
+                                <el-image class="icon-check" :src="form.icon" fit="cover" />
+                            </el-col>
+                            <el-col :span="5">
+                                <el-button @click="handleSelectIcons">
+                                    选择图标
+                                </el-button>
+                            </el-col>
+                        </el-row>
+                    </el-form-item>
+                    <el-form-item label="状态" prop="enable">
+                        <el-radio v-model="form.enable" label="0">
+                            禁用
+                        </el-radio>
+                        <el-radio v-model="form.enable" label="1">
+                            启用
+                        </el-radio>
+                    </el-form-item>
+                    <el-form-item label="链接" prop="url">
+                        <el-input v-model="form.url" placeholder="请输入链接" />
+                    </el-form-item>
+                    <el-form-item label="排序(正序排列)" prop="sort">
+                        <el-input v-model="form.sort" type="number" min="0" placeholder="排序,正序排列" />
+                    </el-form-item>
+                    <el-form-item label="描述" prop="content">
+                        <el-input
+                            v-model="form.content"
+                            :autosize="{ minRows: 5, maxRows: 8}"
+                            type="textarea"
+                            placeholder="请输入描述"
+                            maxlength="200"
+                            show-word-limit
+                        />
+                    </el-form-item>
+                    <el-form-item>
+                        <el-button type="primary" :loading="loading " @click="handleSubmit('form')">
+                            确定
+                        </el-button>
+                        <el-button @click="handleGoBack">
+                            返回
+                        </el-button>
+                    </el-form-item>
+                </el-form>
+            </el-col>
+        </el-card>
+        <icons-modal :dialog-visible.sync="dialogVisible" @getIconPath="getIconPath" />
+    </div>
+</template>
+
+<script>
+import Vue from 'vue';
+import Component from 'vue-class-component';
+import IconsModal from '@/common/components/icons-modal.vue';
+// import noImgIcon from '@/common/assets/icons/no-img.jpg';
+import moduleApi from '../api';
+
+@Component({
+    components: {
+        IconsModal,
+    },
+})
+export default class ModuleEdit extends Vue {
+    dialogVisible = false;
+
+    loading = false;
+
+    title = '新增App模块';
+
+    moduleGroupOptions = [];
+
+    form = {
+        name: '',
+        mode: '',
+        icon: '',
+        sort: 0,
+        content: '',
+        enable: '1',
+        url: '',
+        cateId: '',
+    };
+
+    rules = {
+        name: [
+            { required: true, message: '请输入名称', trigger: 'blur' },
+        ],
+        mode: [
+            { required: true, message: '请输入唯一标识', trigger: 'blur' },
+        ],
+        icon: [
+            { required: true, message: '请上传图标', trigger: 'blur' },
+        ],
+    };
+
+    mounted() {
+        this.getAppModuleDetail();
+        this.getModulesGroupList();
+    }
+
+    getIconPath(val) {
+        this.form.icon = val;
+    }
+
+    getModulesGroupList() {
+        moduleApi.getModuleCateList().then((res) => {
+            this.moduleGroupOptions = res.data;
+            this.moduleGroupOptions.push({
+                id: 0,
+                name: '其他',
+            });
+        }).catch(() => {});
+    }
+
+    getAppModuleDetail() {
+        const { id } = this.$route.params;
+        if (id > 0) {
+            this.title = '编辑App模块';
+            moduleApi.getModuleDetail(id).then((res) => {
+                this.form = { ...res.data };
+                // this.form.cateId = res.data.cateId === 0 ? '' : res.data.cateId;
+                this.form.enable = this.form.enable ? '1' : '0';
+            }).catch(() => {
+                this.$router.push('/module/manage');
+            });
+        }
+    }
+
+    handleGoBack() {
+        this.$router.push('/module/manage');
+    }
+
+    handleSubmit(formName) {
+        this.$refs[formName].validate((valid) => {
+            if (valid) {
+                this.loading = true;
+                if (this.form.id) {
+                    this.form.cateId = this.form.cateId === '' ? 0 : this.form.cateId;
+                    moduleApi.updateModuleInfo(this.form).then(() => {
+                        this.loading = false;
+                        this.$message.success('编辑成功!');
+                        this.$router.push('/module/manage');
+                    }).catch(() => {}).finally(() => {
+                        this.loading = false;
+                    });
+                } else {
+                    moduleApi.addModuleInfo(this.form).then(() => {
+                        this.loading = false;
+                        this.$message.success('保存成功!');
+                        this.$router.push('/module/manage');
+                    }).catch(() => {}).finally(() => {
+                        this.loading = false;
+                    });
+                }
+            } else {
+                this.loading = false;
+            }
+        });
+    }
+
+    handleSelectIcons() {
+        this.dialogVisible = true;
+    }
+}
+</script>
+<style lang="scss" scoped>
+.icon-check {
+    width: 40px;
+    height: 40px;
+}
+</style>

+ 104 - 0
src/apps/app-modules/views/module-manage.js

@@ -0,0 +1,104 @@
+import Vue from 'vue';
+import Component from 'vue-class-component';
+import noImgIcon from '@/common/assets/icons/no-img.jpg';
+import moduleApi from '../api';
+import ModuleGroupCreate from '../components/module-group-create.vue';
+
+@Component({
+    components: {
+        ModuleGroupCreate,
+    },
+})
+export default class ModuleManage extends Vue {
+    noIcon = noImgIcon;
+
+    modulesList = [];
+
+    flag = false;
+
+    loading = false;
+
+    dialogFormVisible = false;
+
+    groupForm = {
+        id: 0,
+        name: '',
+        enable: '1',
+    };
+
+    mounted() {
+        this.getModuleGroupList();
+    }
+
+    handleAddModuleGroup() {
+        this.groupForm = {
+            name: '',
+            enable: '1',
+        };
+        this.dialogFormVisible = true;
+    }
+
+    handleEditModuleGroup(id) {
+        this.dialogFormVisible = true;
+        this.getModuleGroupDetail(id);
+    }
+
+    handleAddModule() {
+        this.$router.push('/module/add');
+    }
+
+    handleEditModule(id) {
+        this.$router.push(`/module/edit/${id}`);
+    }
+
+    handleDisableOrEnableModule(appId, isEnable) {
+        const params = {
+            id: appId,
+            enable: isEnable,
+        };
+        moduleApi.disableOrEnableModule(params).then(() => {
+            const text = isEnable === 1 ? '该模块已启用!' : '该模块已禁用!';
+            this.$message({
+                type: 'success',
+                message: text,
+            });
+            this.getModuleGroupList();
+        });
+    }
+
+    handleDeleteModuleGroup(id) {
+        if (this.flag === false) {
+            this.$confirm('确定要删除此分组吗?', '提示', {
+                confirmButtonText: '确定',
+                cancelButtonText: '取消',
+                type: 'warning',
+            }).then(() => {
+                this.flag = true;
+                moduleApi.deleteModuleGroupInfo(id).then(() => {
+                    this.$message.success('删除成功!');
+                    this.getModuleGroupList();
+                    this.flag = false;
+                }).catch(() => {}).finally(() => {
+                    this.flag = false;
+                });
+            }).catch(() => {});
+        }
+    }
+
+    getModuleGroupList() {
+        this.loading = true;
+        moduleApi.getModuleGroupList().then((res) => {
+            this.modulesList = res.data;
+            this.loading = false;
+        }).catch(() => {}).finally(() => {
+            this.loading = false;
+        });
+    }
+
+    getModuleGroupDetail(id) {
+        moduleApi.getModuleCateDetail(id).then((res) => {
+            this.groupForm = { ...res.data };
+            this.groupForm.enable = this.groupForm.enable ? '1' : '0';
+        }).catch(() => {});
+    }
+}

+ 93 - 0
src/apps/app-modules/views/module-manage.vue

@@ -0,0 +1,93 @@
+<template>
+    <div>
+        <el-breadcrumb class="nav" separator="/">
+            <i class="el-icon-s-home" />&nbsp;
+            <el-breadcrumb-item :to="{ path: '/' }">
+                首页
+            </el-breadcrumb-item>
+            <el-breadcrumb-item>
+                系统设置
+            </el-breadcrumb-item>
+            <el-breadcrumb-item>
+                App模块管理
+            </el-breadcrumb-item>
+        </el-breadcrumb>
+        <el-card class="box-card">
+            <div slot="header" class="clearfix app-module-btn">
+                <el-row type="flex" justify="space-between">
+                    <el-button icon="el-icon-folder-add" @click="handleAddModuleGroup">
+                        创建分组
+                    </el-button>
+                    <el-button type="primary" icon="el-icon-plus" @click="handleAddModule">
+                        创建模块
+                    </el-button>
+                </el-row>
+            </div>
+            <el-alert
+                title="此功能留给开发使用,禁止修改"
+                type="warning"
+                :closable="false"
+                show-icon
+                style="margin-bottom:10px;"
+            />
+            <div class="app-module">
+                <div v-for="item in modulesList" :key="item.id" class="module-group">
+                    <el-row type="flex" justify="space-between" align="middle" class="module-group-nav">
+                        <div class="module-group-header">
+                            <span class="module-group-title">{{ item.name }}({{ item.appIconList.length }})</span>
+                        </div>
+                        <div v-if="item.name !== '其他'" class="module-group-btn">
+                            <el-button type="text" @click="handleEditModuleGroup(item.id)">
+                                编辑
+                            </el-button>
+                            <el-button type="text" @click="handleDeleteModuleGroup(item.id)">
+                                删除
+                            </el-button>
+                        </div>
+                    </el-row>
+                    <el-row
+                        v-for="appIconList in item.appIconList"
+                        :key="appIconList.id"
+                        type="flex"
+                        justify="space-between"
+                        align="middle"
+                        class="module-border"
+                    >
+                        <div class="module-group-header">
+                            <!-- <el-image class="module-icon" :src="appIconList.icon">
+                                <div slot="error" class="image-slot">
+                                    <img class="module-icon" :src="noIcon" alt="">
+                                </div>
+                            </el-image> -->
+                            <img class="module-icon" :src="appIconList.icon" alt="">
+                            <div class="module-group-spans">
+                                <span class="module-icon-title name">{{ appIconList.name }}</span>
+                                <span class="module-icon-title content">{{ appIconList.content }}</span>
+                                <span class="module-icon-title date-time">{{ appIconList.updateTime }} 更新</span>
+                            </div>
+                        </div>
+                        <div class="module-group-btn">
+                            <el-button type="text" @click="handleEditModule(appIconList.id)">
+                                编辑
+                            </el-button>
+                            <el-button v-if="appIconList.enable === 1" type="text" @click="handleDisableOrEnableModule(appIconList.id, 0)">
+                                禁用
+                            </el-button>
+                            <el-button v-else type="text" @click="handleDisableOrEnableModule(appIconList.id, 1)">
+                                启用
+                            </el-button>
+                        </div>
+                    </el-row>
+                </div>
+            </div>
+        </el-card>
+        <module-group-create
+            :dialog-form-visible.sync="dialogFormVisible"
+            :get-module-group-list="getModuleGroupList"
+            :group-form="groupForm"
+        />
+    </div>
+</template>
+<script src="./module-manage.js" />
+
+<style lang="scss" scoped src="@/common/assets/styles/module.scss" />

+ 35 - 0
src/apps/app-version/api.js

@@ -0,0 +1,35 @@
+/*
+ * @Author: duanyingkui
+ * @Date: 2019-12-27 09:55:39
+ * @Last Modified by: duanyingkui
+ * @Last Modified time: 2020-01-13 10:33:31
+ */
+import request from '@/common/utils/request';
+
+const APP_VERSION = '/server/appMgr';
+
+export default {
+    // App版本列表
+    getAppVersionList(parmas) {
+        return request.post(`${APP_VERSION}/list`, parmas);
+    },
+    // 发布版本
+    releaseAppVersion(parmas) {
+        return request.post(`${APP_VERSION}/save`, parmas);
+    },
+    // 编辑版本信息
+    editAppVersion(parmas) {
+        return request.post(`${APP_VERSION}/update`, parmas);
+    },
+    // 删除版本
+    deleteAppVersion(parmas) {
+        return request.post(`${APP_VERSION}/del`, { id: parmas });
+    },
+    // 启用禁用版本
+    enableOrDisableAppVersion(parmas) {
+        return request.post(`${APP_VERSION}/enableMgr`, parmas);
+    },
+    getAppVersionNoticeDetail(parmas) {
+        return request.post(`${APP_VERSION}/detail`, { id: parmas });
+    },
+};

+ 11 - 0
src/apps/app-version/router.js

@@ -0,0 +1,11 @@
+export default {
+    path: '/',
+    component: () => import(/* webpackChunkName: "main" */ '@/apps/account/views/main.vue'),
+    children: [
+        {
+            name: 'app.version',
+            path: 'app/version',
+            component: () => import(/* webpackChunkName: "app-version" */ './views/app-version.vue'),
+        },
+    ],
+};

+ 144 - 0
src/apps/app-version/views/app-version-create.vue

@@ -0,0 +1,144 @@
+<template>
+    <el-dialog
+        v-if="dialogVisible"
+        title="发布版本"
+        width="45%"
+        :visible.sync="dialogVisible"
+        :append-to-body="true"
+        :close-on-click-modal="false"
+        :close-on-press-escape="false"
+        :show-close="false"
+        center
+    >
+        <el-form ref="form" :model="form" :rules="rules" label-width="100px">
+            <el-form-item label="版本号" prop="ver">
+                <el-input v-model="form.ver" clearable placeholder="请输入版本号" />
+            </el-form-item>
+            <el-form-item label="App版本号" prop="appver">
+                <el-input v-model="form.appver" clearable placeholder="请输入App版本号,例如:1.0.0" />
+            </el-form-item>
+            <el-form-item label="更新内容" prop="content">
+                <el-input v-model="form.content" type="textarea" placeholder="请输入更新内容" :autosize="{ minRows: 4 }" />
+            </el-form-item>
+            <el-form-item label="类型" prop="type">
+                <el-radio v-model="form.type" label="1">
+                    Android
+                </el-radio>
+                <el-radio v-model="form.type" label="2">
+                    iOS
+                </el-radio>
+            </el-form-item>
+            <el-form-item v-if="form.type === 1 || form.type === '1'" label="Android apk" prop="path">
+                <el-upload
+                    class="upload-demo"
+                    name="uploadFile"
+                    :action="fileUrl"
+                    :data="updata"
+                    :on-success="handleImgSuccess"
+                    :on-remove="handleRemove"
+                    :before-upload="beforeUpload"
+                    :file-list="fileList"
+                >
+                    <el-button size="small" type="primary">
+                        点击上传
+                    </el-button>
+                </el-upload>
+            </el-form-item>
+        </el-form>
+        <span slot="footer" class="dialog-footer">
+            <el-button type="primary" :loading="loading" @click="handleReleaseAppVersion('form')">确 定</el-button>
+            <el-button @click="handleCloseDialog">取 消</el-button>
+        </span>
+    </el-dialog>
+</template>
+<script>
+import Vue from 'vue';
+import Component from 'vue-class-component';
+import appVersionApi from '../api';
+
+@Component({
+    props: {
+        dialogVisible: {
+            type: Boolean,
+            default: false,
+        },
+        getAppVersionListData: Function,
+    },
+    components: {
+    },
+})
+export default class AppVersionCreate extends Vue {
+    updata = {
+        orgId: 0,
+    };
+
+    loading = false;
+
+    dataLoading = false;
+
+    form = {
+        ver: null,
+        path: '',
+        content: '',
+        type: '1',
+    };
+
+    fileList = [];
+
+    rules = {
+        ver: [
+            { required: true, message: '请输入版本号', trigger: 'blur' },
+        ],
+        appver: [
+            { required: true, message: '请输入App版本号', trigger: 'blur' },
+        ],
+        content: [
+            { required: true, message: '请输入内容', trigger: 'blur' },
+        ],
+    };
+
+    fileUrl = '/phpapi/api/v1/upload/upfile'
+
+    handleReleaseAppVersion(formName) {
+        this.$refs[formName].validate((valid) => {
+            if (valid) {
+                this.loading = true;
+                appVersionApi.releaseAppVersion(this.form).then(() => {
+                    this.$message.success('发布成功!');
+                    this.$emit('update:dialogVisible', false);
+                    this.getAppVersionListData();
+                    this.loading = false;
+                }).catch(() => {}).finally(() => {
+                    this.loading = false;
+                });
+            } else {
+                this.loading = false;
+            }
+        });
+    }
+
+    handleCloseDialog() {
+        this.$emit('update:dialogVisible', false);
+    }
+
+    handleImgSuccess(res) {
+        this.form.path = res.name;
+    }
+
+    handleRemove() {
+        this.form.path = '';
+    }
+
+    beforeUpload(file) {
+        const FileExt = file.name.replace(/.+\./, '');
+        if (['apk'].indexOf(FileExt.toLowerCase()) === -1) {
+            this.$message({
+                type: 'warning',
+                message: '请上传后缀名为apk的文件!',
+            });
+            return false;
+        }
+        return true;
+    }
+}
+</script>

+ 110 - 0
src/apps/app-version/views/app-version.js

@@ -0,0 +1,110 @@
+import Vue from 'vue';
+import Component from 'vue-class-component';
+import appVersionApi from '../api';
+import AppVersionCreate from './app-version-create.vue';
+
+@Component({
+    components: {
+        AppVersionCreate,
+    },
+})
+export default class AppVersion extends Vue {
+    flag = false;
+
+    loading = true;
+
+    dialogVisible = false;
+
+    type = '';
+
+    options = [
+        { value: '1', label: 'Android' },
+        { value: '2', label: 'iOS' },
+    ];
+
+    appVersion = {
+        list: [],
+        page: 1,
+        size: 10,
+        total: 0,
+    };
+
+    mounted() {
+        this.getAppVersionListData();
+    }
+
+    handleDeleteAppVersion(id) {
+        if (this.flag === false) {
+            this.$confirm('确定要删除此版本吗?', '提示', {
+                confirmButtonText: '确定',
+                cancelButtonText: '取消',
+                type: 'warning',
+            }).then(() => {
+                this.flag = true;
+                appVersionApi.deleteAppVersion(id).then(() => {
+                    this.$message.success('删除成功!');
+                    this.flag = false;
+                    if (this.appVersion.list.length > 1) {
+                        this.getAppVersionListData();
+                    } else {
+                        this.appVersion.page = this.appVersion.page > 1 ? this.appVersion.page - 1 : 1;
+                        this.getAppVersionListData();
+                    }
+                }).catch(() => {
+                    this.flag = false;
+                });
+            }).catch(() => {});
+        }
+    }
+
+    handleSizeChange(val) {
+        this.appVersion.size = val;
+        this.getAppVersionListData();
+    }
+
+    handleCurrentChange(val) {
+        this.appVersion.page = val;
+        this.getAppVersionListData();
+    }
+
+    handleSearchAppVersion() {
+        this.getAppVersionListData();
+    }
+
+    handleEnableOrDisableAppVersion(appId, state) {
+        const parmas = {
+            id: appId,
+            enable: state ? 0 : 1,
+        };
+        appVersionApi.enableOrDisableAppVersion(parmas).then(() => {
+            const text = state === 0 ? '该版本已启用!' : '该版本已禁用!';
+            this.$message({
+                type: 'success',
+                message: text,
+            });
+            this.getAppVersionListData();
+        }).catch(() => {});
+    }
+
+    handleShowAppVersion() {
+        this.dialogVisible = true;
+    }
+
+    getAppVersionListData() {
+        this.loading = true;
+        const data = {
+            page: this.appVersion.page,
+            size: this.appVersion.size,
+        };
+        if (this.type !== '0' && this.type !== '') {
+            data.type = this.type;
+        }
+        appVersionApi.getAppVersionList(data).then((res) => {
+            this.appVersion.total = res.data.total;
+            this.appVersion.list = res.data.list;
+            this.loading = false;
+        }).catch(() => {}).finally(() => {
+            this.loading = false;
+        });
+    }
+}

+ 104 - 0
src/apps/app-version/views/app-version.vue

@@ -0,0 +1,104 @@
+<template>
+    <div>
+        <el-breadcrumb class="nav" separator="/">
+            <i class="el-icon-s-home" />&nbsp;
+            <el-breadcrumb-item :to="{ path: '/' }">
+                首页
+            </el-breadcrumb-item>
+            <el-breadcrumb-item>
+                系统设置
+            </el-breadcrumb-item>
+            <el-breadcrumb-item>
+                App版本管理
+            </el-breadcrumb-item>
+        </el-breadcrumb>
+        <el-card class="box-card">
+            <div slot="header" class="clearfix">
+                <el-row>
+                    <el-col :span="8">
+                        <el-button type="primary" size="small" icon="el-icon-edit-outline" @click="handleShowAppVersion">
+                            发布版本
+                        </el-button>
+                    </el-col>
+                    <el-col :span="16" style="text-align: right;">
+                        <div class="search-div">
+                            <el-select v-model="type" placeholder="请选择系统类型" size="small" clearable @change="handleSearchAppVersion">
+                                <el-option
+                                    v-for="item in options"
+                                    :key="item.value"
+                                    :label="item.label"
+                                    :value="item.value"
+                                />
+                            </el-select>
+                        </div>
+                    </el-col>
+                </el-row>
+            </div>
+            <el-alert
+                title="此功能留给开发使用,禁止修改"
+                type="warning"
+                :closable="false"
+                show-icon
+                style="margin-bottom:10px;"
+            />
+            <el-table v-loading="loading" :data="appVersion.list" stripe style="width: 100%">
+                <el-table-column prop="ver" label="版本号" width="70" />
+                <el-table-column prop="appver" label="App版本号" width="100" />
+                <el-table-column prop="content" label="更新内容" />
+                <el-table-column prop="type" label="类型">
+                    <template slot-scope="scope">
+                        <span v-if="scope.row.type === 1" type="text">Android</span>
+                        <span v-else type="text">iOS</span>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="enable" label="状态" width="100">
+                    <template slot-scope="scope">
+                        <span v-if="!scope.row.enable" type="text" class="status-disable">停用</span>
+                        <span v-else type="text">启用</span>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="createTime" label="发布时间" width="200" />
+                <el-table-column fixed="right" label="操作" width="250" align="center">
+                    <template slot-scope="scope">
+                        <el-link type="primary" :underline="false" @click="handleEnableOrDisableAppVersion(scope.row.id,scope.row.enable)">
+                            <span v-if="scope.row.enable" type="text" class="status-disable">停用</span>
+                            <span v-else type="text">启用</span>
+                        </el-link>
+                        <el-link type="primary" :underline="false" @click="handleDeleteAppVersion(scope.row.id)">
+                            删除
+                        </el-link>
+                    </template>
+                </el-table-column>
+            </el-table>
+
+            <div class="page">
+                <el-pagination
+                    class="page-position"
+                    background
+                    layout="prev, pager, next, sizes, total, jumper"
+                    :current-page="appVersion.page"
+                    :page-sizes="[10,15,25,50,100]"
+                    :page-size="appVersion.size"
+                    :total="appVersion.total"
+                    @size-change="handleSizeChange"
+                    @current-change="handleCurrentChange"
+                />
+            </div>
+        </el-card>
+        <app-version-create :dialog-visible.sync="dialogVisible" :get-app-version-list-data="getAppVersionListData" />
+    </div>
+</template>
+<script src="./app-version.js" />
+<style lang="scss" scoped>
+.search-div {
+    width: 220px !important;
+    .type-text {
+        float: right;
+        height: 20px;
+        line-height: 32px;
+    }
+}
+.status-disable {
+    color: #f56c6c;
+}
+</style>

+ 145 - 0
src/apps/approval/api.js

@@ -0,0 +1,145 @@
+import request from '@/common/utils/request';
+import request2 from '@/common/utils/request2';
+
+const APPROVALCATE = '/server/approvalCate/';
+const APPROVAL = '/server/approval/';
+const APPROVALTYPE = '/server/approvalType';
+const APPLY = '/server/apply/';
+const COMPANY = '/server/company/';
+
+export default {
+    cateList(parmas) {
+        return request.post(`${APPROVALCATE}list`, parmas);
+    },
+    cateInfo(id) {
+        return request.post(`${APPROVALCATE}detail`, { id });
+    },
+    cateAdd(parmas) {
+        return request.post(`${APPROVALCATE}save`, parmas);
+    },
+    cateEdit(parmas) {
+        return request.post(`${APPROVALCATE}update`, parmas);
+    },
+    cateDel(id) {
+        return request.post(`${APPROVALCATE}del`, { id });
+    },
+    cateGroup() {
+        return request.post(`${APPROVALCATE}approvalCateGroup`);
+    },
+    queryApprovalCateList() {
+        return request.post(`${APPROVAL}queryApprovalCateList`);
+    },
+    add(parmas) {
+        return request.post(`${APPROVAL}save`, parmas);
+    },
+    edit(parmas) {
+        return request.post(`${APPROVAL}update`, parmas);
+    },
+    info(id) {
+        return request.post(`${APPROVAL}detail`, { id });
+    },
+    getApprovalList() {
+        return request.post(`${APPROVAL}list`);
+    },
+    changeStatus(id, enable) {
+        return request.post(`${APPROVAL}changeStatus`, { id, enable });
+    },
+    applySave(params) { // 提交审批单
+        return request.post(`${APPLY}save`, params);
+    },
+    queryApplyList(params) { // 组织下的审批单
+        return request.post(`${APPLY}queryApplyList`, params);
+    },
+    applyList(params) { // 我发起的审批单
+        return request.post(`${APPLY}list`, params);
+    },
+    userApplyList(params) { // 我审批的审批单
+        return request.post(`${APPLY}userApplyList`, params);
+    },
+    applyInfo(id) { // 审批单详情
+        return request.post(`${APPLY}detail`, { id });
+    },
+    revokeApplyroval(applyId) { // 管理员撤销审批
+        return request.post(`${APPLY}updateStatusById`, { applyId });
+    },
+    backApply(param) { // 审批单打回上一审核节点
+        return request.post(`${APPLY}back`, param);
+    },
+    applyToBack(param) { // 审批单打回(拒绝)
+        return request.post(`${APPLY}toBack`, param);
+    },
+    applyAudit(param) { // 审批单同意
+        return request.post(`${APPLY}audit`, param);
+    },
+    applyDel(params) { // 删除审批单
+        return request.post(`${APPLY}del`, { id: params });
+    },
+    viewCopyApproval(parmas) {
+        return request.post('/server/applyRecord/updateStatusByUId', { id: parmas });
+    },
+    transferApply(param) { // 审批单转交
+        return request.post(`${APPLY}transferApply`, param);
+    },
+    addSign(param) { // 加签
+        return request.post(`${APPLY}addSign`, param);
+    },
+    aggregationApproval(param) { // 会签
+        return request.post(`${APPLY}aggregationApproval`, param);
+    },
+    undoApply(param) { // 审批人或发起人撤回
+        return request.post(`${APPLY}undo`, param);
+    },
+    untodoMenu() {
+        return request.post(`${APPLY}untodoMenu`);
+    },
+    remindAudit(param) { // 催办
+        return request.post('/server/message/remindAudit', param);
+    },
+    // 审批类别
+    approvalTypeList(params) {
+        return request.post(`${APPROVALTYPE}/list`, params);
+    },
+    approvalTypeAdd(params) {
+        return request.post(`${APPROVALTYPE}/save`, params);
+    },
+    approvalTypeUpdate(params) {
+        return request.post(`${APPROVALTYPE}/update`, params);
+    },
+    approvalTypeDelete(params) {
+        return request.post(`${APPROVALTYPE}/del`, { id: params });
+    },
+    approvalTypeDetail(params) {
+        return request.post(`${APPROVALTYPE}/detail`, { id: params });
+    },
+    selectApplyListByType(params) { // 关联审批单列表
+        return request.post(`${APPLY}selectApplyListByType`, params);
+    },
+    selectContractByType(params) { // 关联主合同审批单
+        return request.post(`${APPLY}selectContractByType`, params);
+    },
+    selectContractFinishByType(params) { // 关联未结算清合同审批单
+        return request.post(`${APPLY}selectContractFinishByType`, params);
+    },
+
+    getCompany() {
+        return request.post(`${COMPANY}list`);
+    },
+
+    // 获取自定义组件数据
+    getCustomSelect(parmas) {
+        return request.post(`${APPROVAL}customSelect`, parmas);
+    },
+
+    setApproval(parmas) {
+        return request2.post('/index/Approval/add', parmas);
+    },
+    getMaxDayApproval(parmas) {
+        return request2.post('/index/Approval/info', { id: parmas });
+    },
+    getPayMoney(parmas) {
+        return request2.post('/index/Contract/getmoney', { id: parmas });
+    },
+    checkRoomTime(parmas) {
+        return request2.post('/index/Room/checktime', parmas);
+    },
+};

+ 335 - 0
src/apps/approval/components/apply-content.vue

@@ -0,0 +1,335 @@
+<template>
+    <div>
+        <el-card v-loading="loading" class="box-card" style="padding-bottom:20px;">
+            <div class="apply-btns">
+                <el-button-group>
+                    <el-button type="info" size="small" @click="print">
+                        打印
+                    </el-button>
+                </el-button-group>
+            </div>
+            <div slot="header" class="clearfix">
+                {{ apply.title }}
+            </div>
+            <div ref="approval" class="approval" :class="approvalClass">
+                <div v-if="apply.depName" class="apply-box-title">
+                    部门: <span class="apply-box-value">{{ apply.depName }}</span>
+                </div>
+                <div class="apply-box-title">
+                    申请日期: <span class="apply-box-value">{{ apply.createTime }}</span>
+                </div>
+                <div class="apply-box-title">
+                    审批编号: <span class="apply-box-value">{{ apply.applySn }}</span>
+                </div>
+                <form-apply v-if="ajaxfinish" :data="formJson" />
+                <div v-if="apply.id && (apply.advanced.toString() === '3' || apply.advanced.toString() === '4')">
+                    <contract-pay-progress :apply="apply.id" />
+                </div>
+                <el-divider />
+                <flow-progress v-if="ajaxfinish" :data="flowJson" :visible="approvalVisible" :transfer="transfer" />
+                <div v-if="apply.status === 2||apply.status === 5" class="text-gray text-center" style="font-size:13px;">
+                    <i class="el-icon-s-data" style="color: #FFCC66;" /> 本申请共用时{{ apply.duration }}小时
+                </div>
+            </div>
+        </el-card>
+    </div>
+</template>
+
+<script>
+/* eslint-disable */
+import Vue from 'vue';
+import Component from 'vue-class-component';
+import FormApply from '@/apps/approval/components/form/form-apply.vue';
+import FlowProgress from '@/apps/approval/components/flow-progress.vue';
+import SelectUser from '@/common/components/select-user.vue';
+import ContractPayProgress from '@/apps/approval/components/contract-pay-progress.vue';
+import utils from '@/common/utils/utils';
+import applyApi from '@/apps/approval/api';
+
+@Component({
+    props: {
+        applyid: {
+            type: Number,
+            default() {
+                return 0;
+            },
+        },
+    },
+    components: {
+        FormApply,
+        FlowProgress,
+        SelectUser,
+        ContractPayProgress,
+    },
+    computed: {
+
+    },
+})
+export default class ApplyContent extends Vue {
+    loading = false;
+
+    apply = {}
+
+    formJson = []
+
+    oldFormJson = ''
+
+    formJsonLog = []
+
+    flowJson = []
+
+    flowJsonRecord = []
+
+    ajaxfinish = false
+
+    currentNodeUser = []
+
+    currentNode = {}
+
+    approvalClass = '';
+
+    approvalVisible = true; // 审批意见是否可见 true=可见 false=不可见
+
+    isAuth = true;
+
+    transfer = false;
+
+    created() {
+        this.getInfo();
+    }
+
+    print() {
+        this.$router.push(`/apply/print/${this.apply.id}`);
+    }
+
+    // 获取审批流程信息
+    getInfo() {
+        this.loading = true;
+        applyApi.applyInfo(this.applyid).then((res) => {
+            this.apply = res.data.apply;
+            if (this.apply.status === 2) {
+                this.approvalClass = 'approval-agree';
+            } else if (this.apply.status === 3) {
+                this.approvalClass = 'approval-cancel';
+            } else if (this.apply.status === 5) {
+                this.approvalClass = 'approval-disagree';
+            }
+
+           
+
+            // this.approvalVisible = this.apply.approvalVisible === 1;
+            this.formJson = utils.transFormJson(JSON.parse(res.data.apply.formJson), 1); // 转化表单数据
+            this.flowJson = JSON.parse(res.data.apply.flowJson);
+            this.formJsonLog = JSON.parse(JSON.stringify(res.data.apply.applyFormLogDtoList));
+            this.flowJsonRecord = JSON.parse(JSON.stringify(res.data.apply.applyRecordDtoList));
+
+
+            res.data.apply.applyRecordDtoList.forEach((item) => {
+                if (item.nodeid === this.apply.nodeid) {
+                    this.currentNodeUser.push(JSON.parse(JSON.stringify(item)));
+                }
+            });
+
+            // 格式化流程节点数据
+            this.formatFlowJson();
+
+
+            // 格式化表单数据
+            this.formatFormJson();
+
+            this.loading = false;
+            this.ajaxfinish = true;
+
+            console.log('ss', this.formJson, this.ajaxfinish);
+        }).catch(() => {
+            this.loading = false;
+        });
+    }
+
+    // 格式化流程节点数据
+    formatFlowJson() {
+        let startNodeId = '';
+        const flowJson = JSON.parse(JSON.stringify(this.flowJson));
+        this.flowJson = [];
+        flowJson.forEach((item) => {
+            if (item.type === 1) {
+                startNodeId = item.id;
+            }
+        });
+        this.getSortFlowJson(startNodeId, flowJson);
+        this.setStatusFlowJson();
+        // 0=未开始 1=进行中 2=已审核 3=已拒绝
+        this.flowJson.forEach((item) => {
+            const info = item;
+            const ulist = []; // 本节点流程记录
+
+            const aulist = [];
+            if (item.status == 0) {
+                if (info.userList) {
+                    info.userList.forEach((item2) => {
+                        aulist.push({
+                            userId: item2.userId,
+                            userName: item2.userName,
+                            shortName: item2.shortName ? item2.shortName : utils.getShortName(item2.userName),
+                            duration: 0,
+                            status: 0,
+                            transfer: 0,
+                            finishTime: null,
+                            id: 0,
+                            content: '',
+                        });
+                    });
+                }
+            } else { // 已生成记录的用记录的替代
+                this.flowJsonRecord.forEach((item2) => {
+                    if (info.id === item2.nodeid) {
+                        ulist.push(item2);
+                        aulist.push({
+                            userId: item2.userId,
+                            userName: item2.name,
+                            shortName: utils.getShortName(item2.name),
+                            duration: item2.duration,
+                            status: item2.status,
+                            transfer: item2.transfer,
+                            finishTime: item2.finishTime,
+                            id: item2.id,
+                            content: item2.content,
+                        });
+                    }
+                });
+            }
+
+            info.userList = aulist;
+
+            // 获取当前节点的审批人数据
+            if (info.id === this.apply.nodeid) {
+                this.currentNodeUser = aulist;
+                this.currentNode = info;
+            }
+        });
+    }
+
+    // 排序节点
+    getSortFlowJson(nodeId, flowJson) {
+        for (const o in flowJson) {
+            if (flowJson[o].id === nodeId) {
+                if (flowJson[o].type === 5) { // 流程结束节点
+                    this.flowJson.push(flowJson[o]);
+                    break;
+                }
+                this.flowJson.push(flowJson[o]);
+                this.getSortFlowJson(flowJson[o].nextId, flowJson);
+                break;
+            }
+        }
+    }
+
+    // 节点完成状态
+    setStatusFlowJson() {
+        this.flowJson.forEach((item) => {
+            const info = item;
+            info.status = 0; // 0=未开始 1=进行中 2=已审核 3=已拒绝
+        });
+        for (const o in this.flowJson) {
+            this.flowJson[o].status = 2;
+            if (this.flowJson[o].id === this.apply.nodeid && this.flowJson[o].type !== 5 && this.apply.status === 1) {
+                this.flowJson[o].status = 1;
+                break;
+            } else if (this.flowJson[o].id === this.apply.nodeid && this.flowJson[o].type !== 5 && (this.apply.status === 3 || this.apply.status === 4 || this.apply.status === 5)) {
+                this.flowJson[o].status = 3;
+                break;
+            }
+        }
+    }
+
+    // 格式化表单数据
+    formatFormJson() {
+        this.formJson.forEach((item) => {
+            const info = item;
+            info.auth = '3'; // 默认隐藏权限
+
+            if (this.btn === 0 && !this.isAuth && this.userNodeids.length > 0) { // 无有审批按钮切,权限查看
+                this.userNodeids.forEach((nodeid) => {
+                    const node = this.getNodeInfo(nodeid);
+                    if (JSON.stringify(node) !== '{}') {
+                        const nform = node.form;
+                        for (const m in nform) {
+                            if (nform[m].idx === info.idx) {
+                                info.auth = nform[m].auth !== '3' ? '2' : '3'; // 不是当前节点,不是隐藏就是显示
+                                break;
+                            }
+                        }
+                    }
+                });
+            }
+
+            if (this.isAuth && info.auth === '3') { // 可看全部
+                info.auth = '2';
+            }
+        });
+         
+        // this.formatFormJsonLog();
+        const oldform = JSON.parse(JSON.stringify(this.formJson));
+        // this.oldFormJson = JSON.stringify(this.deleteFormAuth(oldform));
+        console.log('ss', this.apply);
+    }
+
+    // 根据节点id获取节点信息
+    getNodeInfo(nodeid) {
+        let node = {};
+        for (const o in this.flowJson) {
+            if (nodeid === this.flowJson[o].id) {
+                node = this.flowJson[o];
+                break;
+            }
+        }
+        return node;
+    }
+}
+</script>
+
+<style lang="scss" src="../views/approval.scss"></style>
+<style scoped lang="scss">
+.approval .el-divider--horizontal {
+        margin: 14px 0;
+    }
+    .box-card{
+        position: relative;
+    }
+    // .apply-print{
+    //     position: absolute;
+    //     top: 10px;
+    //     right: 10px;
+    //     z-index: 10;
+    // }
+    .apply-btns{
+        position: absolute;
+        top: 14px;
+        right: 10px;
+        z-index: 10;
+        min-width: 50px;
+        overflow: hidden;
+    }
+    .fixed-approval {
+        position: fixed;
+        display: flex;
+    }
+    .approval-agree{
+        background: url('../../../common/assets/icons/tongguo.png') no-repeat;
+        background-position: right 10px;
+    }
+    .approval-disagree{
+        background: url('../../../common/assets/icons/jujue.png') no-repeat;
+        background-position: right 10px;
+    }
+    .approval-cancel{
+        background: url('../../../common/assets/icons/chexiao.png') no-repeat;
+        background-position: right 10px;
+    }
+    .executor-box{
+        width:100%;
+        overflow:hidden;
+        text-align: left;
+        margin-bottom: 10px;
+    }
+</style>

+ 289 - 0
src/apps/approval/components/apply-print/apply-print-app133.vue

@@ -0,0 +1,289 @@
+<template>
+    <div>
+        <div ref="approval" class="approval-print">
+            <div>
+                <h4 class="text-center">
+                    新华恒泰资产管理有限公司<br>
+                    办公设施设备(用品)采购申请表
+                </h4>
+                <table border="1" style="width:100%;" cellspacing="0" cellpadding="0">
+                    <tr>
+                        <td class="text-center">
+                            部门名称
+                        </td>
+                        <td class="text-center">
+                            {{ data.depName }}
+                        </td>
+                        <td class="text-center">
+                            申请时间
+                        </td>
+                        <td>
+                            {{ data.createTime }}
+                        </td>
+                        <td class="text-center">
+                            申请人
+                        </td>
+                        <td>
+                            {{ data.userName }}
+                        </td>
+                    </tr>
+                    <tr class="text-center">
+                        <td>
+                            序号
+                        </td>
+                        <td>
+                            名称
+                        </td>
+                        <td>
+                            规格
+                        </td>
+                        <td>
+                            单位
+                        </td>
+                        <td>
+                            数量
+                        </td>
+                        <td>
+                            备注
+                        </td>
+                    </tr>
+                    <tr v-for="(item,index) in tableList" :key="`${index}b`" class="text-center">
+                        <td>
+                            {{ index + 1 }}
+                        </td>
+                        <td v-for="(item2,index2) in item" :key="`${index2}c`">
+                            {{ item2.values }}
+                        </td>
+                    </tr>
+
+                    <template v-for="(flow,index) in flowJson">
+                        <tr v-if="flow.type === 2||flow.type === 8" :key="`${index}d`">
+                            <td class="text-center">
+                                {{ flow.name }}
+                            </td>
+                            <td colspan="5" class="td-nopadding">
+                                <template v-for="item in flow.users">
+                                    <div v-if="item.status == 1&&item.status == 2" :key="item.id" class="flow-box">
+                                        <div v-if="item.content">
+                                            "{{ item.content }}"
+                                        </div>
+                                        <div>
+                                            <span v-if="item.type === 2||item.type === 8">
+                                                {{ item.name }}
+                                                <span v-if="item.status === 0" class="text-gray">待审批</span>
+                                                <span v-if="item.status === 1" class="text-gray">已同意</span>
+                                                <span v-if="item.status === 2" class="text-gray">已拒绝</span>
+                                                <span v-if="item.status === 3" class="text-gray">已转交</span>
+                                                <span v-if="item.status === 4" class="text-gray">已流转</span>
+                                            </span>
+                                        </div>
+                                    </div>
+                                </template>
+                            </td>
+                        </tr>
+                    </template>
+                </table>
+            </div>
+        </div>
+        <p class="text-center">
+            <el-button icon="el-icon-printer" type="info" @click="print">
+                打印
+            </el-button>
+        </p>
+    </div>
+</template>
+
+<script>
+import Vue from 'vue';
+import Component from 'vue-class-component';
+import utils from '@/common/utils/utils';
+import Print from '@/common/utils/print';
+
+Vue.use(Print); // 注册
+
+// 表单打印
+@Component({
+    props: {
+        data: {
+            type: Object,
+            default() {
+                return {};
+            },
+        },
+        table: {
+            type: Array,
+            default() {
+                return [];
+            },
+        },
+        advform: {
+            type: Array,
+            default() {
+                return [];
+            },
+        },
+        flow: {
+            type: Array,
+            default() {
+                return [];
+            },
+        },
+    },
+    components: {
+
+    },
+    computed: {
+
+    },
+})
+export default class ApplyPrint20 extends Vue {
+    formJson = [];
+
+    jjcontractform = []
+
+    tableData = [];
+
+    flowJsonRecord = [];
+
+    items = [];
+
+    title = '';
+
+    company = '';
+
+    total = 0;
+
+    flowJson = [];
+
+    tableList = [];
+
+    created() {
+        this.jjcontractform = this.advform;
+        this.tableData = this.table;
+        this.flowJsonRecord = this.flow;
+
+
+        this.formJson = utils.transFormJson(JSON.parse(this.data.formJson), 1); // 转化表单数据
+
+        const flowJson = JSON.parse(this.data.flowJson);
+        let startNodeId = '';
+        flowJson.forEach((item) => {
+            if (item.type === 1) {
+                startNodeId = item.id;
+            }
+        });
+        this.getSortFlowJson(startNodeId, flowJson);
+
+        this.flowJson.forEach((item) => {
+            const info = item;
+            info.users = [];
+            console.log(item, this.flowJsonRecord);
+            this.flowJsonRecord.forEach((item2) => {
+                if (item.id === item2.nodeid) {
+                    console.log(1111);
+                    info.users.push(item2);
+                }
+            });
+        });
+
+        console.log(this.flowJson);
+
+        // this.jjcontractform.forEach((item) => {
+        //     if (item.idx === 0) {
+        //         const vals = JSON.parse(item.values);
+        //         this.title = vals.title;
+        //         this.company = vals.company;
+        //         this.items = vals.items;
+        //     }
+        // });
+
+        // this.items.forEach((item) => {
+        //     this.total += Number(item.money);
+        // });
+
+        this.tableList = [];
+        this.formJson.forEach((item) => {
+            if (item.componentName === 'tablefield') {
+                this.tableList = item.values;
+                console.log('sssssss', this.tableList);
+            }
+        });
+    }
+
+    print() {
+        this.$print(this.$refs.approval);
+    }
+
+    // 排序节点
+    getSortFlowJson(nodeId, flowJson) { // eslint-disable-line
+        for (const o in flowJson) { // eslint-disable-line
+            if (flowJson[o].id === nodeId) {
+                if (flowJson[o].type === 5) { // 流程结束节点
+                    this.flowJson.push(flowJson[o]);
+                    break;
+                }
+                this.flowJson.push(flowJson[o]);
+                this.getSortFlowJson(flowJson[o].nextId, flowJson);
+                break;
+            }
+        }
+    }
+}
+</script>
+
+<style media="print">
+    @page {
+        size: auto;
+        margin: 0px 50px;
+    }
+</style>
+<style scoped lang="scss">
+.approval-print{
+    padding-top: 30px;
+    max-width: 1000px;
+    margin: 0 auto;
+}
+.flow-box{
+    padding: 2px 5px;
+    border-bottom: 2px solid #303133;
+}
+.flow-box:last-child{
+    border-bottom: 0;
+}
+td{
+    padding: 2px 5px;
+}
+.td-nopadding{
+    padding: 0;
+}
+
+// table,tr,td{
+//     border: 1px solid #909399;
+// }
+table.no-border{
+    border:0;
+}
+.td-title{
+    width: 100px;
+}
+.sq-title{
+    font-size: 18;
+    font-weight: bold;
+    padding: 10px 0;
+}
+.sq-content{
+    text-indent: 40px;
+    font-size: 16px;
+    padding: 10px 0;
+    padding-bottom: 30px;
+}
+.sq-footer{
+    padding: 10px 0;
+    padding-bottom: 30px;
+    text-align: right;
+}
+td{
+    padding:5px;
+    width: 166px;
+}
+</style>

+ 287 - 0
src/apps/approval/components/apply-print/apply-print-app134.vue

@@ -0,0 +1,287 @@
+<template>
+    <div>
+        <div ref="approval" class="approval-print">
+            <div>
+                <h4 class="text-center">
+                    新华恒泰资产管理有限公司<br>
+                    办公设施设备(用品)采购计划表
+                </h4>
+                <table border="1" style="width:100%;" cellspacing="0" cellpadding="0">
+                    <tr class="text-center">
+                        <td>
+                            序号
+                        </td>
+                        <td>
+                            名称
+                        </td>
+                        <td>
+                            规格
+                        </td>
+                        <td>
+                            单位
+                        </td>
+                        <td>
+                            数量
+                        </td>
+                        <td>
+                            金额
+                        </td>
+                        <td>
+                            备注
+                        </td>
+                    </tr>
+                    <tr v-for="(item,index) in tableList" :key="`${index}b`" class="text-center">
+                        <td>
+                            {{ index + 1 }}
+                        </td>
+                        <td v-for="(item2,index2) in item" :key="`${index2}c`">
+                            {{ item2.values }}
+                        </td>
+                    </tr>
+                    <tr>
+                        <td colspan="2" class="text-center">
+                            经办人
+                        </td>
+                        <td colspan="5" class="td-nopadding">
+                            <div class="flow-box">
+                                <div>
+                                    <span>
+                                        {{ data.userName }}
+                                    </span>
+                                </div>
+                            </div>
+                        </td>
+                    </tr>
+                    <template v-for="(flow,index) in flowJson">
+                        <tr v-if="flow.type === 2||flow.type === 8" :key="`${index}d`">
+                            <td colspan="2" class="text-center">
+                                {{ flow.name }}
+                            </td>
+                            <td colspan="5" class="td-nopadding">
+                                <template v-for="item in flow.users">
+                                    <div v-if="item.status == 1&&item.status == 2" :key="item.id" class="flow-box">
+                                        <div v-if="item.content">
+                                            "{{ item.content }}"
+                                        </div>
+                                        <div>
+                                            <span v-if="item.type === 2||item.type === 8">
+                                                {{ item.name }}
+                                                <span v-if="item.status === 0" class="text-gray">待审批</span>
+                                                <span v-if="item.status === 1" class="text-gray">已同意</span>
+                                                <span v-if="item.status === 2" class="text-gray">已拒绝</span>
+                                                <span v-if="item.status === 3" class="text-gray">已转交</span>
+                                                <span v-if="item.status === 4" class="text-gray">已流转</span>
+                                            </span>
+                                            <span v-if="item.type === 2||item.type === 8" class="pull-right">{{ item.finishTime }}</span>
+                                            <span v-if="item.type === 3||item.type === 9" class="pull-right">{{ item.createTime }}</span>
+                                        </div>
+                                    </div>
+                                </template>
+                            </td>
+                        </tr>
+                    </template>
+                </table>
+            </div>
+        </div>
+        <p class="text-center">
+            <el-button icon="el-icon-printer" type="info" @click="print">
+                打印
+            </el-button>
+        </p>
+    </div>
+</template>
+
+<script>
+import Vue from 'vue';
+import Component from 'vue-class-component';
+import utils from '@/common/utils/utils';
+import Print from '@/common/utils/print';
+
+Vue.use(Print); // 注册
+
+// 表单打印
+@Component({
+    props: {
+        data: {
+            type: Object,
+            default() {
+                return {};
+            },
+        },
+        table: {
+            type: Array,
+            default() {
+                return [];
+            },
+        },
+        advform: {
+            type: Array,
+            default() {
+                return [];
+            },
+        },
+        flow: {
+            type: Array,
+            default() {
+                return [];
+            },
+        },
+    },
+    components: {
+
+    },
+    computed: {
+
+    },
+})
+export default class ApplyPrint20 extends Vue {
+    formJson = [];
+
+    jjcontractform = []
+
+    tableData = [];
+
+    flowJsonRecord = [];
+
+    items = [];
+
+    title = '';
+
+    company = '';
+
+    total = 0;
+
+    flowJson = [];
+
+    tableList = [];
+
+    created() {
+        this.jjcontractform = this.advform;
+        this.tableData = this.table;
+        this.flowJsonRecord = this.flow;
+
+
+        this.formJson = utils.transFormJson(JSON.parse(this.data.formJson), 1); // 转化表单数据
+
+        const flowJson = JSON.parse(this.data.flowJson);
+        let startNodeId = '';
+        flowJson.forEach((item) => {
+            if (item.type === 1) {
+                startNodeId = item.id;
+            }
+        });
+        this.getSortFlowJson(startNodeId, flowJson);
+
+        this.flowJson.forEach((item) => {
+            const info = item;
+            info.users = [];
+            console.log(item, this.flowJsonRecord);
+            this.flowJsonRecord.forEach((item2) => {
+                if (item.id === item2.nodeid) {
+                    console.log(1111);
+                    info.users.push(item2);
+                }
+            });
+        });
+
+        console.log(this.flowJson);
+
+        // this.jjcontractform.forEach((item) => {
+        //     if (item.idx === 0) {
+        //         const vals = JSON.parse(item.values);
+        //         this.title = vals.title;
+        //         this.company = vals.company;
+        //         this.items = vals.items;
+        //     }
+        // });
+
+        // this.items.forEach((item) => {
+        //     this.total += Number(item.money);
+        // });
+
+        this.tableList = [];
+        this.formJson.forEach((item) => {
+            if (item.componentName === 'tablefield') {
+                this.tableList = item.values;
+                console.log('sssssss', this.tableList);
+            }
+        });
+    }
+
+    print() {
+        this.$print(this.$refs.approval);
+    }
+
+    // 排序节点
+    getSortFlowJson(nodeId, flowJson) { // eslint-disable-line
+        for (const o in flowJson) { // eslint-disable-line
+            if (flowJson[o].id === nodeId) {
+                if (flowJson[o].type === 5) { // 流程结束节点
+                    this.flowJson.push(flowJson[o]);
+                    break;
+                }
+                this.flowJson.push(flowJson[o]);
+                this.getSortFlowJson(flowJson[o].nextId, flowJson);
+                break;
+            }
+        }
+    }
+}
+</script>
+
+<style media="print">
+    @page {
+        size: auto;
+        margin: 0px 50px;
+    }
+</style>
+<style scoped lang="scss">
+.approval-print{
+    padding-top: 30px;
+    max-width: 1000px;
+    margin: 0 auto;
+}
+.flow-box{
+    padding: 2px 5px;
+    border-bottom: 2px solid #303133;
+}
+.flow-box:last-child{
+    border-bottom: 0;
+}
+td{
+    padding: 2px 5px;
+}
+.td-nopadding{
+    padding: 0;
+}
+
+// table,tr,td{
+//     border: 1px solid #909399;
+// }
+table.no-border{
+    border:0;
+}
+.td-title{
+    width: 100px;
+}
+.sq-title{
+    font-size: 18;
+    font-weight: bold;
+    padding: 10px 0;
+}
+.sq-content{
+    text-indent: 40px;
+    font-size: 16px;
+    padding: 10px 0;
+    padding-bottom: 30px;
+}
+.sq-footer{
+    padding: 10px 0;
+    padding-bottom: 30px;
+    text-align: right;
+}
+td{
+    padding:5px;
+    width: 166px;
+}
+</style>

+ 277 - 0
src/apps/approval/components/apply-print/apply-print-app151.vue

@@ -0,0 +1,277 @@
+<template>
+    <div>
+        <div class="report-div">
+            <div ref="approval">
+                <p><br></p>
+                <div class="report-title">
+                    资产领用单
+                </div>
+                <table style="width:100%;">
+                    <tr>
+                        <td class="text-right">
+                            单据编号:
+                        </td>
+                        <td>{{data.applySn}}</td>
+                        <td class="text-right">
+                            登记时间:
+                        </td>
+                        <td>{{data.createTime}}</td>
+                    </tr>
+                    <tr>
+                        <td class="text-right">
+                            领用部门:
+                        </td>
+                        <td>{{data.depName}}</td>
+                        <td class="text-right">
+                            领用人员:
+                        </td>
+                        <td>{{data.userName}}</td>
+                    </tr>
+                </table>
+
+                <table
+                    class="custom-table"
+                    border="1"
+                    style="width:100%;"
+                    cellspacing="0"
+                    cellpadding="0"
+                >
+                    <tr>
+                        <th>序号</th>
+                        <th>资产编号</th>
+                        <th>资产名称</th>
+                        <th>规格型号</th>
+                        <th>单位</th>
+                        <th>数量</th>
+                        <th>原值</th>
+                        <th>购置日期</th>
+                    </tr>
+                    <tr v-for="(item,index) in zclist" :key="index">
+                        <td>{{ index + 1 }}</td>
+                        <td>{{ item.sn }}</td>
+                        <td>{{ item.title }}</td>
+                        <td>{{ item.spec }}</td>
+                        <td>{{ item.unitName }}</td>
+                        <td>{{ item.nums }}</td>
+                        <td>{{ item.price }}</td>
+                        <td>{{ item.buyTime }}</td>
+                    </tr>
+                    <tr>
+                        <td colspan="8">
+                            数量合计: {{ zccount }}  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;原值合计: {{ zcprice }}
+                        </td>
+                    </tr>
+                    <tr>
+                        <td colspan="2" rowspan="3">
+                            领用备注
+                        </td>
+                        <td colspan="6" rowspan="3">
+                            <div class="report-remark">
+                                {{ zcremark }}
+                            </div>
+                        </td>
+                    </tr>
+                </table>
+
+                <table style="width:100%;">
+                    <tr>
+                        <td class="text-center">
+                            使用人:{{data.userName}}
+                        </td>
+                        <td class="text-center">
+                            办公室:{{bguser}}
+                        </td>
+                        <td class="text-center">
+                            资产管理处:{{tzuser}}
+                        </td>
+                    </tr>
+                </table>
+                
+            </div>
+        </div>
+        <p class="text-center">
+            <el-button icon="el-icon-printer" type="info" @click="print">
+                打印
+            </el-button>
+        </p>
+    </div>
+</template>
+
+<script>
+import Vue from 'vue';
+import Component from 'vue-class-component';
+import utils from '@/common/utils/utils';
+import Print from '@/common/utils/print';
+
+Vue.use(Print); // 注册
+
+// 表单打印
+@Component({
+    props: {
+        data: {
+            type: Object,
+            default() {
+                return {};
+            },
+        },
+        table: {
+            type: Array,
+            default() {
+                return [];
+            },
+        },
+        advform: {
+            type: Array,
+            default() {
+                return [];
+            },
+        },
+        flow: {
+            type: Array,
+            default() {
+                return [];
+            },
+        },
+    },
+    components: {
+
+    },
+    computed: {
+
+    },
+})
+export default class ApplyPrint20 extends Vue {
+    formJson = [];
+
+    zclist = [];
+
+    zcremark = '';
+
+    zccount = 0;
+    
+    zcprice = 0;
+
+    bguser = ''; // 办公室
+
+    tzuser = ''; // 资产管理处
+
+    created() {
+
+        this.formJson = utils.transFormJson(JSON.parse(this.data.formJson), 1); // 转化表单数据
+
+        console.log('this.formJson',this.formJson);
+
+        this.flow.forEach((item) => {
+            if(item.nodeid === 'node2' && item.status == 1){
+                this.bguser = item.name;
+            } else if(item.nodeid === 'node3' && item.status == 1){
+                this.tzuser = item.name;
+            }
+        });
+
+        this.formJson.forEach((item) => {
+            if(item.idx === 0){
+                let zzlist = item.values?item.values:[];
+                zzlist.forEach((item2) => {
+                    console.log('this.formJson3',item2);
+                    let zcinfo = {
+                        sn: '',
+                        title: '',
+                        spec: '',
+                        unitName: '',
+                        nums: 0,
+                        price: 0,
+                        buyTime: '',
+                    }
+                    item2.forEach((item3) => {
+                        if(item3.idx === 1){
+                            zcinfo.sn = item3.values?item3.values:'';
+                        }else if(item3.idx === 2){
+                            zcinfo.title = item3.values?item3.values:'';
+                        }else if(item3.idx === 3){
+                            zcinfo.spec = item3.values?item3.values:'';
+                        }else if(item3.idx === 4){
+                            zcinfo.unitName = item3.values?item3.values:'';
+                        }else if(item3.idx === 9){
+                            zcinfo.nums = item3.values?Number(item3.values):0;
+                        }else if(item3.idx === 6){
+                            zcinfo.price = item3.values?Number(item3.values):0;
+                        }else if(item3.idx === 7){
+                            zcinfo.buyTime = item3.values?item3.values:'';
+                        }
+                    });
+                    this.zccount += zcinfo.nums;
+                    this.zcprice += zcinfo.nums*zcinfo.price;
+                    this.zclist.push(zcinfo);
+                });
+                this.zcprice = this.zcprice.toFixed(2);
+            }else if(item.idx === 8){
+                this.zcremark = item.values?item.values:'';
+            }
+        });
+    }
+
+    print() {
+        this.$print(this.$refs.approval);
+    }
+
+    // 排序节点
+    getSortFlowJson(nodeId, flowJson) { // eslint-disable-line
+        for (const o in flowJson) { // eslint-disable-line
+            if (flowJson[o].id === nodeId) {
+                if (flowJson[o].type === 5) { // 流程结束节点
+                    this.flowJson.push(flowJson[o]);
+                    break;
+                }
+                this.flowJson.push(flowJson[o]);
+                this.getSortFlowJson(flowJson[o].nextId, flowJson);
+                break;
+            }
+        }
+    }
+}
+</script>
+
+<style media="print">
+    @page {
+        size: auto;
+        margin: 0px 50px;
+    }
+</style>
+<style scoped lang="scss">
+.report-div{
+    padding: 40px;
+    padding-top: 10px;
+    max-width: 1000px;
+    margin: 0 auto;
+}
+.report-title{
+    font-size: 18px;
+    font-weight: bold;
+    border-bottom: 1px solid #222;
+    padding: 10px;
+    text-align: center;
+    margin-bottom: 15px;
+}
+.custom-table{
+    margin: 15px 0 40px 0;
+}
+.custom-table,.custom-table tr,.custom-table th,.custom-table td{
+    border-color: #DCDFE6;
+    text-align: center;
+}
+.custom-table th,.custom-table td{
+    padding: 5px;
+}
+.report-remark{
+    height: 120px;
+    text-align: left;
+}
+.custom-table2{
+    width: 80%;
+    margin: 0 auto;
+}
+.custom-table2 td{
+    width: 25%;
+}
+</style>

+ 276 - 0
src/apps/approval/components/apply-print/apply-print-app152.vue

@@ -0,0 +1,276 @@
+<template>
+    <div>
+        <div class="report-div">
+            <div ref="approval">
+                <p><br></p>
+                <div class="report-title">
+                    资产退库单
+                </div>
+                <table style="width:100%;">
+                    <tr>
+                        <td class="text-right">
+                            单据编号:
+                        </td>
+                        <td>{{data.applySn}}</td>
+                        <td class="text-right">
+                            登记时间:
+                        </td>
+                        <td>{{data.createTime}}</td>
+                    </tr>
+                    <tr>
+                        <td class="text-right">
+                            经办部门:
+                        </td>
+                        <td> 资产管理处 </td>
+                        <td class="text-right">
+                            经办人员:
+                        </td>
+                        <td>张光利</td>
+                    </tr>
+                </table>
+
+                <table
+                    class="custom-table"
+                    border="1"
+                    style="width:100%;"
+                    cellspacing="0"
+                    cellpadding="0"
+                >
+                    <tr>
+                        <th>序号</th>
+                        <th>资产编号</th>
+                        <th>资产名称</th>
+                        <th>规格型号</th>
+                        <th>单位</th>
+                        <th>原值</th>
+                        <th>退库前使用人员</th>
+                        <th>退库前使用部门</th>
+                    </tr>
+                    <tr v-for="(item,index) in zclist" :key="index">
+                        <td>{{ index + 1 }}</td>
+                        <td>{{ item.sn }}</td>
+                        <td>{{ item.title }}</td>
+                        <td>{{ item.spec }}</td>
+                        <td>{{ item.unitName }}</td>
+                        <td>{{ item.price }}</td>
+                        <td>{{ item.userName }}</td>
+                        <td>{{ item.depName }}</td>
+                    </tr>
+                    <tr>
+                        <td colspan="8">
+                            原值合计: {{ zcprice }}
+                        </td>
+                    </tr>
+                    <tr>
+                        <td colspan="2" rowspan="3">
+                            退库备注
+                        </td>
+                        <td colspan="6" rowspan="3">
+                            <div class="report-remark">
+                                {{ zcremark }}
+                            </div>
+                        </td>
+                    </tr>
+                </table>
+
+                <table style="width:100%;">
+                    <tr>
+                        <td class="text-center">
+                            退库人:{{data.userName}}
+                        </td>
+                        <td class="text-center">
+                            办公室:{{bguser}}
+                        </td>
+                        <td class="text-center">
+                            资产管理处:{{tzuser}}
+                        </td>
+                    </tr>
+                </table>
+                
+            </div>
+        </div>
+        <p class="text-center">
+            <el-button icon="el-icon-printer" type="info" @click="print">
+                打印
+            </el-button>
+        </p>
+    </div>
+</template>
+
+<script>
+import Vue from 'vue';
+import Component from 'vue-class-component';
+import utils from '@/common/utils/utils';
+import Print from '@/common/utils/print';
+
+Vue.use(Print); // 注册
+
+// 表单打印
+@Component({
+    props: {
+        data: {
+            type: Object,
+            default() {
+                return {};
+            },
+        },
+        table: {
+            type: Array,
+            default() {
+                return [];
+            },
+        },
+        advform: {
+            type: Array,
+            default() {
+                return [];
+            },
+        },
+        flow: {
+            type: Array,
+            default() {
+                return [];
+            },
+        },
+    },
+    components: {
+
+    },
+    computed: {
+
+    },
+})
+export default class ApplyPrint20 extends Vue {
+    formJson = [];
+
+    zclist = [];
+
+    zcremark = '';
+
+    zccount = 0;
+    
+    zcprice = 0;
+
+    bguser = ''; // 办公室
+
+    tzuser = ''; // 资产管理处
+
+    created() {
+
+        this.formJson = utils.transFormJson(JSON.parse(this.data.formJson), 1); // 转化表单数据
+
+        console.log('this.formJson',this.formJson);
+
+        this.flow.forEach((item) => {
+            if(item.nodeid === 'node2' && item.status == 1){
+                this.bguser = item.name;
+            } else if(item.nodeid === 'node3' && item.status == 1){
+                this.tzuser = item.name;
+            }
+        });
+
+        this.formJson.forEach((item) => {
+            if(item.idx === 0){
+                let zzlist = item.values?item.values:[];
+                zzlist.forEach((item2) => {
+                    console.log('this.formJson3',item2);
+                    let zcinfo = {
+                        sn: '',
+                        title: '',
+                        spec: '',
+                        unitName: '',
+                        userName: 0,
+                        price: 0,
+                        depName: '',
+                    }
+                    item2.forEach((item3) => {
+                        if(item3.idx === 1){
+                            zcinfo.sn = item3.values?item3.values:'';
+                        }else if(item3.idx === 2){
+                            zcinfo.title = item3.values?item3.values:'';
+                        }else if(item3.idx === 3){
+                            zcinfo.spec = item3.values?item3.values:'';
+                        }else if(item3.idx === 4){
+                            zcinfo.unitName = item3.values?item3.values:'';
+                        }else if(item3.idx === 5){
+                            zcinfo.price = item3.values?Number(item3.values):0;
+                        }else if(item3.idx === 6){
+                            zcinfo.userName = item3.values?item3.values:'';
+                        }else if(item3.idx === 7){
+                            zcinfo.depName = item3.values?item3.values:'';
+                        }
+                    });
+                    this.zcprice += zcinfo.price;
+                    this.zclist.push(zcinfo);
+                });
+                this.zcprice = this.zcprice.toFixed(2);
+            }else if(item.idx === 8){
+                this.zcremark = item.values?item.values:'';
+            }
+        });
+    }
+
+    print() {
+        this.$print(this.$refs.approval);
+    }
+
+    // 排序节点
+    getSortFlowJson(nodeId, flowJson) { // eslint-disable-line
+        for (const o in flowJson) { // eslint-disable-line
+            if (flowJson[o].id === nodeId) {
+                if (flowJson[o].type === 5) { // 流程结束节点
+                    this.flowJson.push(flowJson[o]);
+                    break;
+                }
+                this.flowJson.push(flowJson[o]);
+                this.getSortFlowJson(flowJson[o].nextId, flowJson);
+                break;
+            }
+        }
+    }
+}
+</script>
+
+<style media="print">
+    @page {
+        size: auto;
+        margin: 0px 50px;
+    }
+</style>
+<style scoped lang="scss">
+.report-div{
+    padding: 40px;
+    padding-top: 10px;
+    max-width: 1000px;
+    margin: 0 auto;
+}
+.report-title{
+    font-size: 18px;
+    font-weight: bold;
+    border-bottom: 1px solid #222;
+    padding: 10px;
+    text-align: center;
+    margin-bottom: 15px;
+}
+.custom-table{
+    margin: 15px 0 40px 0;
+}
+.custom-table,.custom-table tr,.custom-table th,.custom-table td{
+    border-color: #DCDFE6;
+    text-align: center;
+}
+.custom-table th,.custom-table td{
+    padding: 5px;
+}
+.report-remark{
+    height: 120px;
+    text-align: left;
+}
+.custom-table2{
+    width: 80%;
+    margin: 0 auto;
+}
+.custom-table2 td{
+    width: 25%;
+}
+</style>

+ 271 - 0
src/apps/approval/components/apply-print/apply-print10.vue

@@ -0,0 +1,271 @@
+<template>
+    <div>
+        <div ref="approval" class="approval-print">
+            <div>
+                <h4 class="text-center">
+                    {{ data.approvalName }}
+                </h4>
+                <table style="width:100%;">
+                    <tr>
+                        <td>部门:{{ data.depName }}</td>
+                        <td class="text-right">
+                            日期:{{ data.createTime }}
+                        </td>
+                    </tr>
+                </table>
+                <table border="1" style="width:100%;" cellspacing="0" cellpadding="0">
+                    <tr>
+                        <td>
+                            类型
+                        </td>
+                        <td>
+                            名称/规格
+                        </td>
+                        <td style="width:120px;">
+                            单价(元)
+                        </td>
+                        <td style="width:120px;">
+                            数量
+                        </td>
+                    </tr>
+
+                    <tr v-for="item in items" :key="item.id">
+                        <td>
+                            {{ item.cateName }}
+                        </td>
+                        <td>
+                            {{ item.title }}/{{ item.spec }}
+                        </td>
+                        <td>
+                            {{ item.price }}
+                        </td>
+                        <td>
+                            {{ item.snums }}{{ item.unitName }}
+                        </td>
+                    </tr>
+                    <tr>
+                        <td colspan="3" class="text-right">
+                            总金额(元)
+                        </td>
+                        <td style="width: 100px;">
+                            {{ total }}
+                        </td>
+                    </tr>
+
+                    <tr v-for="(item,index) in tableData" :key="index">
+                        <template v-if="item.type === 1">
+                            <td style="width:120px;">
+                                {{ item.name }}
+                            </td>
+                            <td colspan="3">
+                                {{ item.value }}
+                            </td>
+                        </template>
+                        <template v-if="item.type === 4">
+                            <td v-if="item.value.tables.length > 0" colspan="4" class="td-nopadding">
+                                <table border="1" style="width:100%;" cellspacing="0" cellpadding="0">
+                                    <tr>
+                                        <td style="width: 118px;">
+                                            {{ item.name }}
+                                        </td>
+                                        <td v-for="(item1,index1) in item.value.tables[0]" :key="`${index1}a`">
+                                            {{ item1.defaultLable }}
+                                        </td>
+                                    </tr>
+                                    <tr v-for="(item2,index2) in item.value.tables" :key="`${index2}b`">
+                                        <td>{{ item.name }}{{ index2 + 1 }}</td>
+                                        <td v-for="item3 in item2" :key="`${item3.idx}c${index2}`">
+                                            {{ item3.values }}
+                                        </td>
+                                    </tr>
+                                    <tr v-if="item.value.total.length > 0&&item.value.tables.length > 0">
+                                        <td>
+                                            合计
+                                        </td>
+                                        <td :colspan="item.value.tables[0].length">
+                                            <div v-for="(val,vindex) in item.value.total" :key="vindex">
+                                                <span>总{{ val.defaultLable }}: </span>
+                                                <span>{{ val.values }} <span v-if="val.valuesTranslate">({{ val.valuesTranslate }})</span></span>
+                                            </div>
+                                        </td>
+                                    </tr>
+                                </table>
+                            </td>
+                        </template>
+                        <template v-if="item.type === 5">
+                            <td v-if="item.value.tables.length > 0" colspan="4" class="td-nopadding">
+                                <table border="1" style="width:100%;" cellspacing="0" cellpadding="0">
+                                    <tr :rowspan="item.value.tables.length">
+                                        <td style="width: 118px;">
+                                            {{ item.name }}
+                                        </td>
+                                        <td style="padding: 0;">
+                                            <table border="1" style="width:100%;" cellspacing="0" cellpadding="0">
+                                                <tr v-for="citem in item.value.tables" :key="`${citem.idx}_${item.name}`">
+                                                    <td style="width: 118px;">
+                                                        {{ citem.defaultLable }}
+                                                    </td>
+                                                    <td>
+                                                        {{ citem.values }}
+                                                    </td>
+                                                </tr>
+                                            </table>
+                                        </td>
+                                    </tr>
+                                </table>
+                            </td>
+                        </template>
+                    </tr>
+
+                    <tr>
+                        <td style="width:120px;">
+                            审批流程
+                        </td>
+                        <td colspan="3" class="td-nopadding">
+                            <template v-for="item in flowJsonRecord">
+                                <div v-if="item.status != 3&&item.status != 4" :key="item.id" class="flow-box">
+                                    <div v-if="item.content">
+                                        "{{ item.content }}"
+                                    </div>
+                                    <div>
+                                        <span v-if="item.type === 3"><span class="text-gray">抄送</span> {{ item.name }}</span>
+                                        <span v-if="item.type === 9"><span class="text-gray">执行人</span> {{ item.name }}</span>
+                                        <span v-if="item.type === 2||item.type === 8">
+                                            {{ item.name }}
+                                            <span v-if="item.status === 0" class="text-gray">待审批</span>
+                                            <span v-if="item.status === 1" class="text-gray">已同意</span>
+                                            <span v-if="item.status === 2" class="text-gray">已拒绝</span>
+                                            <span v-if="item.status === 3" class="text-gray">已转交</span>
+                                            <span v-if="item.status === 4" class="text-gray">已流转</span>
+                                        </span>
+                                        <span v-if="item.type === 2||item.type === 8" class="pull-right">{{ item.finishTime }}</span>
+                                        <span v-if="item.type === 3||item.type === 9" class="pull-right">{{ item.createTime }}</span>
+                                    </div>
+                                </div>
+                            </template>
+                        </td>
+                    </tr>
+                </table>
+            </div>
+        </div>
+        <p class="text-center">
+            <el-button icon="el-icon-printer" type="info" @click="print">
+                打印
+            </el-button>
+        </p>
+    </div>
+</template>
+
+<script>
+import Vue from 'vue';
+import Component from 'vue-class-component';
+import Print from '@/common/utils/print';
+
+Vue.use(Print); // 注册
+
+// 表单打印
+@Component({
+    props: {
+        data: {
+            type: Object,
+            default() {
+                return {};
+            },
+        },
+        table: {
+            type: Array,
+            default() {
+                return [];
+            },
+        },
+        advform: {
+            type: Array,
+            default() {
+                return [];
+            },
+        },
+        flow: {
+            type: Array,
+            default() {
+                return [];
+            },
+        },
+    },
+    components: {
+
+    },
+    computed: {
+
+    },
+})
+export default class ApplyPrint5 extends Vue {
+    formJson = [];
+
+    jjcontractform = []
+
+    tableData = [];
+
+    flowJsonRecord = [];
+
+    items = [];
+
+    total = 0;
+
+    created() {
+        this.jjcontractform = this.advform;
+        this.tableData = this.table;
+        this.flowJsonRecord = this.flow;
+
+        this.jjcontractform.forEach((item) => {
+            if (item.idx === 0) {
+                this.items = JSON.parse(item.values);
+            }
+        });
+
+        this.items.forEach((item) => {
+            this.total += Number(item.price) * item.snums;
+        });
+    }
+
+    print() {
+        this.$print(this.$refs.approval);
+    }
+}
+</script>
+
+<style media="print">
+    @page {
+        size: auto;
+        margin: 0px 50px;
+    }
+</style>
+<style scoped lang="scss">
+.approval-print{
+    padding-top: 30px;
+    max-width: 1000px;
+    margin: 0 auto;
+}
+.flow-box{
+    padding: 2px 5px;
+    border-bottom: 2px solid #303133;
+}
+.flow-box:last-child{
+    border-bottom: 0;
+}
+td{
+    padding: 2px 5px;
+}
+.td-nopadding{
+    padding: 0;
+}
+
+// table,tr,td{
+//     border: 1px solid #909399;
+// }
+table.no-border{
+    border:0;
+}
+.td-title{
+    width: 100px;
+}
+</style>

+ 278 - 0
src/apps/approval/components/apply-print/apply-print17.vue

@@ -0,0 +1,278 @@
+<template>
+    <div>
+        <div ref="approval" class="approval-print">
+            <div>
+                <h4 class="text-center">
+                    {{ data.approvalName }}
+                </h4>
+                <table style="width:100%;">
+                    <tr>
+                        <td>部门:{{ data.depName }}</td>
+                        <td class="text-right">
+                            日期:{{ data.createTime }}
+                        </td>
+                    </tr>
+                </table>
+                <table border="1" style="width:100%;" cellspacing="0" cellpadding="0">
+                    <tr>
+                        <td colspan="4" class="text-center">
+                            {{ company }} {{ title }}
+                        </td>
+                    </tr>
+                    <tr>
+                        <td style="width: 250px;">
+                            预算项目
+                        </td>
+                        <td colspan="2">
+                            核算内容
+                        </td>
+                        <td style="width: 100px;">
+                            金额(万元)
+                        </td>
+                    </tr>
+
+                    <tr v-for="item in items" :key="item.id">
+                        <td>
+                            {{ item.title }}
+                        </td>
+                        <td colspan="2">
+                            {{ item.remark }}
+                        </td>
+                        <td>
+                            {{ item.money }}
+                        </td>
+                    </tr>
+                    <tr>
+                        <td colspan="3" class="text-right">
+                            总金额(万元)
+                        </td>
+                        <td style="width: 100px;">
+                            {{ total }}
+                        </td>
+                    </tr>
+
+                    <tr v-for="(item,index) in tableData" :key="index">
+                        <template v-if="item.type === 1">
+                            <td style="width:120px;">
+                                {{ item.name }}
+                            </td>
+                            <td colspan="3">
+                                {{ item.value }}
+                            </td>
+                        </template>
+                        <template v-if="item.type === 4">
+                            <td v-if="item.value.tables.length > 0" colspan="4" class="td-nopadding">
+                                <table border="1" style="width:100%;" cellspacing="0" cellpadding="0">
+                                    <tr>
+                                        <td style="width: 118px;">
+                                            {{ item.name }}
+                                        </td>
+                                        <td v-for="(item1,index1) in item.value.tables[0]" :key="`${index1}a`">
+                                            {{ item1.defaultLable }}
+                                        </td>
+                                    </tr>
+                                    <tr v-for="(item2,index2) in item.value.tables" :key="`${index2}b`">
+                                        <td>{{ item.name }}{{ index2 + 1 }}</td>
+                                        <td v-for="item3 in item2" :key="`${item3.idx}c${index2}`">
+                                            {{ item3.values }}
+                                        </td>
+                                    </tr>
+                                    <tr v-if="item.value.total.length > 0&&item.value.tables.length > 0">
+                                        <td>
+                                            合计
+                                        </td>
+                                        <td :colspan="item.value.tables[0].length">
+                                            <div v-for="(val,vindex) in item.value.total" :key="vindex">
+                                                <span>总{{ val.defaultLable }}: </span>
+                                                <span>{{ val.values }} <span v-if="val.valuesTranslate">({{ val.valuesTranslate }})</span></span>
+                                            </div>
+                                        </td>
+                                    </tr>
+                                </table>
+                            </td>
+                        </template>
+                        <template v-if="item.type === 5">
+                            <td v-if="item.value.tables.length > 0" colspan="4" class="td-nopadding">
+                                <table border="1" style="width:100%;" cellspacing="0" cellpadding="0">
+                                    <tr :rowspan="item.value.tables.length">
+                                        <td style="width: 118px;">
+                                            {{ item.name }}
+                                        </td>
+                                        <td style="padding: 0;">
+                                            <table border="1" style="width:100%;" cellspacing="0" cellpadding="0">
+                                                <tr v-for="citem in item.value.tables" :key="`${citem.idx}_${item.name}`">
+                                                    <td style="width: 118px;">
+                                                        {{ citem.defaultLable }}
+                                                    </td>
+                                                    <td>
+                                                        {{ citem.values }}
+                                                    </td>
+                                                </tr>
+                                            </table>
+                                        </td>
+                                    </tr>
+                                </table>
+                            </td>
+                        </template>
+                    </tr>
+
+                    <tr>
+                        <td style="width:120px;">
+                            审批流程
+                        </td>
+                        <td colspan="3" class="td-nopadding">
+                            <template v-for="item in flowJsonRecord">
+                                <div v-if="item.status != 3&&item.status != 4" :key="item.id" class="flow-box">
+                                    <div v-if="item.content">
+                                        "{{ item.content }}"
+                                    </div>
+                                    <div>
+                                        <span v-if="item.type === 3"><span class="text-gray">抄送</span> {{ item.name }}</span>
+                                        <span v-if="item.type === 9"><span class="text-gray">执行人</span> {{ item.name }}</span>
+                                        <span v-if="item.type === 2||item.type === 8">
+                                            {{ item.name }}
+                                            <span v-if="item.status === 0" class="text-gray">待审批</span>
+                                            <span v-if="item.status === 1" class="text-gray">已同意</span>
+                                            <span v-if="item.status === 2" class="text-gray">已拒绝</span>
+                                            <span v-if="item.status === 3" class="text-gray">已转交</span>
+                                            <span v-if="item.status === 4" class="text-gray">已流转</span>
+                                        </span>
+                                        <span v-if="item.type === 2||item.type === 8" class="pull-right">{{ item.finishTime }}</span>
+                                        <span v-if="item.type === 3||item.type === 9" class="pull-right">{{ item.createTime }}</span>
+                                    </div>
+                                </div>
+                            </template>
+                        </td>
+                    </tr>
+                </table>
+            </div>
+        </div>
+        <p class="text-center">
+            <el-button icon="el-icon-printer" type="info" @click="print">
+                打印
+            </el-button>
+        </p>
+    </div>
+</template>
+
+<script>
+import Vue from 'vue';
+import Component from 'vue-class-component';
+import Print from '@/common/utils/print';
+
+Vue.use(Print); // 注册
+
+// 表单打印
+@Component({
+    props: {
+        data: {
+            type: Object,
+            default() {
+                return {};
+            },
+        },
+        table: {
+            type: Array,
+            default() {
+                return [];
+            },
+        },
+        advform: {
+            type: Array,
+            default() {
+                return [];
+            },
+        },
+        flow: {
+            type: Array,
+            default() {
+                return [];
+            },
+        },
+    },
+    components: {
+
+    },
+    computed: {
+
+    },
+})
+export default class ApplyPrint5 extends Vue {
+    formJson = [];
+
+    jjcontractform = []
+
+    tableData = [];
+
+    flowJsonRecord = [];
+
+    items = [];
+
+    title = '';
+
+    company = '';
+
+    total = 0;
+
+    created() {
+        this.jjcontractform = this.advform;
+        this.tableData = this.table;
+        this.flowJsonRecord = this.flow;
+
+        this.jjcontractform.forEach((item) => {
+            if (item.idx === 0) {
+                const vals = JSON.parse(item.values);
+                this.title = vals.title;
+                this.company = vals.company;
+                this.items = vals.items;
+            }
+        });
+
+
+        this.items.forEach((item) => {
+            this.total += Number(item.money);
+        });
+    }
+
+    print() {
+        this.$print(this.$refs.approval);
+    }
+}
+</script>
+
+<style media="print">
+    @page {
+        size: auto;
+        margin: 0px 50px;
+    }
+</style>
+<style scoped lang="scss">
+.approval-print{
+    padding-top: 30px;
+    max-width: 1000px;
+    margin: 0 auto;
+}
+.flow-box{
+    padding: 2px 5px;
+    border-bottom: 2px solid #303133;
+}
+.flow-box:last-child{
+    border-bottom: 0;
+}
+td{
+    padding: 2px 5px;
+}
+.td-nopadding{
+    padding: 0;
+}
+
+// table,tr,td{
+//     border: 1px solid #909399;
+// }
+table.no-border{
+    border:0;
+}
+.td-title{
+    width: 100px;
+}
+</style>

+ 0 - 0
src/apps/approval/components/apply-print/apply-print20.vue


Some files were not shown because too many files changed in this diff