sky 4 miesięcy temu
commit
f9088e13e9
100 zmienionych plików z 15053 dodań i 0 usunięć
  1. 39 0
      .editorconfig
  2. 1 0
      .eslintignore
  3. 1 0
      .gitattributes
  4. 22 0
      .gitignore
  5. 5 0
      .prettierrc
  6. 104 0
      README.md
  7. 6 0
      babel.config.js
  8. 24 0
      idea.config.js
  9. 109 0
      package.json
  10. BIN
      public/doc/apply_item_template.xls
  11. BIN
      public/doc/buy_sale_item_template.xls
  12. BIN
      public/doc/customer_template.xls
  13. BIN
      public/doc/goods_template.xls
  14. BIN
      public/doc/in_out_item_template.xls
  15. BIN
      public/doc/member_template.xls
  16. BIN
      public/doc/order_item_template.xls
  17. BIN
      public/doc/vendor_template.xls
  18. 319 0
      public/index.html
  19. BIN
      public/static/Android.png
  20. BIN
      public/static/android-code.png
  21. BIN
      public/static/banner-home.jpg
  22. BIN
      public/static/banner-small.jpg
  23. BIN
      public/static/bgimg.png
  24. BIN
      public/static/blue.png
  25. 3456 0
      public/static/color.less
  26. BIN
      public/static/favicon.ico
  27. BIN
      public/static/iPhone.png
  28. BIN
      public/static/iphone-code.png
  29. 12 0
      public/static/less.min.js
  30. BIN
      public/static/mini-program.png
  31. BIN
      public/static/rightImg.png
  32. BIN
      public/static/screenshot/1.jpg
  33. BIN
      public/static/screenshot/10.jpg
  34. BIN
      public/static/screenshot/2.jpg
  35. BIN
      public/static/screenshot/3.jpg
  36. BIN
      public/static/screenshot/4.jpg
  37. BIN
      public/static/screenshot/5.jpg
  38. BIN
      public/static/screenshot/6.jpg
  39. BIN
      public/static/screenshot/7.jpg
  40. BIN
      public/static/screenshot/8.jpg
  41. BIN
      public/static/screenshot/9.jpg
  42. 3725 0
      public/static/translate.js
  43. BIN
      public/static/weixin-code.png
  44. BIN
      public/static/weixin.jpg
  45. 43 0
      src/App.vue
  46. 30 0
      src/api/GroupRequest.js
  47. 213 0
      src/api/api.js
  48. 10 0
      src/api/index.js
  49. 28 0
      src/api/login.js
  50. 164 0
      src/api/manage.js
  51. BIN
      src/assets/checkcode.png
  52. 40 0
      src/assets/dark.svg
  53. 259 0
      src/assets/less/JAreaLinkage.less
  54. 15 0
      src/assets/less/TableExpand.less
  55. 96 0
      src/assets/less/common.less
  56. 28 0
      src/assets/less/index.less
  57. 40 0
      src/assets/light.svg
  58. 46 0
      src/components/AvatarList/Item.vue
  59. 91 0
      src/components/AvatarList/List.vue
  60. 4 0
      src/components/AvatarList/index.js
  61. 60 0
      src/components/AvatarList/index.less
  62. 105 0
      src/components/ChartCard.vue
  63. 103 0
      src/components/CountDown/CountDown.vue
  64. 3 0
      src/components/CountDown/index.js
  65. 36 0
      src/components/Ellipsis/Ellipsis.vue
  66. 3 0
      src/components/Ellipsis/index.js
  67. 54 0
      src/components/NumberInfo/NumberInfo.vue
  68. 3 0
      src/components/NumberInfo/index.js
  69. 55 0
      src/components/NumberInfo/index.less
  70. 41 0
      src/components/Trend/Trend.vue
  71. 3 0
      src/components/Trend/index.js
  72. 42 0
      src/components/Trend/index.less
  73. 88 0
      src/components/chart/AreaChartTy.vue
  74. 55 0
      src/components/chart/Bar.vue
  75. 60 0
      src/components/chart/BarAndLine.vue
  76. 88 0
      src/components/chart/BarMultid.vue
  77. 187 0
      src/components/chart/DashChartDemo.vue
  78. 61 0
      src/components/chart/IndexBar.vue
  79. 94 0
      src/components/chart/LineChartMultid.vue
  80. 80 0
      src/components/chart/Liquid.vue
  81. 69 0
      src/components/chart/MiniArea.vue
  82. 76 0
      src/components/chart/MiniBar.vue
  83. 75 0
      src/components/chart/MiniProgress.vue
  84. 70 0
      src/components/chart/Pie.vue
  85. 90 0
      src/components/chart/Radar.vue
  86. 81 0
      src/components/chart/RankList.vue
  87. 54 0
      src/components/chart/StackBar.vue
  88. 66 0
      src/components/chart/TransferBar.vue
  89. 84 0
      src/components/chart/Trend.vue
  90. 13 0
      src/components/chart/chart.less
  91. 13 0
      src/components/chart/chart.scss
  92. 10 0
      src/components/chart/mixins/ChartMixins.js
  93. 4 0
      src/components/index.less
  94. 155 0
      src/components/jeecg/JAreaLinkage.vue
  95. 238 0
      src/components/jeecg/JCategorySelect.vue
  96. 43 0
      src/components/jeecg/JCheckbox.vue
  97. 429 0
      src/components/jeecg/JCodeEditor.vue
  98. 65 0
      src/components/jeecg/JCron.vue
  99. 86 0
      src/components/jeecg/JDate.vue
  100. 3181 0
      src/components/jeecg/JEditableTable.vue

+ 39 - 0
.editorconfig

@@ -0,0 +1,39 @@
+[*]
+charset=utf-8
+end_of_line=crlf
+insert_final_newline=false
+indent_style=space
+indent_size=2
+
+[{*.ng,*.sht,*.html,*.shtm,*.shtml,*.htm}]
+indent_style=space
+indent_size=2
+
+[{*.jhm,*.xslt,*.xul,*.rng,*.xsl,*.xsd,*.ant,*.tld,*.fxml,*.jrxml,*.xml,*.jnlp,*.wsdl}]
+indent_style=space
+indent_size=2
+
+[{.babelrc,.stylelintrc,jest.config,.eslintrc,.prettierrc,*.json,*.jsb3,*.jsb2,*.bowerrc}]
+indent_style=space
+indent_size=2
+
+[*.svg]
+indent_style=space
+indent_size=2
+
+[*.js.map]
+indent_style=space
+indent_size=2
+
+[*.less]
+indent_style=space
+indent_size=2
+
+[*.vue]
+indent_style=space
+indent_size=2
+
+[{.analysis_options,*.yml,*.yaml}]
+indent_style=space
+indent_size=2
+

+ 1 - 0
.eslintignore

@@ -0,0 +1 @@
+/src

+ 1 - 0
.gitattributes

@@ -0,0 +1 @@
+public/* linguist-vendored

+ 22 - 0
.gitignore

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

+ 5 - 0
.prettierrc

@@ -0,0 +1,5 @@
+{
+  "printWidth": 120,
+  "semi": false,
+  "singleQuote": true
+}

+ 104 - 0
README.md

@@ -0,0 +1,104 @@
+jshERP-web Vue
+====
+
+
+Overview
+----
+
+
+#### 前端技术
+ 
+- 基础框架:[ant-design-vue](https://github.com/vueComponent/ant-design-vue) - Ant Design Of Vue 实现
+- JavaScript框架:Vue
+- Jeecg-boot 的前段UI框架
+- Webpack
+- node
+- yarn
+- eslint
+- @vue/cli 3.2.1
+- [vue-cropper](https://github.com/xyxiao001/vue-cropper) - 头像裁剪组件
+- [@antv/g2](https://antv.alipay.com/zh-cn/index.html) - Alipay AntV 数据可视化图表
+- [Viser-vue](https://viserjs.github.io/docs.html#/viser/guide/installation)  - antv/g2 封装实现
+
+
+
+项目运行
+----
+
+- 安装nodeJS
+```
+建议安装node-v20.17.0-x64版本 教程参考 https://blog.csdn.net/Coin_Collecter/article/details/136484312
+```
+
+- 安装yarn
+```
+npm install -g yarn
+```
+
+- 配镜像源(速度快)
+```
+yarn config set registry https://registry.npmmirror.com
+```
+
+- 安装依赖
+```
+yarn install
+```
+
+- 开发模式运行
+```
+yarn serve
+```
+
+- 编译发布项目
+```
+yarn build
+```
+
+
+其他说明
+----
+
+- 项目使用的 [vue-cli3](https://cli.vuejs.org/guide/), 请更新您的 cli
+
+- 关闭 Eslint (不推荐) 移除 `package.json` 中 `eslintConfig` 整个节点代码
+
+- 修改 Ant Design 配色,在文件 `vue.config.js` 中,其他 less 变量覆盖参考 [ant design](https://ant.design/docs/react/customize-theme-cn) 官方说明
+```ecmascript 6
+  css: {
+    loaderOptions: {
+      less: {
+        modifyVars: {
+          /* less 变量覆盖,用于自定义 ant design 主题 */
+
+          'primary-color': '#F5222D',
+          'link-color': '#F5222D',
+          'border-radius-base': '4px',
+        },
+        javascriptEnabled: true,
+      }
+    }
+  }
+```
+
+
+
+附属文档
+----
+- [Ant Design Vue](https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn)
+
+- [报表 viser-vue](https://viserjs.github.io/demo.html#/viser/bar/basic-bar)
+
+- [Vue](https://cn.vuejs.org/v2/guide)
+
+- [路由/菜单说明](https://github.com/zhangdaiscott/jeecg-boot/tree/master/ant-design-jeecg-vue/src/router/README.md)
+
+- [ANTD 默认配置项](https://github.com/zhangdaiscott/jeecg-boot/tree/master/ant-design-jeecg-vue/src/defaultSettings.js)
+
+- 其他待补充...
+
+
+备注
+----
+
+> @vue/cli 升级后,eslint 规则更新了。由于影响到全部 .vue 文件,需要逐个验证。既暂时关闭部分原本不验证的规则,后期维护时,在逐步修正这些 rules

+ 6 - 0
babel.config.js

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

+ 24 - 0
idea.config.js

@@ -0,0 +1,24 @@
+'use strict'
+const path = require('path')
+
+function resolve (dir) {
+  return path.join(__dirname, '.', dir)
+}
+
+module.exports = {
+    context: path.resolve(__dirname, './'),
+    resolve: {
+        extensions: ['.js', '.vue', '.json'],
+        alias: {
+            'config': resolve('config'),
+            '@': resolve('src'),
+            '@views': resolve('src/views'),
+            '@comp': resolve('src/components'),
+            '@core': resolve('src/core'),
+            '@utils': resolve('src/utils'),
+            '@entry': resolve('src/entry'),
+            '@router': resolve('src/router'),
+            '@store': resolve('src/store')
+        }
+    },
+}

+ 109 - 0
package.json

@@ -0,0 +1,109 @@
+{
+  "name": "jsh-erp-web",
+  "version": "3.5.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build"
+  },
+  "dependencies": {
+    "@antv/data-set": "^0.11.2",
+    "@tinymce/tinymce-vue": "^2.0.0",
+    "ant-design-vue": "1.5.2",
+    "area-data": "^5.0.6",
+    "axios": "^0.18.0",
+    "clipboard": "^2.0.4",
+    "codemirror": "^5.46.0",
+    "dayjs": "^1.8.0",
+    "enquire.js": "^2.1.6",
+    "intro.js": "^4.2.2",
+    "jquery": "^1.12.4",
+    "js-cookie": "^2.2.0",
+    "lodash.get": "^4.4.2",
+    "lodash.pick": "^4.4.0",
+    "md5": "^2.2.1",
+    "nprogress": "^0.2.0",
+    "viser-vue": "^2.4.4",
+    "vue": "^2.7.16",
+    "vue-area-linkage": "^5.1.0",
+    "vue-cropper": "^0.4.8",
+    "vue-draggable-resizable": "^2.3.0",
+    "vue-i18n": "^8.7.0",
+    "vue-loader": "^15.7.0",
+    "vue-ls": "^3.2.0",
+    "vue-photo-preview": "^1.1.3",
+    "vue-print-nb-jeecg": "^1.0.9",
+    "vue-router": "^3.0.1",
+    "vue-splitpane": "^1.0.4",
+    "vuedraggable": "^2.20.0",
+    "vuex": "^3.1.0"
+  },
+  "devDependencies": {
+    "@babel/polyfill": "^7.2.5",
+    "@vue/cli-plugin-babel": "^3.3.0",
+    "@vue/cli-plugin-eslint": "^3.3.0",
+    "@vue/cli-service": "^3.3.0",
+    "@vue/eslint-config-standard": "^4.0.0",
+    "babel-eslint": "^10.0.1",
+    "compression-webpack-plugin": "^3.1.0",
+    "eslint": "^5.16.0",
+    "eslint-plugin-vue": "^5.1.0",
+    "html-webpack-plugin": "^4.2.0",
+    "less": "^3.9.0",
+    "less-loader": "^4.1.0",
+    "vue-template-compiler": "^2.6.10"
+  },
+  "eslintConfig": {
+    "root": true,
+    "env": {
+      "node": true
+    },
+    "extends": [
+      "plugin:vue/strongly-recommended",
+      "@vue/standard"
+    ],
+    "parserOptions": {
+      "parser": "babel-eslint"
+    },
+    "rules": {
+      "generator-star-spacing": "off",
+      "no-mixed-operators": 0,
+      "vue/max-attributes-per-line": [
+        2,
+        {
+          "singleline": 5,
+          "multiline": {
+            "max": 1,
+            "allowFirstLine": false
+          }
+        }
+      ],
+      "vue/attribute-hyphenation": 0,
+      "vue/html-self-closing": 0,
+      "vue/component-name-in-template-casing": 0,
+      "vue/html-closing-bracket-spacing": 0,
+      "vue/singleline-html-element-content-newline": 0,
+      "vue/no-unused-components": 0,
+      "vue/multiline-html-element-content-newline": 0,
+      "vue/no-use-v-if-with-v-for": 0,
+      "vue/html-closing-bracket-newline": 0,
+      "vue/no-parsing-error": 0,
+      "no-console": 0,
+      "no-tabs": 0,
+      "indent": [
+        1,
+        4
+      ]
+    }
+  },
+  "postcss": {
+    "plugins": {
+      "autoprefixer": {}
+    }
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not ie <= 10"
+  ]
+}

BIN
public/doc/apply_item_template.xls


BIN
public/doc/buy_sale_item_template.xls


BIN
public/doc/customer_template.xls


BIN
public/doc/goods_template.xls


BIN
public/doc/in_out_item_template.xls


BIN
public/doc/member_template.xls


BIN
public/doc/order_item_template.xls


BIN
public/doc/vendor_template.xls


+ 319 - 0
public/index.html

@@ -0,0 +1,319 @@
+<!DOCTYPE html>
+<html lang="zh-cmn-Hans">
+<head>
+  <title></title>
+  <meta charset="utf-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport" content="width=device-width,initial-scale=1.0">
+  <meta name="description" content="基于SpringBoot框架,立志为中小企业提供开源好用的ERP软件,目前专注进销存+财务功能。主要模块有零售管理、采购管理、销售管理、仓库管理、财务管理、报表查询、基础数据、系统管理等。" />
+  <meta name="keywords" content="erp,erp系统,进销存,进销存系统" />
+  <link rel="icon" href="<%= BASE_URL %>static/favicon.ico">
+  <style>
+    html,
+    body,
+    #app {
+      height: 100%;
+      margin: 0px;
+      padding: 0px;
+    }
+    .chromeframe {
+      margin: 0.2em 0;
+      background: #ccc;
+      color: #999;
+      padding: 0.2em 0;
+    }
+    #loader-wrapper {
+      position: fixed;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      z-index: 999999;
+    }
+    #loader {
+      display: block;
+      position: relative;
+      left: 50%;
+      top: 50%;
+      width: 120px;
+      height: 120px;
+      margin: -75px 0 0 -75px;
+      border-radius: 50%;
+      border: 3px solid transparent;
+      /* COLOR 1 */
+      border-top-color: #999;
+      -webkit-animation: spin 2s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      -ms-animation: spin 2s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      -moz-animation: spin 2s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      -o-animation: spin 2s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      animation: spin 2s linear infinite;
+      /* Chrome, Firefox 16+, IE 10+, Opera */
+      z-index: 1001;
+    }
+    #loader:before {
+      content: "";
+      position: absolute;
+      top: 5px;
+      left: 5px;
+      right: 5px;
+      bottom: 5px;
+      border-radius: 50%;
+      border: 3px solid transparent;
+      /* COLOR 2 */
+      border-top-color: #999;
+      -webkit-animation: spin 3s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      -moz-animation: spin 3s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      -o-animation: spin 3s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      -ms-animation: spin 3s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      animation: spin 3s linear infinite;
+      /* Chrome, Firefox 16+, IE 10+, Opera */
+    }
+    #loader:after {
+      content: "";
+      position: absolute;
+      top: 15px;
+      left: 15px;
+      right: 15px;
+      bottom: 15px;
+      border-radius: 50%;
+      border: 3px solid transparent;
+      border-top-color: #999;
+      /* COLOR 3 */
+      -moz-animation: spin 1.5s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      -o-animation: spin 1.5s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      -ms-animation: spin 1.5s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      -webkit-animation: spin 1.5s linear infinite;
+      /* Chrome, Opera 15+, Safari 5+ */
+      animation: spin 1.5s linear infinite;
+      /* Chrome, Firefox 16+, IE 10+, Opera */
+    }
+    @-webkit-keyframes spin {
+      0% {
+        -webkit-transform: rotate(0deg);
+        /* Chrome, Opera 15+, Safari 3.1+ */
+        -ms-transform: rotate(0deg);
+        /* IE 9 */
+        transform: rotate(0deg);
+        /* Firefox 16+, IE 10+, Opera */
+      }
+      100% {
+        -webkit-transform: rotate(360deg);
+        /* Chrome, Opera 15+, Safari 3.1+ */
+        -ms-transform: rotate(360deg);
+        /* IE 9 */
+        transform: rotate(360deg);
+        /* Firefox 16+, IE 10+, Opera */
+      }
+    }
+    @keyframes spin {
+      0% {
+        -webkit-transform: rotate(0deg);
+        /* Chrome, Opera 15+, Safari 3.1+ */
+        -ms-transform: rotate(0deg);
+        /* IE 9 */
+        transform: rotate(0deg);
+        /* Firefox 16+, IE 10+, Opera */
+      }
+      100% {
+        -webkit-transform: rotate(360deg);
+        /* Chrome, Opera 15+, Safari 3.1+ */
+        -ms-transform: rotate(360deg);
+        /* IE 9 */
+        transform: rotate(360deg);
+        /* Firefox 16+, IE 10+, Opera */
+      }
+    }
+    #loader-wrapper .loader-section {
+      position: fixed;
+      top: 0;
+      width: 51%;
+      height: 100%;
+      background: #fff;
+      /* Old browsers */
+      z-index: 1000;
+      -webkit-transform: translateX(0);
+      /* Chrome, Opera 15+, Safari 3.1+ */
+      -ms-transform: translateX(0);
+      /* IE 9 */
+      transform: translateX(0);
+      /* Firefox 16+, IE 10+, Opera */
+    }
+    #loader-wrapper .loader-section.section-left {
+      left: 0;
+    }
+    #loader-wrapper .loader-section.section-right {
+      right: 0;
+    }
+    /* Loaded */
+    .loaded #loader-wrapper .loader-section.section-left {
+      -webkit-transform: translateX(-100%);
+      /* Chrome, Opera 15+, Safari 3.1+ */
+      -ms-transform: translateX(-100%);
+      /* IE 9 */
+      transform: translateX(-100%);
+      /* Firefox 16+, IE 10+, Opera */
+      -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+      transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+    }
+    .loaded #loader-wrapper .loader-section.section-right {
+      -webkit-transform: translateX(100%);
+      /* Chrome, Opera 15+, Safari 3.1+ */
+      -ms-transform: translateX(100%);
+      /* IE 9 */
+      transform: translateX(100%);
+      /* Firefox 16+, IE 10+, Opera */
+      -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+      transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
+    }
+    .loaded #loader {
+      opacity: 0;
+      -webkit-transition: all 0.3s ease-out;
+      transition: all 0.3s ease-out;
+    }
+    .loaded #loader-wrapper {
+      visibility: hidden;
+      -webkit-transform: translateY(-100%);
+      /* Chrome, Opera 15+, Safari 3.1+ */
+      -ms-transform: translateY(-100%);
+      /* IE 9 */
+      transform: translateY(-100%);
+      /* Firefox 16+, IE 10+, Opera */
+      -webkit-transition: all 0.3s 1s ease-out;
+      transition: all 0.3s 1s ease-out;
+    }
+    /* JavaScript Turned Off */
+    .no-js #loader-wrapper {
+      display: none;
+    }
+    .no-js h1 {
+      color: #222222;
+    }
+    #loader-wrapper .load_title {
+      font-family: 'Open Sans';
+      color: #999;
+      font-size: 14px;
+      width: 100%;
+      text-align: center;
+      z-index: 9999999999999;
+      position: absolute;
+      top: 60%;
+      opacity: 1;
+      line-height: 30px;
+    }
+    #loader-wrapper .load_title span {
+      font-weight: normal;
+      font-style: italic;
+      font-size: 14px;
+      color: #999;
+      opacity: 0.5;
+    }
+    /* 滚动条优化 start */
+    ::-webkit-scrollbar{
+      width:8px;
+      height:8px;
+    }
+    ::-webkit-scrollbar-track{
+      background: #f6f6f6;
+      border-radius:2px;
+    }
+    ::-webkit-scrollbar-thumb{
+      background: #cdcdcd;
+      border-radius:2px;
+    }
+    ::-webkit-scrollbar-thumb:hover{
+      background: #747474;
+    }
+    ::-webkit-scrollbar-corner {
+      background: #f6f6f6;
+    }
+    /* 滚动条优化 end */
+  </style>
+  <script>
+    function getPlatform(type) {
+      let res = '';
+      let ajax = new XMLHttpRequest();
+      let url = window._CONFIG['domianURL'] + '/platformConfig/getPlatform/' + type
+      ajax.onreadystatechange = function () {
+        if (ajax.readyState===4 &&ajax.status===200) {
+          res = ajax.responseText;
+        } else {
+          res = 'ERP系统';
+        }
+      }
+      ajax.open('get', url, false);
+      ajax.send(null);
+      return res;
+    }
+    window._CONFIG = {};
+    window._CONFIG['domianURL'] = '/jshERP-boot';
+    let statisticsCode = '1cd9bcbaae133f03a6eb19da6579aaba'
+    window.SYS_TITLE = getPlatform("name");
+    window.SYS_URL = getPlatform("url");
+    window._statistics = 'https://hm.baidu.com/hm.js?' + statisticsCode
+    document.title = window.SYS_TITLE;
+    //statistics
+    var _hmt = _hmt || [];
+    (function() {
+      var hm = document.createElement("script");
+      hm.src = window._statistics;
+      var s = document.getElementsByTagName("script")[0];
+      s.parentNode.insertBefore(hm, s);
+    })();
+  </script>
+</head>
+
+<body>
+<!-- built files will be auto injected -->
+<div id="app">
+  <div id="loader-wrapper">
+    <div id="loader"></div>
+    <div class="loader-section section-left"></div>
+    <div class="loader-section section-right"></div>
+    <div class="load_title">
+      正在加载系统,请耐心等待
+    </div>
+  </div>
+</div>
+</body>
+
+<!-- 全局配置-多语言切换-开始 -->
+<script src="<%= BASE_URL %>static/translate.js"></script>
+<script>
+//设置本地语种(当前网页的语种)。如果不设置,默认就是 'chinese_simplified' 简体中文
+translate.language.setLocal('chinese_simplified');
+translate.service.use('client.edge');
+//翻译自定义
+translate.nomenclature.append('chinese_simplified','english',`
+管伊佳ERP=GuanYiJia
+`)
+//开启html页面变化的监控,对变化部分会进行自动翻译
+translate.listener.start();
+//不显示语言选择标签
+translate.selectLanguageTag.show = false;
+//执行翻译初始化操作,显示出select语言选择
+//translate.execute();
+
+//VUE的渲染需要时间,所以留出一点点时间来进行翻译切换
+document.addEventListener('DOMContentLoaded', function () {
+    //页面 DOM 已渲染完毕,当然最好是能监控到整个vue渲染完毕后触发最好
+    translate.execute();
+    //2秒后再一次,避免有遗漏
+    setTimeout(function(){
+    	translate.execute();
+    },2000);
+});
+</script>
+<!-- 全局配置-多语言切换-结束 -->
+
+</html>

BIN
public/static/Android.png


BIN
public/static/android-code.png


BIN
public/static/banner-home.jpg


BIN
public/static/banner-small.jpg


BIN
public/static/bgimg.png


BIN
public/static/blue.png


Plik diff jest za duży
+ 3456 - 0
public/static/color.less


BIN
public/static/favicon.ico


BIN
public/static/iPhone.png


BIN
public/static/iphone-code.png


Plik diff jest za duży
+ 12 - 0
public/static/less.min.js


BIN
public/static/mini-program.png


BIN
public/static/rightImg.png


BIN
public/static/screenshot/1.jpg


BIN
public/static/screenshot/10.jpg


BIN
public/static/screenshot/2.jpg


BIN
public/static/screenshot/3.jpg


BIN
public/static/screenshot/4.jpg


BIN
public/static/screenshot/5.jpg


BIN
public/static/screenshot/6.jpg


BIN
public/static/screenshot/7.jpg


BIN
public/static/screenshot/8.jpg


BIN
public/static/screenshot/9.jpg


Plik diff jest za duży
+ 3725 - 0
public/static/translate.js


BIN
public/static/weixin-code.png


BIN
public/static/weixin.jpg


+ 43 - 0
src/App.vue

@@ -0,0 +1,43 @@
+<template>
+  <a-config-provider :locale="locale">
+    <div id="app">
+      <router-view/>
+    </div>
+  </a-config-provider>
+</template>
+<script>
+  import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN'
+  import enquireScreen from '@/utils/device'
+
+  export default {
+    data () {
+      return {
+        locale: zhCN,
+      }
+    },
+    created () {
+      let that = this
+      enquireScreen(deviceType => {
+        // tablet
+        if (deviceType === 0) {
+          that.$store.commit('TOGGLE_DEVICE', 'mobile')
+          that.$store.dispatch('setSidebar', false)
+        }
+        // mobile
+        else if (deviceType === 1) {
+          that.$store.commit('TOGGLE_DEVICE', 'mobile')
+          that.$store.dispatch('setSidebar', false)
+        }
+        else {
+          that.$store.commit('TOGGLE_DEVICE', 'desktop')
+          that.$store.dispatch('setSidebar', true)
+        }
+      })
+    }
+  }
+</script>
+<style>
+  #app {
+    height: 100%;
+  }
+</style>

+ 30 - 0
src/api/GroupRequest.js

@@ -0,0 +1,30 @@
+import Vue from 'vue'
+
+/**
+ * 将一个请求分组
+ *
+ * @param getPromise 传入一个可以获取到Promise对象的方法
+ * @param groupId 分组ID,如果不传或者为空则不分组
+ * @param expire 过期时间,默认 半分钟
+ */
+export function httpGroupRequest(getPromise, groupId, expire = 1000 * 30) {
+  if (groupId == null || groupId === '') {
+    console.log("--------popup----------getFrom  DB-------with---no--groupId ")
+    return getPromise()
+  }
+
+  if (Vue.ls.get(groupId)) {
+    console.log("---------popup--------getFrom  Cache--------groupId = " + groupId)
+    return Promise.resolve(Vue.ls.get(groupId));
+  } else {
+    console.log("--------popup----------getFrom  DB---------groupId = " + groupId)
+  }
+
+  // 还没有发出请求,就发出第一次的请求
+  return getPromise().then(res => {
+    Vue.ls.set(groupId, res, expire);
+    return Promise.resolve(res);
+  })
+}
+
+

+ 213 - 0
src/api/api.js

@@ -0,0 +1,213 @@
+import { getAction, deleteAction, putAction, postAction, httpAction } from '@/api/manage'
+
+//首页统计
+const getBuyAndSaleStatistics = (params)=>getAction("/depotHead/getBuyAndSaleStatistics",params);
+const buyOrSalePrice = (params)=>getAction("/depotItem/buyOrSalePrice",params);
+//租户管理
+const checkTenant = (params)=>getAction("/tenant/checkIsNameExist",params);
+const addTenant = (params)=>postAction("/tenant/add",params);
+const editTenant = (params)=>putAction("/tenant/update",params);
+//角色管理
+const addRole = (params)=>postAction("/role/add",params);
+const editRole = (params)=>putAction("/role/update",params);
+const checkRole = (params)=>getAction("/role/checkIsNameExist",params);
+const roleAllList = (params)=>getAction("/role/allList",params);
+const getTenantRoleList = (params)=>getAction("/role/tenantRoleList",params);
+//用户管理
+const registerUser = (params)=>postAction("/user/registerUser",params);
+const addUser = (params)=>postAction("/user/addUser",params);
+const editUser = (params)=>putAction("/user/updateUser",params);
+const getUserList = (params)=>getAction("/user/getUserList",params);
+const getUserBtnByCurrentUser = (params)=>getAction("/user/getUserBtnByCurrentUser",params);
+const queryPermissionsByUser = (params)=>postAction("/function/findMenuByPNumber",params);
+//机构管理
+const queryOrganizationTreeList = (params)=>getAction("/organization/getOrganizationTree",params);
+const getAllOrganizationTreeByUser = (params)=>getAction("/organization/getAllOrganizationTreeByUser",params);
+const queryOrganizationById = (params)=>getAction("/organization/findById",params);
+const checkOrganization = (params)=>getAction("/organization/checkIsNameExist",params);
+//经手人管理
+const addPerson = (params)=>postAction("/person/add",params);
+const editPerson = (params)=>putAction("/person/update",params);
+const checkPerson = (params)=>getAction("/person/checkIsNameExist",params);
+const getPersonByType = (params)=>getAction("/person/getPersonByType",params);
+const getPersonByNumType = (params)=>getAction("/person/getPersonByNumType",params);
+//账户管理
+const addAccount = (params)=>postAction("/account/add",params);
+const editAccount = (params)=>putAction("/account/update",params);
+const checkAccount = (params)=>getAction("/account/checkIsNameExist",params);
+const getAccount = (params)=>getAction("/account/getAccount",params);
+//收支项目
+const addInOutItem = (params)=>postAction("/inOutItem/add",params);
+const editInOutItem = (params)=>putAction("/inOutItem/update",params);
+const checkInOutItem = (params)=>getAction("/inOutItem/checkIsNameExist",params);
+const findInOutItemByParam = (params)=>getAction("/inOutItem/findBySelect",params);
+//仓库信息
+const addDepot = (params)=>postAction("/depot/add",params);
+const editDepot = (params)=>putAction("/depot/update",params);
+const checkDepot = (params)=>getAction("/depot/checkIsNameExist",params);
+//商品属性
+const addOrUpdateMaterialProperty = (params)=>postAction("/materialProperty/addOrUpdate",params);
+//商品类型
+const queryMaterialCategoryTreeList = (params)=>getAction("/materialCategory/getMaterialCategoryTree",params);
+const queryMaterialCategoryById = (params)=>getAction("/materialCategory/findById",params);
+const checkMaterialCategory = (params)=>getAction("/materialCategory/checkIsNameExist",params);
+//商品管理
+const addMaterial = (params)=>postAction("/material/add",params);
+const editMaterial = (params)=>putAction("/material/update",params);
+const checkMaterial = (params)=>getAction("/material/checkIsExist",params);
+const getMaterialBySelect = (params)=>getAction("/material/findBySelect",params);
+const getSerialMaterialBySelect = (params)=>getAction("/material/getMaterialEnableSerialNumberList",params);
+const getMaterialByParam = (params)=>getAction("/material/getMaterialByParam",params);
+const getMaterialByBarCode = (params)=>getAction("/material/getMaterialByBarCode",params);
+const getMaxBarCode = (params)=>getAction("/material/getMaxBarCode",params);
+const checkMaterialBarCode = (params)=>getAction("/materialsExtend/checkIsBarCodeExist",params);
+const batchUpdateMaterial = (params)=>postAction("/material/batchUpdate",params);
+const changeNameToPinYin = (params)=>postAction("/material/changeNameToPinYin",params);
+//序列号
+const batAddSerialNumber = (params)=>postAction("/serialNumber/batAddSerialNumber",params);
+const getEnableSerialNumberList = (params)=>getAction("/serialNumber/getEnableSerialNumberList",params);
+//多属性
+const addMaterialAttribute = (params)=>postAction("/materialAttribute/add",params);
+const editMaterialAttribute = (params)=>putAction("/materialAttribute/update",params);
+const checkMaterialAttribute = (params)=>getAction("/materialAttribute/checkIsNameExist",params);
+const getMaterialAttributeNameList = (params)=>getAction("/materialAttribute/getNameList",params);
+const getMaterialAttributeValueListById = (params)=>getAction("/materialAttribute/getValueListById",params);
+//功能管理
+const addFunction = (params)=>postAction("/function/add",params);
+const editFunction = (params)=>putAction("/function/update",params);
+const checkFunction = (params)=>getAction("/function/checkIsNameExist",params);
+const checkNumber = (params)=>getAction("/function/checkIsNumberExist",params);
+//系统配置
+const addSystemConfig = (params)=>postAction("/systemConfig/add",params);
+const editSystemConfig = (params)=>putAction("/systemConfig/update",params);
+const checkSystemConfig = (params)=>getAction("/systemConfig/checkIsNameExist",params);
+const getCurrentSystemConfig = (params)=>getAction("/systemConfig/getCurrentInfo",params);
+const fileSizeLimit = (params)=>getAction("/systemConfig/fileSizeLimit",params);
+//平台参数
+const addPlatformConfig = (params)=>postAction("/platformConfig/add",params);
+const editPlatformConfig = (params)=>putAction("/platformConfig/update",params);
+const getPlatformConfigByKey = (params)=>getAction("/platformConfig/getInfoByKey",params);
+//用户|角色|模块关系
+const addUserBusiness = (params)=>postAction("/userBusiness/add",params);
+const editUserBusiness = (params)=>putAction("/userBusiness/update",params);
+const checkUserBusiness = (params)=>getAction("/userBusiness/checkIsValueExist",params);
+const updateBtnStrByRoleId = (params)=>postAction("/userBusiness/updateBtnStr",params);
+const updateOneValueByKeyIdAndType = (params)=>postAction("/userBusiness/updateOneValueByKeyIdAndType",params);
+//多单位
+const addUnit = (params)=>postAction("/unit/add",params);
+const editUnit = (params)=>putAction("/unit/update",params);
+const checkUnit = (params)=>getAction("/unit/checkIsNameExist",params);
+//供应商|客户|会员
+const addSupplier = (params)=>postAction("/supplier/add",params);
+const editSupplier = (params)=>putAction("/supplier/update",params);
+const checkSupplier = (params)=>getAction("/supplier/checkIsNameAndTypeExist",params);
+const findBySelectSup = (params)=>postAction("/supplier/findBySelect_sup",params);
+const findBySelectCus = (params)=>postAction("/supplier/findBySelect_cus",params);
+const findBySelectRetail = (params)=>postAction("/supplier/findBySelect_retail",params);
+const findBySelectOrgan = (params)=>postAction("/supplier/findBySelect_organ",params);
+//单据相关
+const findBillDetailByNumber = (params)=>getAction("/depotHead/getDetailByNumber",params);
+const waitBillCount = (params)=>getAction("/depotHead/waitBillCount",params);
+const getNeedCount = (params)=>getAction("/depotHead/getNeedCount",params);
+const batchAddDepotHeadAndDetail = (params)=>postAction("/depotHead/batchAddDepotHeadAndDetail",params);
+const findStockByDepotAndBarCode = (params)=>getAction("/depotItem/findStockByDepotAndBarCode",params);
+const getBatchNumberList = (params)=>getAction("/depotItem/getBatchNumberList",params);
+const findFinancialDetailByNumber = (params)=>getAction("/accountHead/getDetailByNumber",params);
+
+export {
+  getBuyAndSaleStatistics,
+  buyOrSalePrice,
+  checkTenant,
+  addTenant,
+  editTenant,
+  addRole,
+  editRole,
+  checkRole,
+  roleAllList,
+  getTenantRoleList,
+  registerUser,
+  addUser,
+  editUser,
+  getUserList,
+  getUserBtnByCurrentUser,
+  queryPermissionsByUser,
+  queryOrganizationTreeList,
+  getAllOrganizationTreeByUser,
+  queryOrganizationById,
+  checkOrganization,
+  addPerson,
+  editPerson,
+  checkPerson,
+  getPersonByType,
+  getPersonByNumType,
+  addAccount,
+  editAccount,
+  checkAccount,
+  getAccount,
+  addInOutItem,
+  editInOutItem,
+  checkInOutItem,
+  findInOutItemByParam,
+  addDepot,
+  editDepot,
+  checkDepot,
+  addOrUpdateMaterialProperty,
+  queryMaterialCategoryTreeList,
+  queryMaterialCategoryById,
+  checkMaterialCategory,
+  addMaterial,
+  editMaterial,
+  checkMaterial,
+  getMaterialBySelect,
+  getSerialMaterialBySelect,
+  getMaterialByParam,
+  getMaterialByBarCode,
+  getMaxBarCode,
+  checkMaterialBarCode,
+  batchUpdateMaterial,
+  changeNameToPinYin,
+  batAddSerialNumber,
+  getEnableSerialNumberList,
+  addMaterialAttribute,
+  editMaterialAttribute,
+  checkMaterialAttribute,
+  getMaterialAttributeNameList,
+  getMaterialAttributeValueListById,
+  addFunction,
+  editFunction,
+  checkFunction,
+  checkNumber,
+  addSystemConfig,
+  editSystemConfig,
+  checkSystemConfig,
+  getCurrentSystemConfig,
+  fileSizeLimit,
+  addPlatformConfig,
+  editPlatformConfig,
+  getPlatformConfigByKey,
+  addUserBusiness,
+  editUserBusiness,
+  checkUserBusiness,
+  updateBtnStrByRoleId,
+  updateOneValueByKeyIdAndType,
+  addUnit,
+  editUnit,
+  checkUnit,
+  addSupplier,
+  editSupplier,
+  checkSupplier,
+  findBySelectSup,
+  findBySelectCus,
+  findBySelectRetail,
+  findBySelectOrgan,
+  findBillDetailByNumber,
+  waitBillCount,
+  getNeedCount,
+  batchAddDepotHeadAndDetail,
+  findStockByDepotAndBarCode,
+  getBatchNumberList,
+  findFinancialDetailByNumber
+}
+
+
+

+ 10 - 0
src/api/index.js

@@ -0,0 +1,10 @@
+const api = {
+    Login: '/user/login',
+    Logout: '/sys/logout',
+    ForgePassword: '/auth/forge-password',
+    Register: '/auth/register',
+    SendSms: '/account/sms',
+    // get my info
+    UserInfo: '/user/info'
+}
+export default api

+ 28 - 0
src/api/login.js

@@ -0,0 +1,28 @@
+import api from './index'
+import { axios } from '@/utils/request'
+
+/**
+ * login func
+ * parameter: {
+ *     username: '',
+ *     password: '',
+ *     remember_me: true,
+ *     captcha: '12345'
+ * }
+ * @param parameter
+ * @returns {*}
+ */
+export function login(parameter) {
+  return axios({
+    url: '/user/login',
+    method: 'post',
+    data: parameter
+  })
+}
+
+export function logout() {
+  return axios({
+    url: '/user/logout',
+    method: 'get'
+  })
+}

+ 164 - 0
src/api/manage.js

@@ -0,0 +1,164 @@
+import Vue from 'vue'
+import { axios } from '@/utils/request'
+
+const api = {
+  user: '/api/user',
+  role: '/api/role',
+  service: '/api/service',
+  permission: '/api/permission',
+  permissionNoPager: '/api/permission/no-pager',
+  exportExcelByParam: '/systemConfig/exportExcelByParam'
+}
+
+export default api
+
+//post
+export function postAction(url,parameter) {
+  return axios({
+    url: url,
+    method:'post' ,
+    data: parameter
+  })
+}
+
+//post method= {post | put}
+export function httpAction(url,parameter,method) {
+  return axios({
+    url: url,
+    method:method ,
+    data: parameter
+  })
+}
+
+//put
+export function putAction(url,parameter) {
+  return axios({
+    url: url,
+    method:'put',
+    data: parameter
+  })
+}
+
+//get
+export function getAction(url,parameter) {
+  return axios({
+    url: url,
+    method: 'get',
+    params: parameter
+  })
+}
+
+//deleteAction
+export function deleteAction(url,parameter) {
+  return axios({
+    url: url,
+    method: 'delete',
+    params: parameter
+  })
+}
+
+export function getUserList(parameter) {
+  return axios({
+    url: api.user,
+    method: 'get',
+    params: parameter
+  })
+}
+
+export function getRoleList(parameter) {
+  return axios({
+    url: api.role,
+    method: 'get',
+    params: parameter
+  })
+}
+
+export function getServiceList(parameter) {
+  return axios({
+    url: api.service,
+    method: 'get',
+    params: parameter
+  })
+}
+
+export function getPermissions(parameter) {
+  return axios({
+    url: api.permissionNoPager,
+    method: 'get',
+    params: parameter
+  })
+}
+
+// id == 0 add     post
+// id != 0 update  put
+export function saveService(parameter) {
+  return axios({
+    url: api.service,
+    method: parameter.id == 0 ? 'post' : 'put',
+    data: parameter
+  })
+}
+
+/**
+ * 下载文件 用于excel导出
+ * @param url
+ * @param parameter
+ * @returns {*}
+ */
+export function downFile(url,parameter){
+  return axios({
+    url: url,
+    params: parameter,
+    method: 'get',
+    responseType: 'blob'
+  })
+}
+
+/**
+ * 下载文件 用于excel导出
+ * @param url
+ * @param parameter
+ * @returns {*}
+ */
+export function downFilePost(parameter){
+  return axios({
+    url: api.exportExcelByParam,
+    data: parameter,
+    method: 'post',
+    responseType: 'blob'
+  })
+}
+
+/**
+ * 文件上传 用于富文本上传图片
+ * @param url
+ * @param parameter
+ * @returns {*}
+ */
+export function uploadAction(url,parameter){
+  return axios({
+    url: url,
+    data: parameter,
+    method:'post' ,
+    headers: {
+      'Content-Type': 'multipart/form-data',  // 文件上传
+    },
+  })
+}
+
+/**
+ * 获取文件服务访问路径
+ * @param avatar
+ * @param subStr
+ * @returns {*}
+ */
+export function getFileAccessHttpUrl(avatar,subStr) {
+  if(!subStr) subStr = 'http'
+  if(avatar && avatar.startsWith(subStr)){
+    return avatar;
+  }else{
+    if(avatar && avatar.length>0 && avatar.indexOf('[')==-1){
+      return window._CONFIG['domianURL'] + "/" + avatar;
+    }
+  }
+}

BIN
src/assets/checkcode.png


+ 40 - 0
src/assets/dark.svg

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
+    <title>Group 5 Copy 5</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
+            <feMerge>
+                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
+                <feMergeNode in="SourceGraphic"></feMergeNode>
+            </feMerge>
+        </filter>
+        <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
+        <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
+            <g id="Group-8" transform="translate(1167.000000, 0.000000)">
+                <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
+                    <mask id="mask-3" fill="white">
+                        <use xlink:href="#path-2"></use>
+                    </mask>
+                    <g id="Rectangle-18">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
+                        <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
+                    </g>
+                    <rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="-1" y="0" width="49" height="10"></rect>
+                    <rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="44"></rect>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 259 - 0
src/assets/less/JAreaLinkage.less

@@ -0,0 +1,259 @@
+.area-zoom-in-top-enter-active,
+.area-zoom-in-top-leave-active {
+  opacity: 1;
+  transform: scaleY(1);
+}
+
+.area-zoom-in-top-enter,
+.area-zoom-in-top-leave-active {
+  opacity: 0;
+  transform: scaleY(0);
+}
+
+.area-select {
+  box-sizing: border-box;
+  margin: 0;
+  padding: 0;
+  color: rgba(0, 0, 0, 0.65);
+  font-size: 14px;
+  font-variant: tabular-nums;
+  line-height: 1.5;
+  list-style: none;
+  font-feature-settings: 'tnum';
+  position: relative;
+  outline: 0;
+  display: block;
+  background-color: #fff;
+  border: 1px solid #d9d9d9;
+  border-top-width: 1.02px;
+  border-radius: 4px;
+  outline: none;
+  transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+  -webkit-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+
+.area-select-wrap .area-select {
+  display: inline-block;
+}
+
+.area-select * {
+  box-sizing: border-box;
+}
+
+.area-select:hover {
+  border-color: #40a9ff;
+  border-right-width: 1px !important;
+  outline: 0;
+}
+
+
+.area-select:active {
+  box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
+}
+
+.area-select.small {
+  width: 126px;
+}
+
+.area-select.medium {
+  width: 160px;
+}
+
+.area-select.large {
+  width: 194px;
+}
+
+.area-select.is-disabled {
+  background: #eceff5;
+  cursor: not-allowed;
+}
+
+.area-select.is-disabled:hover {
+  border-color: #e1e2e6;
+}
+
+.area-select.is-disabled .area-selected-trigger {
+  cursor: not-allowed;
+}
+
+.area-select .area-selected-trigger {
+  position: relative;
+  display: block;
+  font-size: 14px;
+  cursor: pointer;
+  margin: 0;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  height: 100%;
+  padding: 8px 20px 7px 12px;
+}
+
+.area-select .area-select-icon {
+  position: absolute;
+  top: 50%;
+  margin-top: -2px;
+  right: 6px;
+  content: "";
+  width: 0;
+  height: 0;
+  border: 6px solid transparent;
+  border-top-color: rgba(0, 0, 0, 0.25);
+  transition: all .3s linear;
+  transform-origin: center;
+}
+
+.area-select .area-select-icon.active {
+  margin-top: -8px;
+  transform: rotate(180deg);
+}
+
+.area-selectable-list-wrap {
+  position: absolute;
+  width: 100%;
+  max-height: 275px;
+  z-index: 15000;
+  background-color: #fff;
+  box-sizing: border-box;
+  overflow-x: auto;
+  margin: 2px 0;
+  border-radius: 4px;
+  outline: none;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+
+  transition: opacity 0.15s, transform 0.3s !important;
+  transform-origin: center top !important;
+}
+
+.area-selectable-list {
+  position: relative;
+  margin: 0;
+  padding: 6px 0;
+  width: 100%;
+  font-size: 14px;
+  color: #565656;
+  list-style: none;
+}
+
+.area-selectable-list .area-select-option {
+  position: relative;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  cursor: pointer;
+  padding: 0 15px 0 10px;
+  height: 32px;
+  line-height: 32px;
+}
+
+.area-selectable-list .area-select-option.hover {
+  background-color: #e6f7ff;
+}
+
+.area-selectable-list .area-select-option.selected {
+  color: rgba(0, 0, 0, 0.65);
+  font-weight: 600;
+  background-color: #efefef;
+}
+
+.cascader-menu-list-wrap {
+  position: absolute;
+  white-space: nowrap;
+  z-index: 15000;
+  background-color: #fff;
+  box-sizing: border-box;
+  overflow: hidden;
+  font-size: 0;
+  margin: 2px 0;
+  border-radius: 4px;
+  outline: none;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+
+  transition: opacity 0.15s, transform 0.3s !important;
+  transform-origin: center top !important;
+}
+
+.cascader-menu-list {
+  position: relative;
+  margin: 0;
+  font-size: 14px;
+  color: #565656;
+  padding: 6px 0;
+  list-style: none;
+  display: inline-block;
+  height: 204px;
+  overflow-x: hidden;
+  overflow-y: auto;
+  min-width: 160px;
+  vertical-align: top;
+  background-color: #fff;
+  border-right: 1px solid #e4e7ed;
+}
+
+.cascader-menu-list:last-child {
+  border-right: none;
+}
+
+.cascader-menu-list .cascader-menu-option {
+  position: relative;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  cursor: pointer;
+  padding: 0 15px 0 10px;
+  height: 32px;
+  line-height: 32px;
+}
+
+.cascader-menu-list .cascader-menu-option.hover,
+.cascader-menu-list .cascader-menu-option:hover {
+  background-color: #e6f7ff;
+}
+
+.cascader-menu-list .cascader-menu-option.selected {
+  color: rgba(0, 0, 0, 0.65);
+  font-weight: 600;
+  background-color: #efefef;
+}
+
+.cascader-menu-list .cascader-menu-option.cascader-menu-extensible:after {
+  position: absolute;
+  top: 50%;
+  margin-top: -4px;
+  right: 5px;
+  content: "";
+  width: 0;
+  height: 0;
+  border: 4px solid transparent;
+  border-left-color: #a1a4ad;
+}
+
+.cascader-menu-list::-webkit-scrollbar,
+.area-selectable-list-wrap::-webkit-scrollbar {
+  width: 8px;
+  background: transparent;
+}
+
+.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:decremen,
+.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:end:decrement,
+.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:increment,
+.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:start:increment,
+.cascader-menu-list::-webkit-scrollbar-button:vertical:decremen,
+.cascader-menu-list::-webkit-scrollbar-button:vertical:end:decrement,
+.cascader-menu-list::-webkit-scrollbar-button:vertical:increment,
+.cascader-menu-list::-webkit-scrollbar-button:vertical:start:increment {
+  display: none;
+}
+
+.cascader-menu-list::-webkit-scrollbar-thumb:vertical,
+.area-selectable-list-wrap::-webkit-scrollbar-thumb:vertical {
+  background-color: #b8b8b8;
+  border-radius: 4px;
+}
+
+.cascader-menu-list::-webkit-scrollbar-thumb:vertical:hover,
+.area-selectable-list-wrap::-webkit-scrollbar-thumb:vertical:hover {
+  background-color: #777;
+}

+ 15 - 0
src/assets/less/TableExpand.less

@@ -0,0 +1,15 @@
+/** [表格主题样式一] 表格强制列不换行 */
+.j-table-force-nowrap {
+  td, th {
+    white-space: nowrap;
+  }
+
+  .ant-table-selection-column {
+    padding: 12px 22px !important;
+  }
+
+  /** 列自适应,弊端会导致列宽失效 */
+  &.ant-table-wrapper .ant-table-content {
+    overflow-x: auto;
+  }
+}

+ 96 - 0
src/assets/less/common.less

@@ -0,0 +1,96 @@
+/*列表上方操作按钮区域*/
+.ant-card-body .table-operator {
+  margin-bottom: 0px;
+}
+/** Button按钮间距 */
+.table-operator .ant-btn {
+  margin: 0 8px 8px 0;
+}
+.table-operator .ant-btn-group .ant-btn {
+  margin: 0;
+}
+.table-operator .ant-btn-group .ant-btn:last-child {
+  margin: 0 8px 8px 0;
+}
+/*列表td的padding设置 可以控制列表大小*/
+.ant-table-tbody .ant-table-row td {
+  padding-top: 15px;
+  padding-bottom: 15px;
+}
+.depot-mask {
+  margin-top: 93px;
+  margin-left: 154px;
+}
+/*列表页面弹出modal*/
+.ant-modal-cust-warp {
+  height: 100%
+}
+/*弹出modal Y轴滚动条*/
+.ant-modal-cust-warp .ant-modal-body {
+  padding: 24px 24px 12px 24px;
+  height: calc(100% - 110px) !important;
+  overflow-y: auto
+}
+/*弹出modal 先有content后有body 故滚动条控制在body上*/
+.ant-modal-cust-warp .ant-modal-content {
+  height: 90%;
+  overflow-y: hidden
+}
+/*文本框样式*/
+.ant-modal-cust-warp .ant-form-item {
+  margin-bottom: 12px;
+}
+/*全屏模式*/
+.ant-modal-cust-warp .fullscreen >.ant-modal-content {
+  height: 100vh;
+  border-radius: 0;
+}
+/*全屏模式*/
+.ant-modal-cust-warp .fullscreen >.ant-modal-content >.ant-modal-body {
+  padding: 24px 24px 12px 24px;
+  height: calc(100% - 200px) !important;
+  overflow-y: auto
+}
+/*列表中有图片的加这个样式 参考用户管理*/
+.anty-img-wrap {
+  height: 25px;
+  position: relative;
+}
+.anty-img-wrap > img {
+  max-height: 100%;
+}
+/*列表中范围查询样式*/
+.query-group-cust{width: calc(50% - 10px)}
+.query-group-split-cust:before{content:"~";width: 20px;display: inline-block;text-align: center}
+/*erp风格子表外框padding设置*/
+.ant-card-wider-padding.cust-erp-sub-tab>.ant-card-body{padding:5px 12px}
+/* 内嵌子表背景颜色 */
+.j-inner-table-wrapper /deep/ .ant-table-expanded-row .ant-table-wrapper .ant-table-tbody .ant-table-row {
+  background-color: #FFFFFF;
+}
+
+.ant-modal-mask {
+  background-color: rgba(0, 0, 0, 0.1) !important;
+}
+/* 拖拽 */
+.table-draggable-handle {
+  /* width: 10px !important; */
+  height: 100% !important;
+  left: auto !important;
+  right: -5px;
+  cursor: col-resize;
+  touch-action: none;
+  border: none;
+  position: absolute;
+  transform: none !important;
+  bottom: 0;
+}
+.resize-table-th {
+  position: relative;
+}
+/* 单据下拉框-按钮 */
+.dropdown-btn {
+  float:left;
+  padding: 4px 8px;
+  cursor: pointer;
+}

+ 28 - 0
src/assets/less/index.less

@@ -0,0 +1,28 @@
+/**
+ * 列表查询通用样式,移动端自适应
+ */
+.search{
+  margin-bottom: 54px;
+}
+.fold{
+  width: calc(100% - 216px);
+  display: inline-block
+}
+.operator{
+  margin-bottom: 18px;
+}
+@media screen and (max-width: 900px) {
+  .fold {
+    width: 100%;
+  }
+}
+.operator button {
+  margin-right: 5px;
+}
+i {
+  cursor: pointer;
+}
+.trcolor{
+  background-color: rgba(255, 192, 203, 0.31);
+  color:red;
+}

+ 40 - 0
src/assets/light.svg

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
+    <title>Group 5</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
+            <feMerge>
+                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
+                <feMergeNode in="SourceGraphic"></feMergeNode>
+            </feMerge>
+        </filter>
+        <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
+        <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="setting-copy-2" transform="translate(-1254.000000, -136.000000)">
+            <g id="Group-8" transform="translate(1167.000000, 0.000000)">
+                <g id="Group-5" filter="url(#filter-1)" transform="translate(89.000000, 137.000000)">
+                    <mask id="mask-3" fill="white">
+                        <use xlink:href="#path-2"></use>
+                    </mask>
+                    <g id="Rectangle-18">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
+                        <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
+                    </g>
+                    <rect id="Rectangle-18" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="16" height="44"></rect>
+                    <rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="-1" y="0" width="49" height="10"></rect>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 46 - 0
src/components/AvatarList/Item.vue

@@ -0,0 +1,46 @@
+<template>
+  <tooltip v-if="tips !== ''">
+    <template slot="title">{{ tips }}</template>
+    <avatar :size="avatarSize" :src="src" />
+  </tooltip>
+  <avatar v-else :size="avatarSize" :src="src" />
+</template>
+
+<script>
+  import Avatar from 'ant-design-vue/es/avatar'
+  import Tooltip from 'ant-design-vue/es/tooltip'
+
+  export default {
+    name: "AvatarItem",
+    components: {
+      Avatar,
+      Tooltip
+    },
+    props: {
+      tips: {
+        type: String,
+        default: '',
+        required: false
+      },
+      src: {
+        type: String,
+        default: ''
+      }
+    },
+    data () {
+      return {
+        size: this.$parent.size
+      }
+    },
+    computed: {
+      avatarSize () {
+        return this.size !== 'mini' && this.size || 20
+      }
+    },
+    watch: {
+      '$parent.size' (val) {
+        this.size = val
+      }
+    }
+  }
+</script>

+ 91 - 0
src/components/AvatarList/List.vue

@@ -0,0 +1,91 @@
+<!--
+<template>
+  <div :class="[prefixCls]">
+    <ul>
+      <slot></slot>
+      <template v-for="item in filterEmpty($slots.default).slice(0, 3)"></template>
+
+
+      <template v-if="maxLength > 0 && filterEmpty($slots.default).length > maxLength">
+        <avatar-item :size="size">
+          <avatar :size="size !== 'mini' && size || 20" :style="excessItemsStyle">{{ `+${maxLength}` }}</avatar>
+        </avatar-item>
+      </template>
+    </ul>
+  </div>
+</template>
+-->
+
+<script>
+  import Avatar from 'ant-design-vue/es/avatar'
+  import AvatarItem from './Item'
+
+  export default {
+    AvatarItem,
+    name: "AvatarList",
+    components: {
+      Avatar,
+      AvatarItem
+    },
+    props: {
+      prefixCls: {
+        type: String,
+        default: 'ant-pro-avatar-list'
+      },
+      /**
+       * 头像大小 类型: large、small 、mini, default
+       * 默认值: default
+       */
+      size: {
+        type: [String, Number],
+        default: 'default'
+      },
+      /**
+       * 要显示的最大项目
+       */
+      maxLength: {
+        type: Number,
+        default: 0
+      },
+      /**
+       * 多余的项目风格
+       */
+      excessItemsStyle: {
+        type: Object,
+        default: () => {
+          return {
+            color: '#f56a00',
+            backgroundColor: '#fde3cf'
+          }
+        }
+      }
+    },
+    data () {
+      return {}
+    },
+    methods: {
+      getItems(items) {
+        const classString = {
+          [`${this.prefixCls}-item`]: true,
+          [`${this.size}`]: true
+        }
+
+        if (this.maxLength > 0) {
+          items = items.slice(0, this.maxLength)
+          items.push((<Avatar size={ this.size } style={ this.excessItemsStyle }>{`+${this.maxLength}`}</Avatar>))
+        }
+        const itemList = items.map((item) => (
+          <li class={ classString }>{ item }</li>
+        ))
+        return itemList
+      }
+    },
+    render () {
+      const { prefixCls, size } = this.$props
+      const classString = {
+        [`${prefixCls}`]: true,
+        [`${size}`]: true,
+      }
+    }
+  }
+</script>

+ 4 - 0
src/components/AvatarList/index.js

@@ -0,0 +1,4 @@
+import AvatarList from './List'
+import "./index.less"
+
+export default AvatarList

+ 60 - 0
src/components/AvatarList/index.less

@@ -0,0 +1,60 @@
+@import "../index";
+
+@avatar-list-prefix-cls: ~"@{ant-pro-prefix}-avatar-list";
+@avatar-list-item-prefix-cls: ~"@{ant-pro-prefix}-avatar-list-item";
+
+.@{avatar-list-prefix-cls} {
+  display: inline-block;
+
+  ul {
+    list-style: none;
+    display: inline-block;
+    padding: 0;
+    margin: 0 0 0 8px;
+    font-size: 0;
+  }
+}
+
+.@{avatar-list-item-prefix-cls} {
+  display: inline-block;
+  font-size: @font-size-base;
+  margin-left: -8px;
+  width: @avatar-size-base;
+  height: @avatar-size-base;
+
+  :global {
+    .ant-avatar {
+      border: 1px solid #fff;
+      cursor: pointer;
+    }
+  }
+
+  &.large {
+    width: @avatar-size-lg;
+    height: @avatar-size-lg;
+  }
+
+  &.small {
+    width: @avatar-size-sm;
+    height: @avatar-size-sm;
+  }
+
+  &.mini {
+    width: 20px;
+    height: 20px;
+
+    :global {
+      .ant-avatar {
+        width: 20px;
+        height: 20px;
+        line-height: 20px;
+
+        .ant-avatar-string {
+          font-size: 12px;
+          line-height: 18px;
+        }
+      }
+    }
+  }
+}
+

+ 105 - 0
src/components/ChartCard.vue

@@ -0,0 +1,105 @@
+<template>
+  <a-card :loading="loading" :body-style="{ padding: '20px 24px 8px' }" :bordered="false">
+    <div class="chart-card-header">
+      <div class="meta">
+        <span class="chart-card-title">{{ title }}</span>
+        <span class="chart-card-action">
+          <slot name="action"></slot>
+        </span>
+      </div>
+    </div>
+    <div class="chart-card-content">
+      <div class="content-fix">
+        <slot></slot>
+      </div>
+    </div>
+  </a-card>
+</template>
+
+<script>
+  export default {
+    name: "ChartCard",
+    props: {
+      title: {
+        type: String,
+        default: ''
+      },
+      total: {
+        type: String,
+        default: ''
+      },
+      loading: {
+        type: Boolean,
+        default: false
+      }
+    }
+  }
+</script>
+
+<style lang="less" scoped>
+  .chart-card-header {
+    position: relative;
+    overflow: hidden;
+    width: 100%;
+
+    .meta {
+      position: relative;
+      overflow: hidden;
+      width: 100%;
+      color: rgba(0, 0, 0, .45);
+      font-size: 14px;
+      line-height: 22px;
+    }
+  }
+
+  .chart-card-action {
+    cursor: pointer;
+    position: absolute;
+    top: 0;
+    right: 0;
+  }
+
+  .chart-card-footer {
+    border-top: 1px solid #e8e8e8;
+    padding-top: 9px;
+    margin-top: 8px;
+
+    > * {
+      position: relative;
+    }
+
+    .field {
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      margin: 0;
+    }
+  }
+
+  .chart-card-content {
+    margin-bottom: 12px;
+    position: relative;
+    height: 46px;
+    width: 100%;
+
+    .content-fix {
+      position: absolute;
+      left: 0;
+      bottom: 0;
+      width: 100%;
+    }
+  }
+
+  .total {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    word-break: break-all;
+    white-space: nowrap;
+    color: #000;
+    margin-top: 4px;
+    margin-bottom: 0;
+    font-size: 30px;
+    line-height: 38px;
+    height: 38px;
+  }
+</style>

+ 103 - 0
src/components/CountDown/CountDown.vue

@@ -0,0 +1,103 @@
+<template>
+  <span>
+    {{ lastTime | format }}
+  </span>
+</template>
+
+<script>
+
+  function fixedZero(val) {
+    return val * 1 < 10 ? `0${val}` : val;
+  }
+
+  export default {
+    name: "CountDown",
+    props: {
+      format: {
+        type: Function,
+        default: undefined
+      },
+      target: {
+        type: [Date, Number],
+        required: true,
+      },
+      onEnd: {
+        type: Function,
+        default: () => {
+        }
+      }
+    },
+    data() {
+      return {
+        dateTime: '0',
+        originTargetTime: 0,
+        lastTime: 0,
+        timer: 0,
+        interval: 1000
+      }
+    },
+    filters: {
+      format(time) {
+        const hours = 60 * 60 * 1000;
+        const minutes = 60 * 1000;
+
+        const h = Math.floor(time / hours);
+        const m = Math.floor((time - h * hours) / minutes);
+        const s = Math.floor((time - h * hours - m * minutes) / 1000);
+        return `${fixedZero(h)}:${fixedZero(m)}:${fixedZero(s)}`
+      }
+    },
+    created() {
+      this.initTime()
+      this.tick()
+    },
+    methods: {
+      initTime() {
+        let lastTime = 0;
+        let targetTime = 0;
+        this.originTargetTime = this.target
+        try {
+          if (Object.prototype.toString.call(this.target) === '[object Date]') {
+            targetTime = this.target
+          } else {
+            targetTime = new Date(this.target).getTime()
+          }
+        } catch (e) {
+          throw new Error('invalid target prop')
+        }
+
+        lastTime = targetTime - new Date().getTime();
+
+        this.lastTime = lastTime < 0 ? 0 : lastTime
+      },
+      tick() {
+        const {onEnd} = this
+
+        this.timer = setTimeout(() => {
+          if (this.lastTime < this.interval) {
+            clearTimeout(this.timer)
+            this.lastTime = 0
+            if (typeof onEnd === 'function') {
+              onEnd();
+            }
+          } else {
+            this.lastTime -= this.interval
+            this.tick()
+          }
+        }, this.interval)
+      }
+    },
+    beforeUpdate () {
+      if (this.originTargetTime !== this.target) {
+        this.initTime()
+      }
+    },
+    beforeDestroy() {
+      clearTimeout(this.timer)
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 3 - 0
src/components/CountDown/index.js

@@ -0,0 +1,3 @@
+import CountDown from './CountDown'
+
+export default CountDown

+ 36 - 0
src/components/Ellipsis/Ellipsis.vue

@@ -0,0 +1,36 @@
+<script>
+  export default {
+    name: 'Ellipsis',
+    props: {
+      prefixCls: {
+        type: String,
+        default: 'ant-pro-ellipsis'
+      },
+      tooltip: {
+        type: Boolean,
+        default: true,
+      },
+      length: {
+        type: Number,
+        default: 25,
+      },
+      lines: {
+        type: Number,
+        default: 1
+      },
+      fullWidthRecognition: {
+        type: Boolean,
+        default: false
+      }
+    },
+    methods: {},
+    render() {
+      const { tooltip, length } = this.$props
+      let text = ''
+      // 处理没有default插槽时的特殊情况
+      if (this.$slots.default) {
+        text = this.$slots.default.map(vNode => vNode.text).join('')
+      }
+    }
+  }
+</script>

+ 3 - 0
src/components/Ellipsis/index.js

@@ -0,0 +1,3 @@
+import Ellipsis from './Ellipsis'
+
+export default Ellipsis

+ 54 - 0
src/components/NumberInfo/NumberInfo.vue

@@ -0,0 +1,54 @@
+<template>
+  <div :class="[prefixCls]">
+    <slot name="subtitle">
+      <div :class="[`${prefixCls}-subtitle`]">{{ typeof subTitle === 'string' ? subTitle : subTitle() }}</div>
+    </slot>
+    <div class="number-info-value">
+      <span>{{ total }}</span>
+      <span class="sub-total">
+        {{ subTotal }}
+        <icon :type="`caret-${status}`" />
+      </span>
+    </div>
+  </div>
+</template>
+
+<script>
+  import Icon from 'ant-design-vue/es/icon'
+
+  export default {
+    name: 'NumberInfo',
+    props: {
+      prefixCls: {
+        type: String,
+        default: 'ant-pro-number-info'
+      },
+      total: {
+        type: Number,
+        required: true
+      },
+      subTotal: {
+        type: Number,
+        required: true
+      },
+      subTitle: {
+        type: [String, Function],
+        default: ''
+      },
+      status: {
+        type: String,
+        default: 'up'
+      }
+    },
+    components: {
+      Icon
+    },
+    data () {
+      return {}
+    }
+  }
+</script>
+
+<style lang="less" scoped>
+  /*@import "index";*/
+</style>

+ 3 - 0
src/components/NumberInfo/index.js

@@ -0,0 +1,3 @@
+import NumberInfo from './NumberInfo'
+
+export default NumberInfo

+ 55 - 0
src/components/NumberInfo/index.less

@@ -0,0 +1,55 @@
+@import "../index";
+
+@numberInfo-prefix-cls: ~"@{ant-pro-prefix}-number-info";
+
+.@{numberInfo-prefix-cls} {
+
+  .ant-pro-number-info-subtitle {
+    color: @text-color-secondary;
+    font-size: @font-size-base;
+    height: 22px;
+    line-height: 22px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    word-break: break-all;
+    white-space: nowrap;
+  }
+
+  .number-info-value {
+    margin-top: 4px;
+    font-size: 0;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    word-break: break-all;
+    white-space: nowrap;
+
+    & > span {
+      color: @heading-color;
+      display: inline-block;
+      line-height: 32px;
+      height: 32px;
+      font-size: 24px;
+      margin-right: 32px;
+    }
+
+    .sub-total {
+      color: @text-color-secondary;
+      font-size: @font-size-lg;
+      vertical-align: top;
+      margin-right: 0;
+      i {
+        font-size: 12px;
+        transform: scale(0.82);
+        margin-left: 4px;
+      }
+      :global {
+        .anticon-caret-up {
+          color: @red-6;
+        }
+        .anticon-caret-down {
+          color: @green-6;
+        }
+      }
+    }
+  }
+}

+ 41 - 0
src/components/Trend/Trend.vue

@@ -0,0 +1,41 @@
+<template>
+  <div :class="[prefixCls, reverseColor && 'reverse-color' ]">
+    <span>
+      <slot name="term"></slot>
+      <span class="item-text">
+        <slot></slot>
+      </span>
+    </span>
+    <span :class="[flag]"><a-icon :type="`caret-${flag}`"/></span>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: "Trend",
+    props: {
+      prefixCls: {
+        type: String,
+        default: 'ant-pro-trend'
+      },
+      /**
+       * 上升下降标识:up|down
+       */
+      flag: {
+        type: String,
+        required: true
+      },
+      /**
+       * 颜色反转
+       */
+      reverseColor: {
+        type: Boolean,
+        default: false
+      }
+    }
+  }
+</script>
+
+<style lang="less" scoped>
+  @import "index";
+</style>

+ 3 - 0
src/components/Trend/index.js

@@ -0,0 +1,3 @@
+import Trend from './Trend.vue'
+
+export default Trend

+ 42 - 0
src/components/Trend/index.less

@@ -0,0 +1,42 @@
+@import "../index";
+
+@trend-prefix-cls: ~"@{ant-pro-prefix}-trend";
+
+.@{trend-prefix-cls} {
+  display: inline-block;
+  font-size: @font-size-base;
+  line-height: 22px;
+
+  .up,
+  .down {
+    margin-left: 4px;
+    position: relative;
+    top: 1px;
+
+    i {
+      font-size: 12px;
+      transform: scale(0.83);
+    }
+  }
+
+  .item-text {
+    display: inline-block;
+    margin-left: 8px;
+    color: rgba(0,0,0,.85);
+  }
+
+  .up {
+    color: @red-6;
+  }
+  .down {
+    color: @green-6;
+    top: -1px;
+  }
+
+  &.reverse-color .up {
+    color: @green-6;
+  }
+  &.reverse-color .down {
+    color: @red-6;
+  }
+}

+ 88 - 0
src/components/chart/AreaChartTy.vue

@@ -0,0 +1,88 @@
+<template>
+  <div :style="{ padding: '0' }">
+    <h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
+
+    <v-chart ref="chart" :forceFit="true" :height="height" :data="dataSource" :scale="scale">
+      <v-tooltip :shared="false"/>
+      <v-axis/>
+      <v-line position="x*y" :size="lineSize" :color="lineColor"/>
+      <v-area position="x*y" :color="color"/>
+    </v-chart>
+
+  </div>
+</template>
+
+<script>
+  import { triggerWindowResizeEvent } from '@/utils/util'
+
+  export default {
+    name: 'AreaChartTy',
+    props: {
+      // 图表数据
+      dataSource: {
+        type: Array,
+        required: true
+      },
+      // 图表标题
+      title: {
+        type: String,
+        default: ''
+      },
+      // x 轴别名
+      x: {
+        type: String,
+        default: 'x'
+      },
+      // y 轴别名
+      y: {
+        type: String,
+        default: 'y'
+      },
+      // Y轴最小值
+      min: {
+        type: Number,
+        default: 0
+      },
+      // Y轴最大值
+      max: {
+        type: Number,
+        default: null
+      },
+      // 图表高度
+      height: {
+        type: Number,
+        default: 254
+      },
+      // 线的粗细
+      lineSize: {
+        type: Number,
+        default: 2
+      },
+      // 面积的颜色
+      color: {
+        type: String,
+        default: ''
+      },
+      // 线的颜色
+      lineColor: {
+        type: String,
+        default: ''
+      }
+    },
+    computed: {
+      scale() {
+        return [
+          { dataKey: 'x', title: this.x, alias: this.x },
+          { dataKey: 'y', title: this.y, alias: this.y, min: this.min, max: this.max }
+        ]
+      }
+    },
+    mounted() {
+      triggerWindowResizeEvent()
+    }
+  }
+</script>
+
+<style lang="less" scoped>
+  @import "chart";
+</style>

+ 55 - 0
src/components/chart/Bar.vue

@@ -0,0 +1,55 @@
+<template>
+  <div :style="{ padding: '0 0 32px 32px' }">
+    <h3 :style="{ marginBottom: '20px' }">{{ title }}</h3>
+    <v-chart :forceFit="true" :height="height" :data="dataSource" :scale="scale" :padding="padding">
+      <v-tooltip/>
+      <v-axis/>
+      <v-bar position="x*y" :color="color"/>
+    </v-chart>
+  </div>
+</template>
+
+<script>
+  import { triggerWindowResizeEvent } from '@/utils/util'
+  import { DEFAULT_COLOR } from "@/store/mutation-types"
+  import Vue from 'vue'
+
+  export default {
+    name: 'Bar',
+    props: {
+      dataSource: {
+        type: Array,
+        required: true
+      },
+      yaxisText: {
+        type: String,
+        default: 'y'
+      },
+      title: {
+        type: String,
+        default: ''
+      },
+      height: {
+        type: Number,
+        default: 254
+      }
+    },
+    data() {
+      return {
+        padding: ['auto', 'auto', '40', '50'],
+        color: Vue.ls.get(DEFAULT_COLOR)
+      }
+    },
+    computed: {
+      scale() {
+        return [{
+          dataKey: 'y',
+          alias: this.yaxisText
+        }]
+      }
+    },
+    mounted() {
+      triggerWindowResizeEvent()
+    }
+  }
+</script>

+ 60 - 0
src/components/chart/BarAndLine.vue

@@ -0,0 +1,60 @@
+<template>
+  <div :style="{ padding: '0 50px 32px 0' }">
+    <h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
+    <v-chart :forceFit="true" :height="height" :data="data" :scale="scale" :padding=" padding" :onClick="handleClick">
+      <v-tooltip/>
+      <v-legend/>
+      <v-axis/>
+      <v-bar position="type*bar"/>
+      <v-line position="type*line" color="#2fc25b" :size="3"/>
+    </v-chart>
+  </div>
+</template>
+
+<script>
+  import { ChartEventMixins } from './mixins/ChartMixins'
+
+  export default {
+    name: 'BarAndLine',
+    mixins: [ChartEventMixins],
+    props: {
+      title: {
+        type: String,
+        default: ''
+      },
+      dataSource: {
+        type: Array,
+        default: () => [
+          { type: '10:10', bar: 200, line: 1000 },
+          { type: '10:15', bar: 600, line: 1000},
+          { type: '10:20', bar: 200, line: 1000},
+          { type: '10:25', bar: 900, line: 1000},
+          { type: '10:30', bar: 200, line: 1000},
+          { type: '10:35', bar: 200, line: 1000},
+          { type: '10:40', bar: 100, line: 1000}
+        ]
+      },
+      height: {
+        type: Number,
+        default: 400
+      }
+    },
+    data() {
+      return {
+        padding: { top:50, right:50, bottom:100, left:50 },
+        scale: [{
+          dataKey: 'bar',
+          min: 0
+        }, {
+          dataKey: 'line',
+          min: 0
+        }]
+      }
+    },
+    computed: {
+      data() {
+        return this.dataSource
+      }
+    }
+  }
+</script>

+ 88 - 0
src/components/chart/BarMultid.vue

@@ -0,0 +1,88 @@
+<template>
+  <div :style="{ padding: '0 0 32px 32px' }">
+    <h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
+    <v-chart :data="data" :height="height" :force-fit="true" :onClick="handleClick">
+      <v-tooltip/>
+      <v-axis/>
+      <v-legend/>
+      <v-bar position="x*y" color="type" :adjust="adjust"/>
+    </v-chart>
+  </div>
+</template>
+
+<script>
+  import { DataSet } from '@antv/data-set'
+  import { ChartEventMixins } from './mixins/ChartMixins'
+
+  export default {
+    name: 'BarMultid',
+    mixins: [ChartEventMixins],
+    props: {
+      title: {
+        type: String,
+        default: ''
+      },
+      dataSource: {
+        type: Array,
+        default: () => [
+          { type: 'Jeecg', 'Jan.': 18.9, 'Feb.': 28.8, 'Mar.': 39.3, 'Apr.': 81.4, 'May': 47, 'Jun.': 20.3, 'Jul.': 24, 'Aug.': 35.6 },
+          { type: 'Jeebt', 'Jan.': 12.4, 'Feb.': 23.2, 'Mar.': 34.5, 'Apr.': 99.7, 'May': 52.6, 'Jun.': 35.5, 'Jul.': 37.4, 'Aug.': 42.4 }
+        ]
+      },
+      fields: {
+        type: Array,
+        default: () => ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.']
+      },
+      // 别名,需要的格式:[{field:'name',alias:'姓名'}, {field:'sex',alias:'性别'}]
+      aliases: {
+        type: Array,
+        default: () => []
+      },
+      height: {
+        type: Number,
+        default: 254
+      }
+    },
+    data() {
+      return {
+        adjust: [{
+          type: 'dodge',
+          marginRatio: 1 / 32
+        }]
+      }
+    },
+    computed: {
+      data() {
+        const dv = new DataSet.View().source(this.dataSource)
+        dv.transform({
+          type: 'fold',
+          fields: this.fields,
+          key: 'x',
+          value: 'y'
+        })
+
+        // bar 使用不了 - 和 / 所以替换下
+        let rows = dv.rows.map(row => {
+          if (typeof row.x === 'string') {
+            row.x = row.x.replace(/[-/]/g, '_')
+          }
+          return row
+        })
+        // 替换别名
+        rows.forEach(row => {
+          for (let item of this.aliases) {
+            if (item.field === row.type) {
+              row.type = item.alias
+              break
+            }
+          }
+        })
+        return rows
+      }
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 187 - 0
src/components/chart/DashChartDemo.vue

@@ -0,0 +1,187 @@
+<template>
+  <div :style="{ padding: '0 0 32px 32px' }">
+    <v-chart :forceFit="true" :height="300" :data="chartData" :scale="scale">
+      <v-coord type="polar" :startAngle="-202.5" :endAngle="22.5" :radius="0.75"></v-coord>
+      <v-axis
+        dataKey="value"
+        :zIndex="2"
+        :line="null"
+        :label="axisLabel"
+        :subTickCount="4"
+        :subTickLine="axisSubTickLine"
+        :tickLine="axisTickLine"
+        :grid="null"
+      ></v-axis>
+      <v-axis dataKey="1" :show="false"></v-axis>
+      <v-series
+        gemo="point"
+        position="value*1"
+        shape="pointer"
+        color="#1890FF"
+        :active="false"
+      ></v-series>
+      <v-guide
+        type="arc"
+        :zIndex="0"
+        :top="false"
+        :start="arcGuide1Start"
+        :end="arcGuide1End"
+        :vStyle="arcGuide1Style"
+      ></v-guide>
+      <v-guide
+        type="arc"
+        :zIndex="1"
+        :start="arcGuide2Start"
+        :end="getArcGuide2End"
+        :vStyle="arcGuide2Style"
+      ></v-guide>
+      <v-guide
+        type="html"
+        :position="htmlGuidePosition"
+        :html="getHtmlGuideHtml()"
+      ></v-guide>
+    </v-chart>
+  </div>
+</template>
+
+<script>
+  import { registerShape } from 'viser-vue';
+
+  registerShape('point', 'pointer', {
+    draw(cfg, container) {
+      let point = cfg.points[0];
+      point = this.parsePoint(point);
+      const center = this.parsePoint({
+        x: 0,
+        y: 0,
+      });
+      container.addShape('line', {
+        attrs: {
+          x1: center.x,
+          y1: center.y,
+          x2: point.x,
+          y2: point.y + 15,
+          stroke: cfg.color,
+          lineWidth: 5,
+          lineCap: 'round',
+        }
+      });
+      return container.addShape('circle', {
+        attrs: {
+          x: center.x,
+          y: center.y,
+          r: 9.75,
+          stroke: cfg.color,
+          lineWidth: 4.5,
+          fill: '#fff',
+        }
+      });
+    }
+  });
+
+  const scale = [{
+    dataKey: 'value',
+    min: 0,
+    max: 9,
+    tickInterval: 1,
+    nice: false,
+  }];
+
+  const data = [
+    { value: 7.0 },
+  ];
+
+  export default {
+    name:"DashChartDemo",
+    props:{
+      datasource:{
+        type: Number,
+        default:7
+      },
+      title: {
+        type: String,
+        default: ''
+      }
+    },
+    created(){
+      if(!this.datasource){
+        this.chartData = data;
+      }else{
+        this.chartData = [
+          { value: this.datasource },
+        ];
+      }
+      this.getChartData()
+    },
+    watch: {
+      'datasource': function (val) {
+        this.chartData = [
+          { value: val},
+        ];
+        this.getChartData();
+      }
+    },
+    methods:{
+      getChartData(){
+        if(this.chartData && this.chartData.length>0){
+          this.abcd = this.chartData[0].value * 10
+        }else{
+          this.abcd = 70
+        }
+      },
+      getHtmlGuideHtml(){
+        return '<div style="width: 300px;text-align: center;">\n' +
+          '<p style="font-size: 14px;color: #545454;margin: 0;">'+this.title+'</p>\n' +
+          '<p style="font-size: 36px;color: #545454;margin: 0;">'+this.abcd+'%</p>\n' +
+          '</div>'
+      },
+      getArcGuide2End(){
+        return [this.chartData[0].value, 0.945]
+      }
+    },
+    data() {
+      return {
+        chartData:[],
+        height: 400,
+        scale: scale,
+        abcd:70,
+        axisLabel: {
+          offset: -16,
+          textStyle: {
+            fontSize: 18,
+            textAlign: 'center',
+            textBaseline: 'middle'
+          }
+        },
+        axisSubTickLine: {
+          length: -8,
+          stroke: '#fff',
+          strokeOpacity: 1,
+        },
+        axisTickLine: {
+          length: -17,
+          stroke: '#fff',
+          strokeOpacity: 1,
+        },
+        arcGuide1Start: [0, 0.945],
+        arcGuide1End: [9, 0.945],
+        arcGuide1Style: {
+          stroke: '#CBCBCB',
+          lineWidth: 18,
+        },
+        arcGuide2Start: [0, 0.945],
+        arcGuide2Style: {
+          stroke: '#1890FF',
+          lineWidth: 18,
+        },
+        htmlGuidePosition: ['50%', '100%'],
+        htmlGuideHtml: `
+        <div style="width: 300px;text-align: center;">
+          <p style="font-size: 14px;color: #545454;margin: 0;">${this.title}</p>
+          <p style="font-size: 36px;color: #545454;margin: 0;">${this.abcd}%</p>
+        </div>
+      `,
+      };
+    },
+  };
+</script>

+ 61 - 0
src/components/chart/IndexBar.vue

@@ -0,0 +1,61 @@
+<template>
+  <div :style="{ padding: '0 0 32px 32px' }">
+    <h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
+    <v-chart
+      height="254"
+      :data="datasource"
+      :forceFit="true"
+      :padding="['auto', 'auto', '40', '50']">
+      <v-tooltip />
+      <v-axis />
+      <v-bar position="x*y"/>
+    </v-chart>
+  </div>
+</template>
+
+<script>
+
+  const data = []
+  for (let i = 0; i < 12; i += 1) {
+    data.push({
+      x: `${i + 1}月`,
+      y: Math.floor(Math.random() * 1000) + 200
+    })
+  }
+  const tooltip = [
+    'x*y',
+    (x, y) => ({
+      name: x,
+      value: y
+    })
+  ]
+  const scale = [{
+    dataKey: 'x',
+    min: 2
+  }, {
+    dataKey: 'y',
+    title: '时间',
+    min: 1,
+    max: 22
+  }]
+
+  export default {
+    name: "Bar",
+    props: {
+      title: {
+        type: String,
+        default: ''
+      }
+    },
+    mounted(){
+      this.datasource = data
+    },
+    data () {
+      return {
+        datasource:[],
+        scale,
+        tooltip
+      }
+    }
+  }
+</script>

+ 94 - 0
src/components/chart/LineChartMultid.vue

@@ -0,0 +1,94 @@
+<template>
+  <div :style="{ padding: '0 0 32px 32px' }">
+    <h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
+    <v-chart :force-fit="true" :height="height" :data="data" :scale="scale" :onClick="handleClick">
+      <v-tooltip/>
+      <v-axis/>
+      <v-legend/>
+      <v-line position="type*y" color="x"/>
+      <v-point position="type*y" color="x" :size="4" :v-style="style" :shape="'circle'"/>
+    </v-chart>
+  </div>
+</template>
+
+<script>
+  import { DataSet } from '@antv/data-set'
+  import { ChartEventMixins } from './mixins/ChartMixins'
+
+  export default {
+    name: 'LineChartMultid',
+    mixins: [ChartEventMixins],
+    props: {
+      title: {
+        type: String,
+        default: ''
+      },
+      dataSource: {
+        type: Array,
+        default: () => [
+          { type: 'Jan', jeecg: 7.0, jeebt: 3.9 },
+          { type: 'Feb', jeecg: 6.9, jeebt: 4.2 },
+          { type: 'Mar', jeecg: 9.5, jeebt: 5.7 },
+          { type: 'Apr', jeecg: 14.5, jeebt: 8.5 },
+          { type: 'May', jeecg: 18.4, jeebt: 11.9 },
+          { type: 'Jun', jeecg: 21.5, jeebt: 15.2 },
+          { type: 'Jul', jeecg: 25.2, jeebt: 17.0 },
+          { type: 'Aug', jeecg: 26.5, jeebt: 16.6 },
+          { type: 'Sep', jeecg: 23.3, jeebt: 14.2 },
+          { type: 'Oct', jeecg: 18.3, jeebt: 10.3 },
+          { type: 'Nov', jeecg: 13.9, jeebt: 6.6 },
+          { type: 'Dec', jeecg: 9.6, jeebt: 4.8 }
+        ]
+      },
+      fields: {
+        type: Array,
+        default: () => ['jeecg', 'jeebt']
+      },
+      // 别名,需要的格式:[{field:'name',alias:'姓名'}, {field:'sex',alias:'性别'}]
+      aliases:{
+        type: Array,
+        default: () => []
+      },
+      height: {
+        type: Number,
+        default: 254
+      }
+    },
+    data() {
+      return {
+        scale: [{
+          dataKey: 'x',
+          min: 0,
+          max: 1
+        }],
+        style: { stroke: '#fff', lineWidth: 1 }
+      }
+    },
+    computed: {
+      data() {
+        const dv = new DataSet.View().source(this.dataSource)
+        dv.transform({
+          type: 'fold',
+          fields: this.fields,
+          key: 'x',
+          value: 'y'
+        })
+        let rows =  dv.rows
+        // 替换别名
+        rows.forEach(row => {
+          for (let item of this.aliases) {
+            if (item.field === row.x) {
+              row.x = item.alias
+              break
+            }
+          }
+        })
+        return rows
+      }
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 80 - 0
src/components/chart/Liquid.vue

@@ -0,0 +1,80 @@
+<template>
+  <div>
+    <v-chart
+      :forceFit="true"
+      :height="height"
+      :width="width"
+      :data="data"
+      :scale="scale"
+      :padding="0">
+      <v-tooltip/>
+      <v-interval
+        :shape="['liquid-fill-gauge']"
+        position="transfer*value"
+        color=""
+        :v-style="{
+          lineWidth: 8,
+          opacity: 0.75
+        }"
+        :tooltip="[
+          'transfer*value',
+          (transfer, value) => {
+            return {
+              name: transfer,
+              value,
+            };
+          },
+        ]"
+      ></v-interval>
+      <v-guide
+        v-for="(row, index) in data"
+        :key="index"
+        type="text"
+        :top="true"
+        :position="{
+          gender: row.transfer,
+          value: 45
+        }"
+        :content="row.value + '%'"
+        :v-style="{
+          fontSize: 100,
+          textAlign: 'center',
+          opacity: 0.75,
+        }"
+      />
+    </v-chart>
+  </div>
+</template>
+
+<script>
+
+  const sourceDataConst = [
+    { transfer: '一月', value: 813 },
+    { transfer: '二月', value: 233 },
+    { transfer: '三月', value: 561 }
+  ]
+
+  export default {
+    name: 'Liquid',
+    props: {
+      height: {
+        type: Number,
+        default: 0
+      },
+      width: {
+        type: Number,
+        default: 0
+      }
+    },
+    data() {
+      return {
+        data: sourceDataConst,
+        scale: []
+      }
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 69 - 0
src/components/chart/MiniArea.vue

@@ -0,0 +1,69 @@
+<template>
+  <div class="antv-chart-mini">
+    <div class="chart-wrapper" :style="{ height: 46 }">
+      <v-chart :force-fit="true" :height="height" :data="data" :scale="scale" :padding="[36, 0, 18, 0]">
+        <v-tooltip/>
+        <v-smooth-area position="x*y"/>
+      </v-chart>
+    </div>
+  </div>
+</template>
+
+<script>
+  import moment from 'dayjs'
+
+  const sourceData = []
+  const beginDay = new Date().getTime()
+
+  for (let i = 0; i < 10; i++) {
+    sourceData.push({
+      x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
+      y: Math.round(Math.random() * 10)
+    })
+  }
+
+  export default {
+    name: 'MiniArea',
+    props: {
+      dataSource: {
+        type: Array,
+        default: () => []
+      },
+      // x 轴别名
+      x: {
+        type: String,
+        default: 'x'
+      },
+      // y 轴别名
+      y: {
+        type: String,
+        default: 'y'
+      }
+    },
+    data() {
+      return {
+        data: [],
+        height: 100
+      }
+    },
+    computed: {
+      scale() {
+        return [
+          { dataKey: 'x', title: this.x, alias: this.x },
+          { dataKey: 'y', title: this.y, alias: this.y }
+        ]
+      }
+    },
+    created() {
+      if (this.dataSource.length === 0) {
+        this.data = sourceData
+      } else {
+        this.data = this.dataSource
+      }
+    }
+  }
+</script>
+
+<style lang="less" scoped>
+  @import "chart";
+</style>

+ 76 - 0
src/components/chart/MiniBar.vue

@@ -0,0 +1,76 @@
+<template>
+  <div :style="{'width':width==null?'auto':width+'px'}">
+    <v-chart :forceFit="width==null" :height="height" :data="data" padding="0">
+      <v-tooltip/>
+      <v-bar position="x*y"/>
+    </v-chart>
+  </div>
+</template>
+
+<script>
+  import moment from 'dayjs'
+
+  const sourceData = []
+  const beginDay = new Date().getTime()
+
+  for (let i = 0; i < 10; i++) {
+    sourceData.push({
+      x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
+      y: Math.round(Math.random() * 10)
+    })
+  }
+
+  const tooltip = [
+    'x*y',
+    (x, y) => ({
+      name: x,
+      value: y
+    })
+  ]
+
+  const scale = [{
+    dataKey: 'x',
+    min: 2
+  }, {
+    dataKey: 'y',
+    title: '时间',
+    min: 1,
+    max: 30
+  }]
+
+  export default {
+    name: 'MiniBar',
+    props: {
+      dataSource: {
+        type: Array,
+        default: () => []
+      },
+      width: {
+        type: Number,
+        default: null
+      },
+      height: {
+        type: Number,
+        default: 200
+      }
+    },
+    created() {
+      if (this.dataSource.length === 0) {
+        this.data = sourceData
+      } else {
+        this.data = this.dataSource
+      }
+    },
+    data() {
+      return {
+        tooltip,
+        data: [],
+        scale
+      }
+    }
+  }
+</script>
+
+<style lang="less" scoped>
+  @import "chart";
+</style>

+ 75 - 0
src/components/chart/MiniProgress.vue

@@ -0,0 +1,75 @@
+<template>
+  <div class="chart-mini-progress">
+    <div class="target" :style="{ left: target + '%'}">
+      <span :style="{ backgroundColor: color }"/>
+      <span :style="{ backgroundColor: color }"/>
+    </div>
+    <div class="progress-wrapper">
+      <div class="progress" :style="{ backgroundColor: color, width: percentage + '%', height: height+'px' }"></div>
+    </div>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: 'MiniProgress',
+    props: {
+      target: {
+        type: Number,
+        default: 0
+      },
+      height: {
+        type: Number,
+        default: 10
+      },
+      color: {
+        type: String,
+        default: '#13C2C2'
+      },
+      percentage: {
+        type: Number,
+        default: 0
+      }
+    }
+  }
+</script>
+
+<style lang="less" scoped>
+  .chart-mini-progress {
+    padding: 5px 0;
+    position: relative;
+    width: 100%;
+
+    .target {
+      position: absolute;
+      top: 0;
+      bottom: 0;
+
+      span {
+        border-radius: 100px;
+        position: absolute;
+        top: 0;
+        left: 0;
+        height: 4px;
+        width: 2px;
+
+        &:last-child {
+          top: auto;
+          bottom: 0;
+        }
+      }
+    }
+    .progress-wrapper {
+      background-color: #f5f5f5;
+      position: relative;
+
+      .progress {
+        transition: all .4s cubic-bezier(.08, .82, .17, 1) 0s;
+        border-radius: 1px 0 0 1px;
+        background-color: #1890ff;
+        width: 0;
+        height: 100%;
+      }
+    }
+  }
+</style>

+ 70 - 0
src/components/chart/Pie.vue

@@ -0,0 +1,70 @@
+<template>
+  <v-chart :forceFit="true" :height="height" :data="data" :scale="scale" :onClick="handleClick">
+    <v-tooltip :showTitle="false" dataKey="item*percent"/>
+    <v-axis/>
+    <v-legend dataKey="item"/>
+    <v-pie position="percent" color="item" :v-style="pieStyle" :label="labelConfig"/>
+    <v-coord type="theta"/>
+  </v-chart>
+</template>
+
+<script>
+  const DataSet = require('@antv/data-set')
+  import { ChartEventMixins } from './mixins/ChartMixins'
+
+  export default {
+    name: 'Pie',
+    mixins: [ChartEventMixins],
+    props: {
+      title: {
+        type: String,
+        default: ''
+      },
+      height: {
+        type: Number,
+        default: 254
+      },
+      dataSource: {
+        type: Array,
+        default: () => [
+          { item: '示例一', count: 40 },
+          { item: '示例二', count: 21 },
+          { item: '示例三', count: 17 },
+          { item: '示例四', count: 13 },
+          { item: '示例五', count: 9 }
+        ]
+      }
+    },
+    data() {
+      return {
+        scale: [{
+          dataKey: 'percent',
+          min: 0,
+          formatter: '.0%'
+        }],
+        pieStyle: {
+          stroke: '#fff',
+          lineWidth: 1
+        },
+        labelConfig: ['percent', {
+          formatter: (val, item) => {
+            return item.point.item + ': ' + val
+          }
+        }]
+      }
+    },
+    computed: {
+      data() {
+        let dv = new DataSet.View().source(this.dataSource)
+        // 计算数据百分比
+        dv.transform({
+          type: 'percent',
+          field: 'count',
+          dimension: 'item',
+          as: 'percent'
+        })
+        return dv.rows
+      }
+    }
+  }
+</script>

+ 90 - 0
src/components/chart/Radar.vue

@@ -0,0 +1,90 @@
+<template>
+  <v-chart :forceFit="true" :height="height" :data="data" :padding="[20, 20, 95, 20]" :scale="scale">
+    <v-tooltip></v-tooltip>
+    <v-axis :dataKey="axis1Opts.dataKey" :line="axis1Opts.line" :tickLine="axis1Opts.tickLine" :grid="axis1Opts.grid"/>
+    <v-axis :dataKey="axis2Opts.dataKey" :line="axis2Opts.line" :tickLine="axis2Opts.tickLine" :grid="axis2Opts.grid"/>
+    <v-legend dataKey="user" marker="circle" :offset="30"/>
+    <v-coord type="polar" radius="0.8"/>
+    <v-line position="item*score" color="user" :size="2"/>
+    <v-point position="item*score" color="user" :size="4" shape="circle"/>
+  </v-chart>
+</template>
+
+<script>
+  const axis1Opts = {
+    dataKey: 'item',
+    line: null,
+    tickLine: null,
+    grid: {
+      lineStyle: {
+        lineDash: null
+      },
+      hideFirstLine: false
+    }
+  }
+  const axis2Opts = {
+    dataKey: 'score',
+    line: null,
+    tickLine: null,
+    grid: {
+      type: 'polygon',
+      lineStyle: {
+        lineDash: null
+      }
+    }
+  }
+
+  const scale = [
+    {
+      dataKey: 'score',
+      min: 0,
+      max: 100
+    }, {
+      dataKey: 'user',
+      alias: '类型'
+    }
+  ]
+
+  const sourceData = [
+    { item: '示例一', score: 40 },
+    { item: '示例二', score: 20 },
+    { item: '示例三', score: 67 },
+    { item: '示例四', score: 43 },
+    { item: '示例五', score: 90 }
+  ]
+
+  export default {
+    name: 'Radar',
+    props: {
+      height: {
+        type: Number,
+        default: 254
+      },
+      dataSource: {
+        type: Array,
+        default: () => []
+      }
+    },
+    data() {
+      return {
+        axis1Opts,
+        axis2Opts,
+        scale,
+        data: sourceData
+      }
+    },
+    watch: {
+      dataSource(newVal) {
+        if (newVal.length === 0) {
+          this.data = sourceData
+        } else {
+          this.data = newVal
+        }
+      }
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 81 - 0
src/components/chart/RankList.vue

@@ -0,0 +1,81 @@
+<template>
+  <div class="rank">
+    <h4 class="title">{{ title }}</h4>
+    <ul class="list" :style="{height:height?`${height}px`:'auto',overflow:'auto'}">
+      <li :key="index" v-for="(item, index) in list">
+        <span :class="index < 3 ? 'active' : null">{{ index + 1 }}</span>
+        <span>{{ item.name }}</span>
+        <span>{{ item.total }}</span>
+      </li>
+    </ul>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: "RankList",
+    // ['title', 'list']
+    props: {
+      title: {
+        type: String,
+        default: ''
+      },
+      list: {
+        type: Array,
+        default: null
+      },
+      height: {
+        type: Number,
+        default: null
+      }
+    }
+  }
+</script>
+
+<style lang="less" scoped>
+
+  .rank {
+    padding: 0 32px 32px 72px;
+
+    .list {
+      margin: 25px 0 0;
+      padding: 0;
+      list-style: none;
+
+      li {
+        margin-top: 16px;
+
+        span {
+          color: rgba(0, 0, 0, .65);
+          font-size: 14px;
+          line-height: 22px;
+
+          &:first-child {
+            background-color: #f5f5f5;
+            border-radius: 20px;
+            display: inline-block;
+            font-size: 12px;
+            font-weight: 600;
+            margin-right: 24px;
+            height: 20px;
+            line-height: 20px;
+            width: 20px;
+            text-align: center;
+          }
+          &.active {
+            background-color: #314659;
+            color: #fff;
+          }
+          &:last-child {
+            float: right;
+          }
+        }
+      }
+    }
+  }
+
+  .mobile .rank {
+    padding: 0 32px 32px 32px;
+  }
+
+</style>

+ 54 - 0
src/components/chart/StackBar.vue

@@ -0,0 +1,54 @@
+<template>
+  <div>
+    <v-chart :forceFit="true" :height="height" :data="data">
+      <v-coord type="rect" direction="LB" />
+      <v-tooltip />
+      <v-legend />
+      <v-axis dataKey="State" :label="label" />
+      <v-stack-bar position="State*流程数量"  color="流程状态" />
+    </v-chart>
+  </div>
+
+</template>
+
+<script>
+  const DataSet = require('@antv/data-set');
+
+  export default {
+    name: 'StackBar',
+    props: {
+      dataSource: {
+        type: Array,
+        required: true,
+        default: () => [
+          { 'State': '请假', '流转中': 25, '已归档': 18 },
+          { 'State': '出差', '流转中': 30, '已归档': 20 },
+          { 'State': '加班', '流转中': 38, '已归档': 42},
+          { 'State': '用车', '流转中': 51, '已归档': 67}
+        ]
+      },
+      height: {
+        type: Number,
+        default: 254
+      }
+    },
+    data() {
+      return {
+        label: { offset: 12 }
+      }
+    },
+    computed: {
+      data() {
+        const dv = new DataSet.View().source(this.dataSource);
+        dv.transform({
+          type: 'fold',
+          fields: ['流转中', '已归档'],
+          key: '流程状态',
+          value: '流程数量',
+          retains: ['State'],
+        });
+       return dv.rows;
+      }
+    }
+  }
+</script>

+ 66 - 0
src/components/chart/TransferBar.vue

@@ -0,0 +1,66 @@
+<template>
+  <div :style="{ padding: '0 0 32px 32px' }">
+    <h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
+    <v-chart
+      :height="height"
+      :data="data"
+      :scale="scale"
+      :forceFit="true"
+      :padding="['auto', 'auto', '40', '50']">
+      <v-tooltip/>
+      <v-axis/>
+      <v-bar position="x*y"/>
+    </v-chart>
+  </div>
+</template>
+
+<script>
+
+  export default {
+    name: 'Bar',
+    props: {
+      title: {
+        type: String,
+        default: ''
+      },
+      x: {
+        type: String,
+        default: 'x'
+      },
+      y: {
+        type: String,
+        default: 'y'
+      },
+      data: {
+        type: Array,
+        default: () => []
+      },
+      height: {
+        type: Number,
+        default: 254
+      }
+    },
+    data() {
+      return {}
+    },
+    computed: {
+      scale() {
+        return [
+          { dataKey: 'x', title: this.x, alias: this.x },
+          { dataKey: 'y', title: this.y, alias: this.y }
+        ]
+      }
+    },
+    created() {
+      // this.getMonthBar()
+    },
+    methods: {
+      // getMonthBar() {
+      //   this.$http.get('/analysis/month-bar')
+      //     .then(res => {
+      //       this.data = res.result
+      //     })
+      // }
+    }
+  }
+</script>

+ 84 - 0
src/components/chart/Trend.vue

@@ -0,0 +1,84 @@
+<template>
+  <div class="chart-trend">
+    {{ term }}
+    <span>{{ rate }}%</span>
+    <span :class="['trend-icon', trend]"><a-icon :type="'caret-' + trend"/></span>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: "Trend",
+    props: {
+      // 同title
+      term: {
+        type: String,
+        default: '',
+        required: true
+      },
+      // 百分比
+      percentage: {
+        type: Number,
+        default: null
+      },
+      type: {
+        type: Boolean,
+        default: null
+      },
+      target: {
+        type: Number,
+        default: 0
+      },
+      value: {
+        type: Number,
+        default: 0
+      },
+      fixed: {
+        type: Number,
+        default: 2
+      }
+    },
+    data () {
+      return {
+        trend: this.type && 'up' || 'down',
+        rate: this.percentage
+      }
+    },
+    created () {
+      let type = this.type === null ? this.value >= this.target : this.type
+      this.trend = type ? 'up' : 'down';
+      this.rate = (this.percentage === null ? Math.abs(this.value - this.target) * 100 / this.target : this.percentage).toFixed(this.fixed)
+    }
+  }
+</script>
+
+<style lang="less" scoped>
+  .chart-trend {
+    display: inline-block;
+    font-size: 14px;
+    line-height: 22px;
+
+    .trend-icon {
+      font-size: 12px;
+
+      &.up, &.down {
+        margin-left: 4px;
+        position: relative;
+        top: 1px;
+
+        i {
+          font-size: 12px;
+          transform: scale(.83);
+        }
+      }
+
+      &.up {
+        color: #f5222d;
+      }
+      &.down {
+        color: #52c41a;
+        top: -1px;
+      }
+    }
+  }
+</style>

+ 13 - 0
src/components/chart/chart.less

@@ -0,0 +1,13 @@
+.antv-chart-mini {
+  position: relative;
+  width: 100%;
+
+  .chart-wrapper {
+    position: absolute;
+    bottom: -28px;
+    width: 100%;
+
+/*    margin: 0 -5px;
+    overflow: hidden;*/
+  }
+}

+ 13 - 0
src/components/chart/chart.scss

@@ -0,0 +1,13 @@
+.antv-chart-mini {
+  position: relative;
+  width: 100%;
+
+  .chart-wrapper {
+    position: absolute;
+    bottom: -28px;
+    width: 100%;
+
+/*    margin: 0 -5px;
+    overflow: hidden;*/
+  }
+}

+ 10 - 0
src/components/chart/mixins/ChartMixins.js

@@ -0,0 +1,10 @@
+export const ChartEventMixins = {
+  methods: {
+    handleClick(event, chart) {
+      this.handleEvent('click', event, chart)
+    },
+    handleEvent(eventName, event, chart) {
+      this.$emit(eventName, event, chart)
+    },
+  }
+}

+ 4 - 0
src/components/index.less

@@ -0,0 +1,4 @@
+@import "~ant-design-vue/lib/style/index";
+
+// The prefix to use on all css classes from ant-pro.
+@ant-pro-prefix             : ant-pro;

+ 155 - 0
src/components/jeecg/JAreaLinkage.vue

@@ -0,0 +1,155 @@
+<template>
+  <div v-if="!reloading" class="j-area-linkage">
+    <area-cascader
+      v-if="_type === enums.type[0]"
+      :value="innerValue"
+      :data="pcaa"
+      :level="1"
+      :style="{width}"
+      v-bind="$attrs"
+      v-on="_listeners"
+      @change="handleChange"
+    />
+    <area-select
+      v-else-if="_type === enums.type[1]"
+      :value="innerValue"
+      :data="pcaa"
+      :level="2"
+      v-bind="$attrs"
+      v-on="_listeners"
+      @change="handleChange"
+    />
+    <div v-else>
+      <span style="color:red;"> Bad type value: {{_type}}</span>
+    </div>
+  </div>
+</template>
+
+<script>
+  import { pcaa } from 'area-data'
+
+  export default {
+    name: 'JAreaLinkage',
+    props: {
+      value: {
+        type: String,
+        required:false
+      },
+      // 组件的类型,可选值:
+      // select 下拉样式
+      // cascader 级联样式(默认)
+      type: {
+        type: String,
+        default: 'cascader'
+      },
+      width: {
+        type: String,
+        default: '100%'
+      }
+    },
+    data() {
+      return {
+        pcaa,
+        innerValue: [],
+        usedListeners: ['change'],
+        enums: {
+          type: ['cascader', 'select']
+        },
+        reloading: false,
+        areaData:''
+      }
+    },
+    computed: {
+      _listeners() {
+        let listeners = { ...this.$listeners }
+        // 去掉已使用的事件,防止冲突
+        this.usedListeners.forEach(key => {
+          delete listeners[key]
+        })
+        return listeners
+      },
+      _type() {
+        if (this.enums.type.includes(this.type)) {
+          return this.type
+        } else {
+          console.error(`JAreaLinkage的type属性只能接收指定的值(${this.enums.type.join('|')})`)
+          return this.enums.type[0]
+        }
+      },
+    },
+    watch: {
+      value: {
+        immediate: true,
+        handler() {
+          this.loadDataByValue(this.value)
+        }
+      },
+    },
+    created() {
+      this.initAreaData();
+    },
+    methods: {
+      /** 通过 value 反推 options */
+      loadDataByValue(value) {
+        if(!value || value.length==0){
+          this.innerValue = []
+          this.reloading = true;
+          setTimeout(()=>{
+            this.reloading = false
+          },100)
+        }else{
+          this.initAreaData();
+          let arr = this.areaData.getRealCode(value);
+          this.innerValue = arr
+        }
+      },
+      /** 通过地区code获取子级 */
+      loadDataByCode(value) {
+        let options = []
+        let data = pcaa[value]
+        if (data) {
+          for (let key in data) {
+            if (data.hasOwnProperty(key)) {
+              options.push({ value: key, label: data[key], })
+            }
+          }
+          return options
+        } else {
+          return []
+        }
+      },
+      /** 判断是否有子节点 */
+      hasChildren(options) {
+        options.forEach(option => {
+          let data = this.loadDataByCode(option.value)
+          option.isLeaf = data.length === 0
+        })
+      },
+      handleChange(values) {
+        let value = values[values.length - 1]
+        this.$emit('change', value)
+      },
+      initAreaData(){
+        if(!this.areaData){
+          this.areaData = new Area();
+        }
+      },
+
+    },
+    model: { prop: 'value', event: 'change' },
+  }
+</script>
+
+<style lang="less" scoped>
+  .j-area-linkage {
+    height:40px;
+    /deep/ .area-cascader-wrap .area-select {
+      width: 100%;
+    }
+
+    /deep/ .area-select .area-selected-trigger {
+      line-height: 1.15;
+    }
+  }
+
+</style>

+ 238 - 0
src/components/jeecg/JCategorySelect.vue

@@ -0,0 +1,238 @@
+<template>
+  <a-tree-select
+    allowClear
+    labelInValue
+    style="width: 100%"
+    :disabled="disabled"
+    :dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
+    :placeholder="placeholder"
+    :loadData="asyncLoadTreeData"
+    :value="treeValue"
+    :treeData="treeData"
+    :multiple="multiple"
+    @change="onChange">
+  </a-tree-select>
+</template>
+<script>
+
+  import { getAction } from '@/api/manage'
+
+  export default {
+    name: 'JCategorySelect',
+    props: {
+      value:{
+        type: String,
+        required: false
+      },
+      placeholder:{
+        type: String,
+        default: '请选择',
+        required: false
+      },
+      disabled:{
+        type:Boolean,
+        default:false,
+        required:false
+      },
+      condition:{
+        type:String,
+        default:'',
+        required:false
+      },
+      // 是否支持多选
+      multiple: {
+        type: Boolean,
+        default: false,
+      },
+      loadTriggleChange:{
+        type: Boolean,
+        default: false,
+        required:false
+      },
+      pid:{
+        type:String,
+        default:'',
+        required:false
+      },
+      pcode:{
+        type:String,
+        default:'',
+        required:false
+      },
+      back:{
+        type:String,
+        default:'',
+        required:false
+      }
+    },
+    data () {
+      return {
+        treeValue:"",
+        treeData:[],
+        url:"/sys/category/loadTreeData",
+        view:'/sys/category/loadDictItem/',
+        tableName:"",
+        text:"",
+        code:"",
+
+      }
+    },
+    watch: {
+      value () {
+        this.loadItemByCode()
+      },
+      pcode(){
+        this.loadRoot();
+      }
+    },
+    created(){
+      this.validateProp().then(()=>{
+        this.loadRoot()
+        this.loadItemByCode()
+      })
+    },
+    methods: {
+      /**加载一级节点 */
+      loadRoot(){
+        let param = {
+          pid:this.pid,
+          pcode:!this.pcode?'0':this.pcode,
+          condition:this.condition
+        }
+        getAction(this.url,param).then(res=>{
+          if(res.success && res.result){
+            for(let i of res.result){
+              i.value = i.key
+              if(i.leaf==false){
+                i.isLeaf=false
+              }else if(i.leaf==true){
+                i.isLeaf=true
+              }
+            }
+            this.treeData = [...res.result]
+          }else{
+            console.log("树一级节点查询结果-else",res)
+          }
+        })
+      },
+
+      /** 数据回显*/
+      loadItemByCode(){
+        if(!this.value || this.value=="0"){
+          this.treeValue = []
+        }else{
+          getAction(this.view,{ids:this.value}).then(res=>{
+            if(res.success){
+              let values = this.value.split(',')
+              this.treeValue = res.result.map((item, index) => ({
+                key: values[index],
+                value: values[index],
+                label: item
+              }))
+              this.onLoadTriggleChange(res.result[0]);
+            }
+          })
+        }
+      },
+      onLoadTriggleChange(text){
+        //只有单选才会触发
+        if(!this.multiple && this.loadTriggleChange){
+          this.backValue(this.value,text)
+        }
+      },
+      backValue(value,label){
+        let obj = {}
+        if(this.back){
+          obj[this.back] = label
+        }
+        this.$emit('change', value, obj)
+      },
+      asyncLoadTreeData (treeNode) {
+        return new Promise((resolve) => {
+          if (treeNode.$vnode.children) {
+            resolve()
+            return
+          }
+          let pid = treeNode.$vnode.key
+          let param = {
+            pid:pid,
+            condition:this.condition
+          }
+          getAction(this.url,param).then(res=>{
+            if(res.success){
+              for(let i of res.result){
+                i.value = i.key
+                if(i.leaf==false){
+                  i.isLeaf=false
+                }else if(i.leaf==true){
+                  i.isLeaf=true
+                }
+              }
+              this.addChildren(pid,res.result,this.treeData)
+              this.treeData = [...this.treeData]
+            }
+            resolve()
+          })
+        })
+      },
+      addChildren(pid,children,treeArray){
+        if(treeArray && treeArray.length>0){
+          for(let item of treeArray){
+            if(item.key == pid){
+              if(!children || children.length==0){
+                item.isLeaf=true
+              }else{
+                item.children = children
+              }
+              break
+            }else{
+              this.addChildren(pid,children,item.children)
+            }
+          }
+        }
+      },
+
+      onChange(value){
+        if(!value){
+          this.$emit('change', '');
+          this.treeValue = ''
+        } else if (value instanceof Array) {
+          //this.$emit('change', value.map(item => item.value).join(','))
+          //this.treeValue = value
+        } else {
+          this.backValue(value.value,value.label)
+          this.treeValue = value
+        }
+      },
+      getCurrTreeData(){
+        return this.treeData
+      },
+      validateProp(){
+        let mycondition = this.condition
+        return new Promise((resolve,reject)=>{
+          if(!mycondition){
+            resolve();
+          }else{
+            try {
+              let test=JSON.parse(mycondition);
+              if(typeof test == 'object' && test){
+                resolve()
+              }else{
+                this.$message.error("组件JTreeSelect-condition传值有误,需要一个json字符串!")
+                reject()
+              }
+            } catch(e) {
+              this.$message.error("组件JTreeSelect-condition传值有误,需要一个json字符串!")
+              reject()
+            }
+          }
+        })
+      }
+    },
+    //2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型 这个牛逼
+    model: {
+      prop: 'value',
+      event: 'change'
+    }
+  }
+</script>

+ 43 - 0
src/components/jeecg/JCheckbox.vue

@@ -0,0 +1,43 @@
+<template>
+  <a-checkbox-group :options="options" :value="checkboxArray" v-bind="$attrs" @change="onChange" />
+</template>
+
+<script>
+  export default {
+    name: 'JCheckbox',
+    props: {
+      value:{
+        type: String,
+        required: false
+      },
+      /*label value*/
+      options:{
+        type: Array,
+        required: true
+      }
+    },
+    data(){
+      return {
+        checkboxArray:!this.value?[]:this.value.split(",")
+      }
+    },
+    watch:{
+      value (val) {
+        if(!val){
+          this.checkboxArray = []
+        }else{
+          this.checkboxArray = this.value.split(",")
+        }
+      }
+    },
+    methods:{
+      onChange (checkedValues) {
+        this.$emit('change', checkedValues.join(","));
+      },
+    },
+    model: {
+      prop: 'value',
+      event: 'change'
+    }
+  }
+</script>

+ 429 - 0
src/components/jeecg/JCodeEditor.vue

@@ -0,0 +1,429 @@
+<template>
+  <div v-bind="fullScreenParentProps">
+    <a-icon v-if="fullScreen" class="full-screen-icon" :type="iconType" @click="()=>fullCoder=!fullCoder"/>
+
+    <div class="code-editor-cust full-screen-child">
+      <textarea ref="textarea"></textarea>
+      <span @click="nullTipClick" class="null-tip" :class="{'null-tip-hidden':hasCode}" :style="nullTipStyle">{{ placeholderShow }}</span>
+      <template v-if="languageChange">
+        <a-select v-model="mode" size="small" class="code-mode-select" @change="changeMode" placeholder="请选择主题">
+          <a-select-option
+            v-for="mode in modes"
+            :key="mode.value"
+            :value="mode.value">
+            {{ mode.label }}
+          </a-select-option>
+        </a-select>
+      </template>
+
+    </div>
+  </div>
+</template>
+
+<script type="text/ecmascript-6">
+  // 引入全局实例
+  import _CodeMirror from 'codemirror'
+
+  // 核心样式
+  import 'codemirror/lib/codemirror.css'
+  // 引入主题后还需要在 options 中指定主题才会生效 darcula  gruvbox-dark hopscotch  monokai
+  import 'codemirror/theme/panda-syntax.css'
+  //提示css
+  import "codemirror/addon/hint/show-hint.css";
+
+  // 需要引入具体的语法高亮库才会有对应的语法高亮效果
+  // codemirror 官方其实支持通过 /addon/mode/loadmode.js 和 /mode/meta.js 来实现动态加载对应语法高亮库
+  // 但 vue 貌似没有无法在实例初始化后再动态加载对应 JS ,所以此处才把对应的 JS 提前引入
+  import 'codemirror/mode/javascript/javascript.js'
+  import 'codemirror/mode/css/css.js'
+  import 'codemirror/mode/xml/xml.js'
+  import 'codemirror/mode/clike/clike.js'
+  import 'codemirror/mode/markdown/markdown.js'
+  import 'codemirror/mode/python/python.js'
+  import 'codemirror/mode/r/r.js'
+  import 'codemirror/mode/shell/shell.js'
+  import 'codemirror/mode/sql/sql.js'
+  import 'codemirror/mode/swift/swift.js'
+  import 'codemirror/mode/vue/vue.js'
+
+  // 尝试获取全局实例
+  const CodeMirror = window.CodeMirror || _CodeMirror
+
+  export default {
+    name: 'JCodeEditor',
+    props: {
+      // 外部传入的内容,用于实现双向绑定
+      value: {
+        type: String,
+        default: ''
+      },
+      // 外部传入的语法类型
+      language: {
+        type: String,
+        default: null
+      },
+     languageChange:{
+       type: Boolean,
+       default:false,
+       required:false
+     },
+      placeholder: {
+        type: String,
+        default: null
+      },
+      // 显示行号
+      lineNumbers: {
+        type: Boolean,
+        default: true
+      },
+      // 是否显示全屏按钮
+      fullScreen: {
+        type: Boolean,
+        default: false
+      },
+      // 全屏以后的z-index
+      zIndex: {
+        type: [Number, String],
+        default: 999
+      }
+    },
+    data () {
+      return {
+        // 内部真实的内容
+        code: '',
+        iconType: 'fullscreen',
+        hasCode:false,
+        // 默认的语法类型
+        mode: 'javascript',
+        // 编辑器实例
+        coder: null,
+        // 默认配置
+        options: {
+          // 缩进格式
+          tabSize: 2,
+          // 主题,对应主题库 JS 需要提前引入
+          theme: 'panda-syntax',
+          line: true,
+         // extraKeys: {'Ctrl': 'autocomplete'},//自定义快捷键
+          hintOptions: {
+            tables: {
+              users: ['name', 'score', 'birthDate'],
+              countries: ['name', 'population', 'size']
+            }
+          },
+        },
+        // 支持切换的语法高亮类型,对应 JS 已经提前引入
+        // 使用的是 MIME-TYPE ,不过作为前缀的 text/ 在后面指定时写死了
+        modes: [{
+          value: 'css',
+          label: 'CSS'
+        }, {
+          value: 'javascript',
+          label: 'Javascript'
+        }, {
+          value: 'html',
+          label: 'XML/HTML'
+        }, {
+          value: 'x-java',
+          label: 'Java'
+        }, {
+          value: 'x-objectivec',
+          label: 'Objective-C'
+        }, {
+          value: 'x-python',
+          label: 'Python'
+        }, {
+          value: 'x-rsrc',
+          label: 'R'
+        }, {
+          value: 'x-sh',
+          label: 'Shell'
+        }, {
+          value: 'x-sql',
+          label: 'SQL'
+        }, {
+          value: 'x-swift',
+          label: 'Swift'
+        }, {
+          value: 'x-vue',
+          label: 'Vue'
+        }, {
+          value: 'markdown',
+          label: 'Markdown'
+        }],
+        // code 编辑器 是否全屏
+        fullCoder: false
+      }
+    },
+    watch: {
+      fullCoder:{
+        handler(value) {
+          if(value){
+            this.iconType="fullscreen-exit"
+          }else{
+            this.iconType="fullscreen"
+          }
+        }
+      },
+      // value: {
+      //   immediate: false,
+      //   handler(value) {
+      //     this._getCoder().then(() => {
+      //       this.coder.setValue(value)
+      //     })
+      //   }
+      // },
+      language: {
+        immediate: true,
+        handler(language) {
+          this._getCoder().then(() => {
+            // 尝试从父容器获取语法类型
+            if (language) {
+              // 获取具体的语法类型对象
+              let modeObj = this._getLanguage(language)
+
+              // 判断父容器传入的语法是否被支持
+              if (modeObj) {
+                this.mode = modeObj.label
+                this.coder.setOption('mode', `text/${modeObj.value}`)
+              }
+            }
+          })
+        }
+      }
+    },
+    computed: {
+      placeholderShow() {
+        if (this.placeholder == null) {
+          return `请在此输入${this.language}代码`
+        } else {
+          return this.placeholder
+        }
+      },
+      nullTipStyle(){
+        if (this.lineNumbers) {
+          return { left: '36px' }
+        } else {
+          return { left: '12px' }
+        }
+      },
+      // coder 配置
+      coderOptions() {
+        return {
+          tabSize: this.options.tabSize,
+          theme: this.options.theme,
+          lineNumbers: this.lineNumbers,
+          line: true,
+          hintOptions: this.options.hintOptions
+        }
+      },
+      fullScreenParentProps(){
+        let props = {
+          class: ['full-screen-parent', this.fullCoder ? 'full-screen' : ''],
+          style: {}
+        }
+        if (this.fullCoder) {
+          props.style['z-index'] = this.zIndex
+        }
+        return props
+      }
+    },
+    mounted () {
+      // 初始化
+      this._initialize()
+    },
+    methods: {
+      // 初始化
+      _initialize () {
+        // 初始化编辑器实例,传入需要被实例化的文本域对象和默认配置
+        this.coder = CodeMirror.fromTextArea(this.$refs.textarea, this.coderOptions)
+        // 编辑器赋值
+        if(this.value||this.code){
+          this.hasCode=true
+          this.coder.setValue(this.value || this.code)
+        }else{
+          this.coder.setValue('')
+          this.hasCode=false
+        }
+        // 支持双向绑定
+        this.coder.on('change', (coder) => {
+          this.code = coder.getValue()
+          if(this.code){
+            this.hasCode=true
+          }else{
+            this.hasCode=false
+          }
+          if (this.$emit) {
+            this.$emit('input', this.code)
+          }
+        })
+        this.coder.on('focus', () => {
+          this.hasCode=true
+        })
+        this.coder.on('blur', () => {
+          if(this.code){
+            this.hasCode=true
+          }else{
+            this.hasCode=false
+          }
+        })
+
+       /* this.coder.on('cursorActivity',()=>{
+          this.coder.showHint()
+        })*/
+
+      },
+      getCodeContent(){
+        return this.code
+      },
+      setCodeContent(val){
+        setTimeout(()=>{
+          if(!val){
+            this.coder.setValue('')
+          }else{
+            this.coder.setValue(val)
+          }
+        },300)
+      },
+      // 获取当前语法类型
+      _getLanguage (language) {
+        // 在支持的语法类型列表中寻找传入的语法类型
+        return this.modes.find((mode) => {
+          // 所有的值都忽略大小写,方便比较
+          let currentLanguage = language.toLowerCase()
+          let currentLabel = mode.label.toLowerCase()
+          let currentValue = mode.value.toLowerCase()
+
+          // 由于真实值可能不规范,例如 java 的真实值是 x-java ,所以讲 value 和 label 同时和传入语法进行比较
+          return currentLabel === currentLanguage || currentValue === currentLanguage
+        })
+      },
+      _getCoder() {
+        let _this = this
+        return new Promise((resolve) => {
+          (function get() {
+            if (_this.coder) {
+              resolve(_this.coder)
+            } else {
+              setTimeout(get, 10)
+            }
+          })()
+        })
+      },
+      // 更改模式
+      changeMode (val) {
+        // 修改编辑器的语法配置
+        this.coder.setOption('mode', `text/${val}`)
+
+        // 获取修改后的语法
+        let label = this._getLanguage(val).label.toLowerCase()
+
+        // 允许父容器通过以下函数监听当前的语法值
+        this.$emit('language-change', label)
+      },
+      nullTipClick(){
+        this.coder.focus()
+      }
+    }
+  }
+</script>
+
+<style lang="less">
+  .code-editor-cust{
+    flex-grow:1;
+    display:flex;
+    position:relative;
+    height:100%;
+    .CodeMirror{
+      flex-grow:1;
+      z-index:1;
+      .CodeMirror-code{
+        line-height:19px;
+      }
+
+    }
+    .code-mode-select{
+      position:absolute;
+      z-index:2;
+      right:10px;
+      top:10px;
+      max-width:130px;
+    }
+    .CodeMirror{
+      height: auto;
+      min-height:100%;
+    }
+    .null-tip{
+      position: absolute;
+      top: 4px;
+      left: 36px;
+      z-index: 10;
+      color: #ffffffc9;
+      line-height: initial;
+    }
+    .null-tip-hidden{
+      display: none;
+    }
+  }
+
+  /* 全屏样式 */
+  .full-screen-parent {
+    position: relative;
+
+    .full-screen-icon {
+      opacity: 0;
+      color: black;
+      width: 20px;
+      height: 20px;
+      line-height: 24px;
+      background-color: white;
+      position: absolute;
+      top: 2px;
+      right: 2px;
+      z-index: 9;
+      cursor: pointer;
+      transition: opacity 0.3s;
+    }
+
+    &:hover {
+      .full-screen-icon {
+        opacity: 1;
+
+        &:hover {
+          background-color: rgba(255, 255, 255, 0.88);
+        }
+      }
+    }
+
+    &.full-screen {
+      position: fixed;
+      top: 10px;
+      left: 10px;
+      width: calc(100% - 20px);
+      height: calc(100% - 20px);
+      padding: 10px;
+      background-color: #f5f5f5;
+
+      .full-screen-icon {
+        top: 12px;
+        right: 12px;
+      }
+      .full-screen-child {
+        height: 100%;
+        max-height: 100%;
+        min-height: 100%;
+      }
+    }
+
+    .full-screen-child {
+      min-height: 120px;
+      max-height: 320px;
+      overflow:hidden;
+    }
+
+  }
+
+.CodeMirror-cursor{
+  height:18.4px !important;
+}
+</style>

+ 65 - 0
src/components/jeecg/JCron.vue

@@ -0,0 +1,65 @@
+<template>
+  <div class="components-input-demo-presuffix">
+    <a-input @click="openModal" placeholder="corn表达式" v-model="cron" @change="handleOK">
+      <a-icon slot="prefix" type="schedule" title="corn控件"/>
+      <a-icon v-if="cron" slot="suffix" type="close-circle" @click="handleEmpty" title="清空"/>
+    </a-input>
+    <JCronModal ref="innerVueCron" :data="cron" @ok="handleOK"></JCronModal>
+  </div>
+</template>
+<script>
+  import JCronModal from "./modal/JCronModal";
+  export default {
+    name: 'JCron',
+    components: {
+      JCronModal
+    },
+    props: {
+      value: {
+        required: false,
+        type: String,
+      }
+    },
+    data(){
+      return {
+        cron: this.value,
+      }
+    },
+    watch:{
+      value(val){
+        this.cron = val
+      }
+    },
+    methods:{
+      openModal(){
+        this.$refs.innerVueCron.show();
+      },
+      handleOK(val){
+        this.cron = val;
+        this.$emit("change", this.cron);
+        //this.$emit("change", Object.assign({},  this.cron));
+      },
+      handleEmpty(){
+        this.handleOK('')
+      }
+    },
+    model: {
+      prop: 'value',
+      event: 'change'
+    }
+  }
+</script>
+<style scoped>
+  .components-input-demo-presuffix .anticon-close-circle {
+    cursor: pointer;
+    color: #ccc;
+    transition: color 0.3s;
+    font-size: 12px;
+  }
+  .components-input-demo-presuffix .anticon-close-circle:hover {
+    color: #f5222d;
+  }
+  .components-input-demo-presuffix .anticon-close-circle:active {
+    color: #666;
+  }
+</style>

+ 86 - 0
src/components/jeecg/JDate.vue

@@ -0,0 +1,86 @@
+<template>
+  <a-date-picker
+    :disabled="disabled || readOnly"
+    :placeholder="placeholder"
+    @change="handleDateChange"
+    :value="momVal"
+    :showTime="showTime"
+    :format="dateFormat"
+    :getCalendarContainer="getCalendarContainer"
+    style="width:100%"
+  />
+</template>
+<script>
+  import moment from 'moment'
+  export default {
+    name: 'JDate',
+    props: {
+      placeholder:{
+        type: String,
+        default: '',
+        required: false
+      },
+      value:{
+        type: String,
+        required: false
+      },
+      dateFormat:{
+        type: String,
+        default: 'YYYY-MM-DD HH:mm:ss',
+        required: false
+      },
+      //此属性可以被废弃了
+      triggerChange:{
+        type: Boolean,
+        required: false,
+        default: false
+      },
+      readOnly:{
+        type: Boolean,
+        required: false,
+        default: false
+      },
+      disabled:{
+        type: Boolean,
+        required: false,
+        default: false
+      },
+      showTime:{
+        type: Boolean,
+        required: false,
+        default: false
+      },
+      getCalendarContainer: {
+        type: Function,
+        default: (node) => node.parentNode
+      }
+    },
+    data () {
+      let dateStr = this.value;
+      return {
+        decorator:"",
+        momVal:!dateStr?null:moment(dateStr,this.dateFormat)
+      }
+    },
+    watch: {
+      value (val) {
+        if(!val){
+          this.momVal = null
+        }else{
+          this.momVal = moment(val,this.dateFormat)
+        }
+      }
+    },
+    methods: {
+      moment,
+      handleDateChange(mom,dateStr){
+        this.$emit('change', dateStr);
+      }
+    },
+    //2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型 这个牛逼
+    model: {
+      prop: 'value',
+      event: 'change'
+    }
+  }
+</script>

+ 3181 - 0
src/components/jeecg/JEditableTable.vue

@@ -0,0 +1,3181 @@
+<!-- JEditableTable -->
+<!-- @version 1.5.0 -->
+<!-- @author sjlei -->
+<template>
+  <a-spin :spinning="loading">
+
+    <a-row type="flex">
+      <a-col>
+        <slot name="buttonBefore" :target="getVM()"/>
+      </a-col>
+      <a-col>
+        <!-- 操作按钮 -->
+        <div v-if="actionButton" class="action-button">
+          <a-button type="primary" icon="plus" @click="handleClickAdd" :disabled="disabled">插入行</a-button>
+          <span class="gap"></span>
+          <template v-if="selectedRowIds.length>0">
+            <a-popconfirm
+              :title="`确定要移除这 ${selectedRowIds.length} 项吗?`"
+              @confirm="handleConfirmDelete">
+              <a-button type="primary" icon="minus" :disabled="disabled">移除行</a-button>
+              <span class="gap"></span>
+            </a-popconfirm>
+            <template v-if="showClearSelectButton">
+              <a-button icon="delete" @click="handleClickClearSelection">清空选择</a-button>
+              <span class="gap"></span>
+            </template>
+          </template>
+        </div>
+        <div v-if="actionDeleteButton" class="action-button">
+          <template v-if="selectedRowIds.length>0">
+            <a-popconfirm
+              :title="`确定要移除这 ${selectedRowIds.length} 项吗?`"
+              @confirm="handleConfirmDelete">
+              <a-button type="primary" icon="minus" :disabled="disabled">移除行</a-button>
+              <span class="gap"></span>
+            </a-popconfirm>
+          </template>
+        </div>
+      </a-col>
+      <a-col>
+        <slot name="buttonAfter" :target="getVM()"/>
+      </a-col>
+    </a-row>
+
+    <slot name="actionButtonAfter" :target="getVM()"/>
+
+    <div :id="`${caseId}inputTable`" class="input-table" :style="{'min-width':minWidth+'px'}">
+      <!-- 渲染表头 -->
+      <div class="thead" ref="thead">
+        <div class="tr" :style="{width: this.realTrWidth}">
+          <!-- 左侧固定td  -->
+          <div v-if="dragSort" class="td td-ds" :style="style.tdLeftDs">
+            <span></span>
+          </div>
+          <div v-if="dragSortAndNumber" class="td td-ds" :style="style.tdLeftDs">
+            <span>#</span>
+          </div>
+          <div v-if="rowNumber" class="td td-num" :style="style.tdLeft">
+            <span>#</span>
+          </div>
+          <div v-if="rowSelection" class="td td-cb" :style="style.tdLeft">
+            <!--:indeterminate="true"-->
+            <a-checkbox
+              :checked="getSelectAll"
+              :indeterminate="getSelectIndeterminate"
+              @change="handleChangeCheckedAll"
+            />
+          </div>
+          <!-- 右侧动态生成td -->
+          <template v-for="col in columns">
+            <div
+              v-show="col.type !== formTypes.hidden"
+              class="td"
+              :key="col.key"
+              :style="buildTdStyle(col)">
+              <span style="padding: 0 5px;">
+                {{ col.title }}
+                <slot name="depotBatchSet" v-if="col.key === 'depotId'" :target="getVM()"/>
+              </span>
+            </div>
+          </template>
+        </div>
+      </div>
+
+      <div class="scroll-view" ref="scrollView" :style="{'max-height':maxHeight+'px'}">
+
+
+        <!-- 渲染主体 body -->
+        <div :id="`${caseId}tbody`" class="tbody" :style="tbodyStyle">
+          <!-- 扩展高度 -->
+          <div class="tr-expand" :style="`height:${getExpandHeight}px; z-index:${loading?'11':'9'};`"></div>
+          <!-- 无数据时显示 -->
+          <div v-if="rows.length===0" class="tr-nodata">
+            <span>暂无数据</span>
+          </div>
+          <!-- v-model="rows"-->
+          <draggable
+            :value="rows"
+            handle=".td-ds-icons"
+            @start="handleDragMoveStart"
+            @end="handleDragMoveEnd"
+          >
+
+            <!-- 动态生成tr -->
+            <template v-for="(row,rowIndex) in rows">
+              <!-- tr 如果超出200条,则只加载可见的和预加载的总共十条数据 -->
+              <div
+                v-if="rows.length<=200 ||
+                (rows.length>200 &&
+                rowIndex >= parseInt(`${(scrollTop-rowHeight) / rowHeight}`) &&
+                  (parseInt(`${scrollTop / rowHeight}`) + 9) > rowIndex)"
+                :id="`${caseId}tbody-tr-${rowIndex}`"
+                :data-idx="rowIndex"
+                class="tr"
+                :class="selectedRowIds.indexOf(row.id) !== -1 ? 'tr-checked' : ''"
+                :style="buildTrStyle(rowIndex)"
+                :key="row.id">
+                <!-- 左侧固定td  -->
+
+                <div v-if="dragSort" class="td td-ds" :style="style.tdLeftDs">
+                  <a-dropdown :trigger="['click']" :getPopupContainer="getParentContainer">
+                    <div class="td-ds-icons">
+                      <a-icon type="align-left"/>
+                      <a-icon type="align-right"/>
+                    </div>
+
+                    <a-menu slot="overlay">
+                      <a-menu-item key="0" :disabled="rowIndex===0" @click="_handleRowMoveUp(rowIndex)">向上移</a-menu-item>
+                      <a-menu-item key="1" :disabled="rowIndex===(rows.length-1)" @click="_handleRowMoveDown(rowIndex)">向下移</a-menu-item>
+                      <!-- <a-menu-divider/>
+                      <a-menu-item key="3" @click="_handleRowInsertDown(rowIndex)">插入一行</a-menu-item> -->
+                    </a-menu>
+                  </a-dropdown>
+                </div>
+
+                <div v-if="dragSortAndNumber" class="td td-ds" :style="style.tdLeftDs">
+                  <a-dropdown :trigger="['click']" :getPopupContainer="getParentContainer">
+                    <div class="td-ds-icons" title="点击不放可以拖动" style="text-align: center; line-height: 32px">
+                      <span>{{ rowIndex+1 }}</span>
+                    </div>
+
+                    <a-menu slot="overlay">
+                      <a-menu-item key="0" :disabled="rowIndex===0" @click="_handleRowMoveUp(rowIndex)">向上移</a-menu-item>
+                      <a-menu-item key="1" :disabled="rowIndex===(rows.length-1)" @click="_handleRowMoveDown(rowIndex)">向下移</a-menu-item>
+                      <!-- <a-menu-divider/>
+                      <a-menu-item key="3" @click="_handleRowInsertDown(rowIndex)">插入一行</a-menu-item> -->
+                    </a-menu>
+                  </a-dropdown>
+                </div>
+
+                <div v-if="rowNumber" class="td td-num" :style="style.tdLeft">
+                  <span>{{ rowIndex+1 }}</span>
+                </div>
+
+                <div v-if="rowSelection" class="td td-cb" :style="style.tdLeft">
+                  <!-- 此 v-for 只是为了拼接 id 字符串 -->
+                  <template v-for="(id,i) in [`${row.id}`]">
+                    <a-checkbox
+                      :id="id"
+                      :key="i"
+                      :checked="selectedRowIds.indexOf(id) !== -1"
+                      @change="handleChangeLeftCheckbox"/>
+                  </template>
+                </div>
+
+                <!-- 右侧动态生成td -->
+                <div
+                  class="td"
+                  v-for="col in columns"
+                  v-show="col.type !== formTypes.hidden"
+                  :key="col.key"
+                  :style="buildTdStyle(col)">
+
+                  <!-- 此 v-for 只是为了拼接 id 字符串 -->
+                  <template v-for="(id,i) in [`${col.key}${row.id}`]">
+
+                    <!-- native input -->
+                    <label :key="i" v-if="col.type === formTypes.input || col.type === formTypes.inputNumber">
+                      <a-tooltip
+                        :id="id"
+                        placement="top"
+                        :title="(tooltips[id] || {}).title"
+                        :visible="(tooltips[id] || {}).visible || false"
+                        :autoAdjustOverflow="true"
+                        :getPopupContainer="getParentContainer"
+                      >
+
+                        <input
+                          :id="id"
+                          :readonly="col.readonly"
+                          :visible="col.visible"
+                          v-bind="buildProps(row,col)"
+                          :data-input-number="col.type === formTypes.inputNumber"
+                          :placeholder="replaceProps(col, col.placeholder)"
+                          @blur="(e)=>{handleBlurCommono(e.target,rowIndex,row,col)}"
+                          @input="(e)=>{handleInputCommono(e.target,rowIndex,row,col)}"
+                          @mouseover="()=>{handleMouseoverCommono(row,col)}"
+                          @mouseout="()=>{handleMouseoutCommono(row,col)}"/>
+
+                      </a-tooltip>
+
+                    </label>
+                    <!-- checkbox -->
+                    <template v-else-if="col.type === formTypes.checkbox">
+                      <a-checkbox
+                        :key="i"
+                        :id="id"
+                        v-bind="buildProps(row,col)"
+                        :checked="checkboxValues[id]"
+                        @change="(e)=>handleChangeCheckboxCommon(e,row,col)"
+                      />
+                    </template>
+                    <!-- select -->
+                    <template v-else-if="col.type === formTypes.select">
+                      <a-tooltip
+                        :key="i"
+                        :id="id"
+                        placement="top"
+                        :title="(tooltips[id] || {}).title"
+                        :visible="(tooltips[id] || {}).visible || false"
+                        :autoAdjustOverflow="true"
+                        :getPopupContainer="getParentContainer"
+                      >
+
+                      <span
+                        @mouseover="()=>{handleMouseoverCommono(row,col)}"
+                        @mouseout="()=>{handleMouseoutCommono(row,col)}">
+
+                        <a-select
+                          :id="id"
+                          :key="i"
+                          v-bind="buildProps(row,col)"
+                          style="width: 100%;"
+                          :value="getSelectValue(id)"
+                          :options="col.options"
+                          :getPopupContainer="getParentContainer"
+                          :placeholder="replaceProps(col, col.placeholder)"
+                          :filterOption="(i,o)=>handleSelectFilterOption(i,o,col)"
+                          @change="(v)=>handleChangeSelectCommon(v,id,row,col)"
+                          @search="(v)=>handleSearchSelect(v,id,row,col)"
+                          @blur="(v)=>handleBlurSearch(v,id,row,col)"
+                          allowClear
+                        >
+                          <div slot="dropdownRender" slot-scope="menu">
+                            <v-nodes :vnodes="menu" />
+                            <slot name="depotAdd" v-if="col.key === 'depotId'" :target="getVM()"/>
+                            <slot name="inOutItemAdd" v-if="col.key === 'inOutItemId'" :target="getVM()"/>
+                          </div>
+                          <!--<template v-for="(opt,optKey) in col.options">-->
+                          <!--<a-select-option :value="opt.value" :key="optKey">{{ opt.title }}</a-select-option>-->
+                          <!--</template>-->
+                        </a-select>
+                      </span>
+                      </a-tooltip>
+                    </template>
+                    <!-- date -->
+                    <template v-else-if="col.type === formTypes.date || col.type === formTypes.datetime">
+                      <a-tooltip
+                        :key="i"
+                        :id="id"
+                        placement="top"
+                        :title="(tooltips[id] || {}).title"
+                        :visible="(tooltips[id] || {}).visible || false"
+                        :autoAdjustOverflow="true"
+                        :getPopupContainer="getParentContainer"
+                      >
+
+                      <span
+                        @mouseover="()=>{handleMouseoverCommono(row,col)}"
+                        @mouseout="()=>{handleMouseoutCommono(row,col)}">
+
+                        <j-date
+                          :id="id"
+                          :key="i"
+                          v-bind="buildProps(row,col)"
+                          style="width: 100%;"
+                          :value="jdateValues[id]"
+                          :getCalendarContainer="getParentContainer"
+                          :placeholder="replaceProps(col, col.placeholder)"
+                          :trigger-change="true"
+                          :showTime="col.type === formTypes.datetime"
+                          :dateFormat="col.type === formTypes.date? 'YYYY-MM-DD':'YYYY-MM-DD HH:mm:ss'"
+                          allowClear
+                          @change="(v)=>handleChangeJDateCommon(v,id,row,col,col.type === formTypes.datetime)"/>
+
+                      </span>
+                      </a-tooltip>
+                    </template>
+
+                    <!-- input_pop -->
+                    <template v-else-if="col.type === formTypes.input_pop">
+                      <a-tooltip
+                        :key="i"
+                        :id="id"
+                        placement="top"
+                        :title="(tooltips[id] || {}).title"
+                        :visible="(tooltips[id] || {}).visible || false"
+                        :autoAdjustOverflow="true"
+                        :getPopupContainer="getParentContainer">
+                        <span
+                          @mouseover="()=>{handleMouseoverCommono(row,col)}"
+                          @mouseout="()=>{handleMouseoutCommono(row,col)}">
+                          <j-input-pop
+                            :id="id"
+                            :key="i"
+                            :width="300"
+                            :height="210"
+                            :pop-container="`${caseId}tbody`"
+                            v-bind="buildProps(row,col)"
+                            style="width: 100%;"
+                            :value="jInputPopValues[id]"
+                            :getCalendarContainer="getParentContainer"
+                            :placeholder="replaceProps(col, col.placeholder)"
+                            @change="(v)=>handleChangeJInputPopCommon(v,id,row,col)">
+                          </j-input-pop>
+                        </span>
+                      </a-tooltip>
+                    </template>
+
+                    <div v-else-if="col.type === formTypes.upload" :key="i">
+                      <template v-if="uploadValues[id] != null" v-for="(file,fileKey) of [(uploadValues[id]||{})]">
+                        <a-input
+                          :key="fileKey"
+                          :readOnly="true"
+                          :value="file.name"
+                        >
+
+                          <template slot="addonBefore" style="width: 30px">
+                            <a-tooltip v-if="file.status==='uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
+                              <a-icon type="loading"/>
+                            </a-tooltip>
+                            <a-tooltip v-else-if="file.status==='done'" title="上传完成">
+                              <a-icon type="check-circle" style="color:#00DB00;"/>
+                            </a-tooltip>
+                            <a-tooltip v-else title="上传失败">
+                              <a-icon type="exclamation-circle" style="color:red;"/>
+                            </a-tooltip>
+                          </template>
+
+                          <template v-if="col.allowDownload!==false || col.allowRemove!==false" slot="addonAfter" style="width: 30px">
+                            <a-dropdown :trigger="['click']" placement="bottomRight" :getPopupContainer="getParentContainer">
+                              <a-tooltip title="操作" :getPopupContainer="getParentContainer">
+                                <a-icon
+                                  v-if="file.status!=='uploading'"
+                                  type="setting"
+                                  style="cursor: pointer;"/>
+                              </a-tooltip>
+
+                              <a-menu slot="overlay">
+                                <a-menu-item v-if="col.allowDownload!==false" @click="handleClickDownloadFile(id)">
+                                  <span><a-icon type="download"/>&nbsp;下载</span>
+                                </a-menu-item>
+                                <a-menu-item v-if="col.allowRemove!==false" @click="handleClickDelFile(id)">
+                                  <span><a-icon type="delete"/>&nbsp;删除</span>
+                                </a-menu-item>
+                              </a-menu>
+                            </a-dropdown>
+                          </template>
+
+                        </a-input>
+                      </template>
+
+                      <div :hidden="uploadValues[id] != null">
+                        <a-tooltip
+                          :key="i"
+                          :id="id"
+                          placement="top"
+                          :title="(tooltips[id] || {}).title"
+                          :visible="(tooltips[id] || {}).visible || false"
+                          :autoAdjustOverflow="true"
+                          :getPopupContainer="getParentContainer"
+                        >
+
+                          <span
+                            @mouseover="()=>{handleMouseoverCommono(row,col)}"
+                            @mouseout="()=>{handleMouseoutCommono(row,col)}">
+                            <a-upload
+                              name="file"
+                              :data="{'isup':1}"
+                              :multiple="false"
+                              :action="col.action"
+                              :headers="uploadGetHeaders(row,col)"
+                              :showUploadList="false"
+                              v-bind="buildProps(row,col)"
+                              @change="(v)=>handleChangeUpload(v,id,row,col)"
+                            >
+                              <a-button icon="upload">{{ col.placeholder }}</a-button>
+                            </a-upload>
+                          </span>
+                        </a-tooltip>
+                      </div>
+
+                    </div>
+
+                    <!-- update-begin-author:taoyan date:0827 for:popup -->
+                    <template v-else-if="col.type === formTypes.popup">
+                      <a-tooltip
+                        :key="i"
+                        :id="id"
+                        placement="top"
+                        :title="(tooltips[id] || {}).title"
+                        :visible="(tooltips[id] || {}).visible || false"
+                        :autoAdjustOverflow="true"
+                        :getPopupContainer="getParentContainer"
+                      >
+
+                        <span
+                          @mouseover="()=>{handleMouseoverCommono(row,col)}"
+                          @mouseout="()=>{handleMouseoutCommono(row,col)}">
+
+                          <j-popup
+                            :id="id"
+                            :key="i"
+                            v-bind="buildProps(row,col)"
+                            :placeholder="replaceProps(col, col.placeholder)"
+                            style="width: 100%;"
+                            :value="getPopupValue(id)"
+                            :field="col.field || col.key"
+                            :org-fields="col.orgFields"
+                            :dest-fields="col.destFields"
+                            :code="col.popupCode"
+                            :groupId="caseId"
+                            @input="(value,others)=>popupCallback(value,others,id,row,col,rowIndex)"/>
+                        </span>
+                      </a-tooltip>
+                    </template>
+                    <!-- update-end-author:taoyan date:0827 for:popup -->
+
+                    <!-- update-begin-author:jsh date:20210308 for:popupJsh -->
+                    <template v-else-if="col.type === formTypes.popupJsh">
+                      <a-tooltip
+                        :key="i"
+                        :id="id"
+                        placement="top"
+                        :title="(tooltips[id] || {}).title"
+                        :visible="(tooltips[id] || {}).visible || false"
+                        :autoAdjustOverflow="true"
+                        :getPopupContainer="getParentContainer"
+                      >
+
+                        <span
+                          @mouseover="()=>{handleMouseoverCommono(row,col)}"
+                          @mouseout="()=>{handleMouseoutCommono(row,col)}">
+
+                          <j-select-list
+                            :rows="getPopupJshRows(row)"
+                            :kind="col.kind"
+                            :multi="col.multi"
+                            :value="getPopupJshValue(id)"
+                            @change="(v)=>handleChangePopupJshCommon(v,id,row,col,rowIndex)" />
+                        </span>
+                      </a-tooltip>
+                    </template>
+                    <!-- update-end-author:jsh date:20210308 for:popupJsh -->
+
+                    <!-- update-beign-author:taoyan date:0827 for:文件/图片逻辑新增 -->
+                    <div v-else-if="col.type === formTypes.file" :key="i">
+                      <template v-if="uploadValues[id] != null" v-for="(file,fileKey) of [(uploadValues[id]||{})]">
+                        <div :key="fileKey" style="position: relative;">
+                          <a-tooltip v-if="file.status==='uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
+                            <a-icon type="loading" style="color:red;"/>
+                            <span style="color:red;margin-left:5px">{{  file.status }}</span>
+                          </a-tooltip>
+
+                          <a-tooltip v-else-if="file.status==='done'" :title="file.name">
+                            <a-icon type="paper-clip" />
+                            <span style="margin-left:5px">{{ getEllipsisWord(file.name,5) }}</span>
+                          </a-tooltip>
+
+                          <a-tooltip v-else :title="file.name">
+                            <a-icon type="paper-clip" style="color:red;"/>
+                            <span style="color:red;margin-left:5px">{{ getEllipsisWord(file.name,5) }}</span>
+                          </a-tooltip>
+
+                          <template style="width: 30px">
+                            <a-dropdown :trigger="['click']" placement="bottomRight" :getPopupContainer="getParentContainer" style="margin-left: 10px;">
+                              <a-tooltip title="操作" :getPopupContainer="getParentContainer">
+                                <a-icon v-if="file.status!=='uploading'" type="setting" style="cursor: pointer;"/>
+                              </a-tooltip>
+
+                              <a-menu slot="overlay">
+                                <a-menu-item v-if="col.allowDownload!==false" @click="handleClickDownFileByUrl(id)">
+                                  <span><a-icon type="download"/>&nbsp;下载</span>
+                                </a-menu-item>
+                                <a-menu-item @click="handleClickDelFile(id)">
+                                  <span><a-icon type="delete"/>&nbsp;删除</span>
+                                </a-menu-item>
+                                <a-menu-item @click="handleMoreOperation(id)">
+                                  <span><a-icon type="bars" /> 更多</span>
+                                </a-menu-item>
+                              </a-menu>
+                            </a-dropdown>
+                          </template>
+                        </div>
+                      </template>
+
+                      <div :hidden="uploadValues[id] != null">
+                        <a-tooltip
+                          :key="i"
+                          :id="id"
+                          placement="top"
+                          :title="(tooltips[id] || {}).title"
+                          :visible="(tooltips[id] || {}).visible || false"
+                          :autoAdjustOverflow="true"
+                          :getPopupContainer="getParentContainer"
+                        >
+
+                          <span
+                            @mouseover="()=>{handleMouseoverCommono(row,col)}"
+                            @mouseout="()=>{handleMouseoutCommono(row,col)}">
+                            <a-upload
+                              name="file"
+                              :data="{'isup':1}"
+                              :multiple="false"
+                              :action="getUploadAction(col.action)"
+                              :headers="uploadGetHeaders(row,col)"
+                              :showUploadList="false"
+                              v-bind="buildProps(row,col)"
+                              @change="(v)=>handleChangeUpload(v,id,row,col)"
+                            >
+                              <a-button icon="upload">上传文件</a-button>
+                            </a-upload>
+                          </span>
+                        </a-tooltip>
+                      </div>
+
+                    </div>
+
+                    <div v-else-if="col.type === formTypes.image" :key="i">
+                      <template v-if="uploadValues[id] != null" v-for="(file,fileKey) of [(uploadValues[id]||{})]">
+                        <div :key="fileKey" style="position: relative;">
+                          <template v-if="!uploadValues[id] || !(uploadValues[id]['url'] || uploadValues[id]['path'] || uploadValues[id]['message'])">
+                            <a-icon type="loading"/>
+                          </template>
+                          <template v-else-if="uploadValues[id]['path']">
+                            <img class="j-editable-image" :src="getCellImageView(id)" alt="无图片" @click="handleMoreOperation(id,'img')"/>
+                          </template>
+                          <template v-else>
+                            <a-icon type="exclamation-circle" style="color: red;" @click="handleClickShowImageError(id)"/>
+                          </template>
+                          <template slot="addonBefore" style="width: 30px">
+                            <a-tooltip v-if="file.status==='uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
+                              <a-icon type="loading"/>
+                            </a-tooltip>
+                            <a-tooltip v-else-if="file.status==='done'" title="上传完成">
+                              <a-icon type="check-circle" style="color:#00DB00;"/>
+                            </a-tooltip>
+                            <a-tooltip v-else title="上传失败">
+                              <a-icon type="exclamation-circle" style="color:red;"/>
+                            </a-tooltip>
+                          </template>
+
+                          <template style="width: 30px">
+                            <a-dropdown :trigger="['click']" placement="bottomRight" :getPopupContainer="getParentContainer" style="margin-left: 10px;">
+                              <a-tooltip title="操作" :getPopupContainer="getParentContainer">
+                                <a-icon
+                                  v-if="file.status!=='uploading'"
+                                  type="setting"
+                                  style="cursor: pointer;"/>
+                              </a-tooltip>
+
+                              <a-menu slot="overlay">
+                                <a-menu-item v-if="col.allowDownload!==false" @click="handleClickDownFileByUrl(id)">
+                                  <span><a-icon type="download"/>&nbsp;下载</span>
+                                </a-menu-item>
+                                <a-menu-item @click="handleClickDelFile(id)">
+                                  <span><a-icon type="delete"/>&nbsp;删除</span>
+                                </a-menu-item>
+                                <a-menu-item @click="handleMoreOperation(id,'img')">
+                                  <span><a-icon type="bars" /> 更多</span>
+                                </a-menu-item>
+                              </a-menu>
+                            </a-dropdown>
+                          </template>
+
+                        </div>
+                      </template>
+
+                      <div :hidden="uploadValues[id] != null">
+                        <a-tooltip
+                          :key="i"
+                          :id="id"
+                          placement="top"
+                          :title="(tooltips[id] || {}).title"
+                          :visible="(tooltips[id] || {}).visible || false"
+                          :autoAdjustOverflow="true"
+                          :getPopupContainer="getParentContainer"
+                        >
+
+                          <span
+                            @mouseover="()=>{handleMouseoverCommono(row,col)}"
+                            @mouseout="()=>{handleMouseoutCommono(row,col)}">
+                            <a-upload
+                              name="file"
+                              :data="{'isup':1}"
+                              :multiple="false"
+                              :action="getUploadAction(col.action)"
+                              :headers="uploadGetHeaders(row,col)"
+                              :showUploadList="false"
+                              v-bind="buildProps(row,col)"
+                              @change="(v)=>handleChangeUpload(v,id,row,col)"
+                            >
+                              <a-button icon="upload">上传图片</a-button>
+                            </a-upload>
+                          </span>
+                        </a-tooltip>
+                      </div>
+
+                    </div>
+                    <!-- update-end-author:taoyan date:0827 for:图片逻辑新增 -->
+
+
+                    <!-- radio-begin -->
+                    <template v-else-if="col.type === formTypes.radio">
+                      <a-tooltip
+                        :key="i"
+                        :id="id"
+                        placement="top"
+                        :title="(tooltips[id] || {}).title"
+                        :visible="(tooltips[id] || {}).visible || false"
+                        :autoAdjustOverflow="true"
+                        :getPopupContainer="getParentContainer"
+                      >
+
+                        <span
+                          @mouseover="()=>{handleMouseoverCommono(row,col)}"
+                          @mouseout="()=>{handleMouseoutCommono(row,col)}">
+                          <a-radio-group
+                            :id="id"
+                            :key="i"
+                            v-bind="buildProps(row,col)"
+                            :value="radioValues[id]"
+                            @change="(e)=>handleRadioChange(e.target.value,id,row,col)">
+                            <a-radio v-for="(item, key) in col.options" :key="key" :value="item.value">{{ item.text }}</a-radio>
+                          </a-radio-group>
+                        </span>
+                      </a-tooltip>
+                    </template>
+                    <!-- radio-end -->
+
+
+                    <!-- select多选 -begin -->
+                    <template v-else-if="col.type === formTypes.list_multi">
+                      <a-tooltip
+                        :key="i"
+                        :id="id"
+                        placement="top"
+                        :title="(tooltips[id] || {}).title"
+                        :visible="(tooltips[id] || {}).visible || false"
+                        :autoAdjustOverflow="true"
+                        :getPopupContainer="getParentContainer"
+                      >
+
+                        <span
+                          @mouseover="()=>{handleMouseoverCommono(row,col)}"
+                          @mouseout="()=>{handleMouseoutCommono(row,col)}">
+
+                          <a-select
+                            :id="id"
+                            :key="i"
+                            mode="multiple"
+                            :maxTagCount="1"
+                            v-bind="buildProps(row,col)"
+                            style="width: 100%;"
+                            :value="multiSelectValues[id]"
+                            :options="col.options"
+                            :getPopupContainer="getParentContainer"
+                            :placeholder="replaceProps(col, col.placeholder)"
+                            @change="(v)=>handleMultiSelectChange(v,id,row,col)"
+                            allowClear>
+                          </a-select>
+                        </span>
+                      </a-tooltip>
+                    </template>
+                    <!-- select多选 -end -->
+
+                    <!-- select搜索 -begin -->
+                    <template v-else-if="col.type === formTypes.sel_search">
+                      <a-tooltip
+                        :key="i"
+                        :id="id"
+                        placement="top"
+                        :title="(tooltips[id] || {}).title"
+                        :visible="(tooltips[id] || {}).visible || false"
+                        :autoAdjustOverflow="true"
+                        :getPopupContainer="getParentContainer"
+                      >
+
+                        <span
+                          @mouseover="()=>{handleMouseoverCommono(row,col)}"
+                          @mouseout="()=>{handleMouseoutCommono(row,col)}">
+
+                          <a-select
+                            :id="id"
+                            :key="i"
+                            showSearch
+                            optionFilterProp="children"
+                            :filterOption="filterOption"
+                            v-bind="buildProps(row,col)"
+                            style="width: 100%;"
+                            :value="searchSelectValues[id]"
+                            :options="col.options"
+                            :getPopupContainer="getParentContainer"
+                            :placeholder="replaceProps(col, col.placeholder)"
+                            @change="(v)=>handleSearchSelectChange(v,id,row,col)"
+                            allowClear>
+                          </a-select>
+                        </span>
+                      </a-tooltip>
+                    </template>
+                    <!-- select搜索 -end -->
+
+
+                    <div v-else-if="col.type === formTypes.slot" :key="i">
+                      <a-tooltip
+                        :key="i"
+                        :id="id"
+                        placement="top"
+                        :title="(tooltips[id] || {}).title"
+                        :visible="(tooltips[id] || {}).visible || false"
+                        :autoAdjustOverflow="true"
+                        :getPopupContainer="getParentContainer"
+                      >
+
+                        <span
+                          @mouseover="()=>{handleMouseoverCommono(row,col)}"
+                          @mouseout="()=>{handleMouseoutCommono(row,col)}">
+                            <slot
+                              :name="(col.slot || col.slotName) || col.key"
+                              :index="rowIndex"
+                              :text="slotValues[id]"
+                              :value="slotValues[id]"
+                              :column="col"
+                              :rowId="getCleanId(row.id)"
+                              :getValue="()=>_getValueForSlot(row.id)"
+                              :caseId="caseId"
+                              :allValues="_getAllValuesForSlot()"
+                              :target="getVM()"
+                              :handleChange="(v)=>handleChangeSlotCommon(v,id,row,col)"
+                              :isNotPass="notPassedIds.includes(col.key+row.id)"
+                            />
+                        </span>
+                      </a-tooltip>
+                    </div>
+
+                    <!-- else (normal) -->
+                    <span v-else :key="i" v-bind="buildProps(row,col)" class="td-span" :title="inputValues[rowIndex][col.key]">
+                      {{ inputValues[rowIndex][col.key] }}
+                    </span>
+                  </template>
+                </div>
+              </div>
+              <!-- -- tr end -- -->
+
+            </template>
+          </draggable>
+
+
+          <!-- 统计行 -->
+          <div
+            v-if="showStatisticsRow"
+            class="tr"
+            :style="{
+              ...buildTrStyle(rows.length),
+              height: '32px'
+            }"
+          >
+            <div v-if="dragSort" class="td td-ds" :style="style.tdLeftDs">
+            </div>
+            <div v-if="dragSortAndNumber" class="td td-ds" :style="style.tdLeftDs">
+            </div>
+            <div v-if="rowNumber" class="td td-num" :style="style.tdLeft">
+              <span v-if="!rowSelection">统计</span>
+            </div>
+            <div v-if="rowSelection" class="td td-cb" :style="style.tdLeft">
+            </div>
+
+            <!-- 右侧动态生成td -->
+            <template v-for="col in columns">
+              <div
+                :key="col.key"
+                class="td"
+                v-show="col.type !== formTypes.hidden"
+                :style="buildTdStyle(col)"
+              >
+                <span
+                  v-show="col.type === formTypes.inputNumber"
+                  style="padding: 0 2px;"
+                >{{statisticsColumns[col.key]}}</span>
+              </div>
+            </template>
+
+          </div>
+
+        </div>
+      </div>
+      <j-file-pop ref="filePop" @ok="handleFileSuccess"></j-file-pop>
+    </div>
+  </a-spin>
+</template>
+
+<script>
+  import Vue from 'vue'
+  import $ from 'jquery'
+  import Draggable from 'vuedraggable'
+  import { ACCESS_TOKEN } from '@/store/mutation-types'
+  import { FormTypes, VALIDATE_NO_PASSED } from '@/utils/JEditableTableUtil'
+  import { cloneObject, randomString, randomNumber } from '@/utils/util'
+  import JDate from '@/components/jeecg/JDate'
+  import { getFileAccessHttpUrl } from '@/api/manage';
+  import JInputPop from '@/components/jeecg/minipop/JInputPop'
+  import JFilePop from '@/components/jeecg/minipop/JFilePop'
+  import JSelectList from '@/components/jeecgbiz/JSelectList'
+
+  // 行高,需要在实例加载完成前用到
+  let rowHeight = 42
+
+  export default {
+    name: 'JEditableTable',
+    components: {
+      JDate, Draggable, JInputPop, JFilePop, JSelectList,
+      VNodes: {
+        functional: true,
+        render: (h, ctx) => ctx.props.vnodes,
+      }
+    },
+    provide() {
+      return {
+        parentIsJEditableTable: true,
+        getDestroyCleanGroupRequest: () => this.destroyCleanGroupRequest,
+      }
+    },
+    props: {
+      // 列信息
+      columns: {
+        type: Array,
+        required: true
+      },
+      // 数据源
+      dataSource: {
+        type: Array,
+        required: true,
+        default: () => []
+      },
+      // 是否显示操作按钮
+      actionButton: {
+        type: Boolean,
+        default: false
+      },
+      // 是否显示删除按钮
+      actionDeleteButton: {
+        type: Boolean,
+        default: false
+      },
+      // 是否显示行号
+      rowNumber: {
+        type: Boolean,
+        default: false
+      },
+      // 是否可选择行
+      rowSelection: {
+        type: Boolean,
+        default: false
+      },
+      // 页面是否在加载中
+      loading: {
+        type: Boolean,
+        default: false
+      },
+      // 页面是否在加载中
+      minWidth: {
+        type: Number,
+        default: 1500
+      },
+      maxHeight: {
+        type: Number,
+        default: 400
+      },
+      // 要禁用的行
+      disabledRows: {
+        type: Object,
+        default() {
+          return {}
+        }
+      },
+      // 是否禁用全部组件
+      disabled: {
+        type: Boolean,
+        default: false
+      },
+      // 是否可拖拽排序
+      dragSort: {
+        type: Boolean,
+        default: false
+      },
+      // 是否可拖拽排序并显示行号
+      dragSortAndNumber: {
+        type: Boolean,
+        default: false
+      },
+      dragSortKey: {
+        type: String,
+        default: 'orderNum'
+      },
+    },
+    data() {
+      return {
+        // 是否首次运行
+        isFirst: true,
+        // 当前实例是否是行编辑
+        isJEditableTable: true,
+        // caseId,用于防止有多个实例的时候会冲突
+        caseIdPrefix: '_jet-',
+        caseId: `_jet-${randomString(6)}-`,
+        // 临时ID标识,凡是以该标识结尾的ID都是临时ID,不添加到数据库中
+        tempId: `_tid-${randomString(6)}`,
+        // 存储document element 对象
+        el: {
+          inputTable: null,
+          tbody: null
+        },
+        // 存储各个div的style
+        style: {
+          // 'max-height': '400px'
+          tbody: { left: '0px' },
+          // 左侧固定td的style
+          // 20200331 cfm modify
+          // tdLeft: { 'min-width': '4%', 'max-width': '45px' },
+          tdLeft: { 'min-width': '40px', 'max-width': '45px' },
+          tdLeftDs: { 'min-width': '30px', 'max-width': '35px' },
+        },
+        // 表单的类型
+        formTypes: FormTypes,
+        // 行数据
+        rows: [],
+        // 行高,height + padding + border
+        rowHeight,
+        // 滚动条顶部距离
+        scrollTop: 0,
+        // 绑定 select 的值
+        selectValues: {},
+        // 绑定 checkbox 的值
+        checkboxValues: {},
+        // 绑定 jdate 的值
+        jdateValues: {},
+        // 绑定jinputpop
+        jInputPopValues:{},
+        // 绑定插槽数据
+        slotValues: {},
+        // file 信息
+        uploadValues: {},
+        //popup信息
+        popupValues: {},
+        //popupJsh信息
+        popupJshValues: {},
+        radioValues: {},
+        metaCheckboxValues: {},
+        multiSelectValues: {},
+        searchSelectValues: {},
+        // 绑定左侧选择框已选择的id
+        selectedRowIds: [],
+        // 存储被删除行的id
+        deleteIds: [],
+        // 存储显示tooltip的信息
+        tooltips: {},
+        // 存储没有通过验证的inputId
+        notPassedIds: [],
+
+        // 当前是否正在拖拽排序
+        dragging: false,
+        // 是否有统计列
+        hasStatisticsColumn: false,
+        statisticsColumns: {},
+        // 只有在行编辑被销毁时才主动清空GroupRequest的内存
+        destroyCleanGroupRequest: false,
+      }
+    },
+    created() {
+      this.inputValues = []
+      // 当前显示的tr
+      this.visibleTrEls = []
+      this.disabledRowIds = (this.disabledRowIds || [])
+    },
+    // 计算属性
+    computed: {
+      // expandHeight = rows.length * rowHeight
+      getExpandHeight() {
+        let length = this.rows.length * this.rowHeight
+        if (this.showStatisticsRow) {
+          length += 34
+        }
+        return length
+      },
+      // 是否显示统计行
+      showStatisticsRow() {
+        return this.hasStatisticsColumn && this.rows.length > 0
+      },
+      // 获取是否选择了部分
+      getSelectIndeterminate() {
+        return (this.selectedRowIds.length > 0 &&
+          this.selectedRowIds.length < this.rows.length)
+      },
+      // 获取是否选择了全部
+      getSelectAll() {
+        return (this.selectedRowIds.length === this.rows.length) && this.rows.length > 0
+      },
+      tbodyStyle() {
+        let style = Object.assign({}, this.style.tbody)
+        // style['max-height'] = `${this.maxHeight}px`
+        style['width'] = this.realTrWidth
+        return style
+      },
+      showClearSelectButton() {
+        let count = 0
+        for (let key in this.disabledRows) {
+          if (this.disabledRows.hasOwnProperty(key)) count++
+        }
+        return count > 0
+      },
+      accessToken() {
+        return Vue.ls.get(ACCESS_TOKEN)
+      },
+      realTrWidth() {
+        let splice = ' + '
+        let calcWidth = 'calc('
+        this.columns.forEach((column, i) => {
+          let { type, width } = column
+          // 隐藏字段不参与计算
+          if (type !== FormTypes.hidden) {
+            if (typeof width === 'number') {
+              calcWidth += width + 'px'
+            } else if (typeof width === 'string') {
+              calcWidth += width
+            } else {
+              calcWidth += '120px'
+            }
+            calcWidth += splice
+          }
+        })
+        if (calcWidth.endsWith(splice)) {
+          calcWidth = calcWidth.substring(0, calcWidth.length - splice.length)
+        }
+        calcWidth += ')'
+        // console.log('calcWidth: ', calcWidth)
+        return calcWidth
+      }
+    },
+    // 侦听器
+    watch: {
+      rows: {
+        immediate: true,
+        handler(val, old) {
+          // val.forEach(item => {
+          //   for (let inputValue of  this.inputValues) {
+          //     if (inputValue.id === item.id) {
+          //       item['dbFieldName'] = inputValue['dbFieldName']
+          //       break
+          //     }
+          //   }
+          // })
+          // console.log('watch.rows:', cloneObject({ val, old }))
+        }
+      },
+      dataSource: {
+        immediate: true,
+        handler: function (newValue) {
+          // 兼容IE
+          this.getElementPromise('tbody').then(() => {
+            this.initialize()
+            this._pushByDataSource(newValue)
+          })
+        }
+      },
+      columns: {
+        immediate: true,
+        handler(columns) {
+          // 兼容IE
+          this.getElementPromise('tbody').then(() => {
+            columns.forEach(column => {
+              if (column.type === FormTypes.select || column.type === FormTypes.list_multi || column.type === FormTypes.sel_search) {
+                // 兼容 旧版本 options
+                if (column.options instanceof Array) {
+                  column.options = column.options.map(item => {
+                    if (item) {
+                      return {
+                        ...item,
+                        text: item.text || item.title,
+                        title: item.text || item.title
+                      }
+                    }
+                    return {}
+                  })
+                }
+              }
+            })
+          })
+        }
+      },
+      // 当selectRowIds改变时触发事件
+      selectedRowIds(newValue) {
+        this.$emit('selectRowChange', cloneObject(newValue).map(i => this.getCleanId(i)))
+      }
+    },
+    mounted() {
+      let vm = this
+      /** 监听滚动条事件 */
+      this.getElement('inputTable').onscroll = function (event) {
+        vm.syncScrollBar(event.target.scrollLeft)
+      }
+      this.getElement('tbody').onscroll = function (event) {
+        // vm.recalcTrHiddenItem(event.target.scrollTop)
+      }
+
+      let { thead, scrollView } = this.$refs
+      scrollView.onscroll = function (event) {
+
+        // console.log(event.target.scrollTop, ' - ', event.target.scrollLeft)
+
+        thead.scrollLeft = event.target.scrollLeft
+
+        vm.recalcTrHiddenItem(event.target.scrollTop)
+
+      }
+
+    },
+    methods: {
+
+      getElement(id, noCaseId = false) {
+        if (!this.el[id]) {
+          this.el[id] = document.getElementById((noCaseId ? '' : this.caseId) + id)
+        }
+        return this.el[id]
+      },
+
+      getElementPromise(id, noCaseId = false) {
+        return new Promise((resolve) => {
+          let timer = setInterval(() => {
+            let element = this.getElement(id, noCaseId)
+            if (element) {
+              clearInterval(timer)
+              resolve(element)
+            }
+          }, 10)
+        })
+      },
+
+      /** 初始化列表 */
+      initialize() {
+        this.visibleTrEls = []
+        // 判断是否是首次进入该方法,如果是就不清空行,防止删除了预添加的数据
+        if (!this.isFirst) {
+          // inputValues:用来存储input表单的值
+          // 数组里的每项都是一个对象,对象里每个key都是input的rowKey,值就是input的值,其中有个id的字段来区分
+          // 示例:
+          // [{
+          //    id: "_jet-4sp0iu-15541771111770"
+          //    dbDefaultVal: "aaa",
+          //    dbFieldName: "bbb",
+          //    dbFieldTxt: "ccc",
+          //    dbLength: 32
+          // }]
+          this.inputValues = []
+          this.rows = []
+          this.deleteIds = []
+          this.selectValues = {}
+          this.checkboxValues = {}
+          this.jdateValues = {}
+          this.jInputPopValues = {}
+          this.slotValues = {}
+          this.selectedRowIds = []
+          this.tooltips = {}
+          this.notPassedIds = []
+          this.uploadValues = []
+          this.popupValues = []
+          this.popupJshValues = []
+          this.radioValues = []
+          this.multiSelectValues = []
+          this.searchSelectValues = []
+          this.scrollTop = 0
+          this.$nextTick(() => {
+            this.getElement('tbody').scrollTop = 0
+          })
+        } else {
+          this.isFirst = false
+        }
+      },
+
+      /** 同步滚动条状态 */
+      syncScrollBar(scrollLeft) {
+        // this.style.tbody.left = `${scrollLeft}px`
+        // this.getElement('tbody').scrollLeft = scrollLeft
+      },
+      /** 重置滚动条位置,参数留空则滚动到上次记录的位置 */
+      resetScrollTop(top) {
+        let { scrollView } = this.$refs
+        if (top != null && typeof top === 'number') {
+          scrollView.scrollTop = top
+        } else {
+          scrollView.scrollTop = this.scrollTop
+        }
+      },
+      /** 重新计算需要隐藏或显示的tr */
+      recalcTrHiddenItem(top) {
+        let diff = top - this.scrollTop
+        if (diff < 0) {
+          diff = this.scrollTop - top
+        }
+        // 只有在滚动了百分之三十的行高的距离时才进行更新
+        if (diff >= this.rowHeight * 0.3) {
+          this.scrollTop = top
+          // 更新form表单的值
+          this.$nextTick(() => {
+            this.updateFormValues()
+          })
+        }
+      },
+      /** 生成id */
+      generateId(rows) {
+        if (!(rows instanceof Array)) {
+          rows = this.rows || []
+        }
+        let timestamp = new Date().getTime()
+        return `${this.caseId}${timestamp}${rows.length}${randomNumber(6)}${this.tempId}`
+      },
+      /** push 一条数据 */
+      push(record, update = true, rows, insertIndex = null, setDefaultValue = true) {
+        return this._pushByDataSource([record], [insertIndex], update, rows, setDefaultValue)
+      },
+
+      /**
+       * push 数据
+       *
+       * @param dataSource 数据源
+       * @param insertIndexes 行插入位置,和dataSource的下标一一对应
+       * @param update 是否更新
+       * @param rows 若不传就使用 this.rows
+       * @param setDefaultValue 是否填充默认值
+       *
+       */
+      _pushByDataSource(dataSource, insertIndexes = null, update = true, rows = null, setDefaultValue = false) {
+        if (!(rows instanceof Array)) {
+          rows = [...this.rows] || []
+        }
+        let checkboxValues = { ...this.checkboxValues }
+        let selectValues = { ...this.selectValues }
+        let jdateValues = { ...this.jdateValues }
+        let jInputPopValues = { ...this.jInputPopValues }
+        let slotValues = { ...this.slotValues }
+        let uploadValues = { ...this.uploadValues }
+        let popupValues = { ...this.popupValues }
+        let popupJshValues = { ...this.popupJshValues }
+        let radioValues = { ...this.radioValues }
+        let multiSelectValues = { ...this.multiSelectValues }
+        let searchSelectValues = { ...this.searchSelectValues }
+        // 禁用行的id
+        let disabledRowIds = (this.disabledRowIds || [])
+        dataSource.forEach((data, newValueIndex) => {
+          // 不能直接更改数据源的id
+          let dataId = data.id
+          // 判断源数据是否带有id
+          if (dataId == null || dataId === '') {
+            dataId = this.generateId(rows)
+          } else if(!this.hasCaseId(dataId)) {
+            dataId = this.caseId + dataId
+          }
+          let row = { id: dataId }
+          let value = { id: dataId }
+          let disabled = false
+          this.columns.forEach(column => {
+            let inputId = column.key + value.id
+            let sourceValue = (data[column.key] == null ? '' : data[column.key]).toString()
+
+            let defaultValue = null;
+            if (setDefaultValue) {
+              defaultValue = column.defaultValue || (column.defaultValue === 0 ? 0 : '')
+              if (defaultValue instanceof Array) {
+                defaultValue = defaultValue.join(',')
+              }
+
+              sourceValue = (typeof sourceValue === 'number' || sourceValue) ? sourceValue : defaultValue
+            }
+            let sourceValueIsEmpty = (sourceValue == null || sourceValue === '')
+
+            if (column.type === FormTypes.inputNumber) {
+              // 判断是否是排序字段,如果是就赋最大值
+              if (column.isOrder === true) {
+                value[column.key] = this.getInputNumberMaxValue(column) + 1
+              } else {
+                value[column.key] = sourceValue
+              }
+              // 判断是否是统计列
+              if (column.statistics) {
+                this.hasStatisticsColumn = true
+                if (!this.statisticsColumns[column.key]) {
+                  this.$set(this.statisticsColumns, column.key, 0)
+                }
+              }
+
+            } else if (column.type === FormTypes.checkbox) {
+              // 判断是否设定了customValue(自定义值)
+              if (column.customValue instanceof Array) {
+                let customValue = (column.customValue[0] || '').toString()
+                if (sourceValueIsEmpty && setDefaultValue) {
+                  sourceValue = column.defaultChecked ? customValue : sourceValue
+                }
+                checkboxValues[inputId] = (sourceValue === customValue)
+              } else {
+                if (sourceValueIsEmpty && setDefaultValue) {
+                  checkboxValues[inputId] = !!column.defaultChecked
+                } else {
+                  checkboxValues[inputId] = sourceValue
+                }
+              }
+
+            } else if (column.type === FormTypes.select) {
+              if (!sourceValueIsEmpty) {
+                // 判断是否是多选
+                if (typeof sourceValue === 'string' && (column.props || {})['mode'] === 'multiple') {
+                  sourceValue = sourceValue.split(',')
+                }
+                selectValues[inputId] = sourceValue
+              } else {
+                selectValues[inputId] = undefined
+              }
+
+            } else if (column.type === FormTypes.date || column.type === FormTypes.datetime) {
+              jdateValues[inputId] = sourceValue
+
+            } else if (column.type === FormTypes.slot) {
+              slotValues[inputId] = sourceValue
+
+            } else if (column.type === FormTypes.popup) {
+              popupValues[inputId] = sourceValue
+            } else if (column.type === FormTypes.popupJsh) {
+              popupJshValues[inputId] = sourceValue
+            } else if (column.type === FormTypes.input_pop) {
+              jInputPopValues[inputId] = sourceValue
+            } else if (column.type === FormTypes.radio) {
+              radioValues[inputId] = sourceValue
+            } else if (column.type === FormTypes.sel_search) {
+              searchSelectValues[inputId] = sourceValue
+            } else if (column.type === FormTypes.list_multi) {
+              if (typeof sourceValue === 'string' && sourceValue.length > 0) {
+                multiSelectValues[inputId] = sourceValue.split(',')
+              } else {
+                multiSelectValues[inputId] = []
+              }
+            } else if (column.type === FormTypes.upload || column.type === FormTypes.file || column.type === FormTypes.image) {
+              if (sourceValue) {
+                let fileName = ''
+                if (sourceValue.indexOf(',') > 0) {
+                  let sourceValue2 = sourceValue.split(',')[0]
+                  fileName = sourceValue2.substring(sourceValue2.lastIndexOf('/') + 1)
+                } else {
+                  fileName = sourceValue.substring(sourceValue.lastIndexOf('/') + 1)
+                }
+                uploadValues[inputId] = {
+                  name: fileName,
+                  status: 'done',
+                  path: sourceValue
+                }
+              }
+            } else {
+              value[column.key] = sourceValue
+            }
+
+            // 解析disabledRows
+            for (let columnKey in this.disabledRows) {
+              // 判断是否有该属性
+              if (this.disabledRows.hasOwnProperty(columnKey) && data.hasOwnProperty(columnKey)) {
+                if (disabled !== true) {
+                  let temp = this.disabledRows[columnKey]
+                  // 禁用规则可以是一个数组
+                  if (temp instanceof Array) {
+                    disabled = temp.includes(data[columnKey])
+                  } else {
+                    disabled = (temp === data[columnKey])
+                  }
+                  if (disabled) {
+                    disabledRowIds.push(row.id)
+                  }
+                }
+              }
+            }
+          })
+          // 插入行而不是添加到最后
+          let added = false
+          if (insertIndexes instanceof Array) {
+            let insertIndex = insertIndexes[newValueIndex]
+            if (typeof insertIndex === 'number') {
+              added = true
+              rows.splice(insertIndex, 0, row)
+              this.inputValues.splice(insertIndex, 0, value)
+            }
+          }
+          if (!added) {
+            rows.push(row)
+            this.inputValues.push(value)
+          }
+        })
+        // 启用了拖动排序,就重新计算排序编号
+        if (this.dragSort) {
+          this.inputValues.forEach((item, index) => {
+            item[this.dragSortKey] = (index + 1)
+          })
+        }
+        this.disabledRowIds = disabledRowIds
+        this.checkboxValues = checkboxValues
+        this.selectValues = selectValues
+        this.jdateValues = jdateValues
+        this.jInputPopValues = jInputPopValues
+        this.slotValues = slotValues
+        this.uploadValues = uploadValues
+        this.popupValues = popupValues
+        this.popupJshValues = popupJshValues
+        this.radioValues = radioValues
+        this.multiSelectValues = multiSelectValues
+        this.searchSelectValues = searchSelectValues
+        // 重新计算所有统计列
+        this.recalcAllStatisticsColumns()
+        // 更新到 dom
+        if (update) {
+          this.rows = rows
+
+          // 更新form表单的值
+          this.$nextTick(() => {
+            this.updateFormValues()
+          })
+        }
+        return rows
+      },
+
+      /** 获取某一数字输入框列中的最大的值 */
+      getInputNumberMaxValue(column) {
+        let maxNum = 0
+        this.inputValues.forEach((item, index) => {
+          let val = item[column.key], num
+          try {
+            num = parseInt(val)
+          } catch {
+            num = 0
+          }
+          // 把首次循环的结果当成最大值
+          if (index === 0) {
+            maxNum = num
+          } else {
+            maxNum = (num > maxNum) ? num : maxNum
+          }
+        })
+        return maxNum
+      },
+      /** 添加一行 */
+      add(num = 1, forceScrollToBottom = false) {
+        if (num < 1) return
+        // let timestamp = new Date().getTime()
+        let rows = this.rows
+        let row
+        for (let i = 0; i < num; i++) {
+          rows = this.push({}, false, rows)
+          row = rows[rows.length - 1]
+        }
+        this.rows = rows
+
+        this.$nextTick(() => {
+          this.updateFormValues()
+        })
+        // 触发add事件
+        this.$emit('added', {
+          row: (() => {
+            let r = Object.assign({}, row)
+            r.id = this.getCleanId(r.id)
+            return r
+          })(),
+          target: this
+        })
+        // 设置滚动条位置
+        let tbody = this.getElement('tbody')
+        let offsetHeight = tbody.offsetHeight
+        let realScrollTop = tbody.scrollTop + offsetHeight
+        if (forceScrollToBottom === false) {
+          // 只有滚动条在底部的时候才自动滚动
+          if (!((tbody.scrollHeight - realScrollTop) <= 10)) {
+            return
+          }
+        }
+        this.$nextTick(() => {
+          tbody.scrollTop = tbody.scrollHeight
+        })
+        this.$nextTick(() => {
+          this.autoJumpNextInputBill()
+        })
+      },
+      /**
+       * 在指定位置添加一行
+       * @param insertIndex 添加位置下标
+       * @param num 添加的行数,默认1
+       */
+      insert(insertIndex, num = 1) {
+        if (!insertIndex && num < 1) return
+        let rows = this.rows
+        let newRows = []
+        for (let i = 0; i < num; i++) {
+          let row = { id: this.generateId(rows) }
+          rows = this.push(row, false, rows, insertIndex)
+          newRows.push(row)
+        }
+        // 同步更改
+        this.rows = rows
+        this.$nextTick(() => {
+          this.recalcSortNumber()
+          this.forceUpdateFormValues()
+        })
+        // 触发 insert 事件
+        this.$emit('inserted', {
+          rows: newRows.map(row => {
+            let r = cloneObject(row)
+            r.id = this.getCleanId(r.id)
+            return r
+          }),
+          num, insertIndex,
+          target: this
+        })
+        this.$nextTick(() => {
+          this.autoJumpNextInputBill()
+        })
+      },
+      /** 删除被选中的行 */
+      removeSelectedRows() {
+        this.removeRows(this.selectedRowIds)
+        this.selectedRowIds = []
+      },
+      /** 删除一行或多行 */
+      removeRows(id) {
+        let ids = id
+        if (!(id instanceof Array)) {
+          if (typeof id === 'string') {
+            ids = [id]
+          } else {
+            throw  `JEditableTable.removeRows() 函数需要的参数可以是string或Array类型,但提供的却是${typeof id}`
+          }
+        }
+
+        let rows = cloneObject(this.rows)
+        ids.forEach(removeId => {
+          removeId = this.getCleanId(removeId)
+          // 找到每个id对应的真实index并删除
+          const findAndDelete = (arr) => {
+            for (let i = 0; i < arr.length; i++) {
+              let currentId = this.getCleanId(arr[i].id)
+              if (currentId === removeId) {
+                arr.splice(i, 1)
+                return true
+              }
+            }
+          }
+          // 找到rows对应的index,并删除
+          if (findAndDelete(rows)) {
+            // 找到values对应的index,并删除
+            findAndDelete(this.inputValues)
+            // 将caseId去除
+            let id = this.getCleanId(removeId)
+            this.deleteIds.push(id)
+          }
+        })
+        this.rows = rows
+        this.$emit('deleted', this.getDeleteIds(), this)
+        this.$nextTick(() => {
+          // 更新formValues
+          this.updateFormValues()
+          // 重新计算统计
+          this.recalcAllStatisticsColumns()
+        })
+        return true
+      },
+
+      /** 获取表格表单里的值(异步版) */
+      getValuesAsync(options = {}, callback) {
+        let { validate, rowIds, deleteTempId } = options
+        if (typeof validate !== 'boolean') validate = true
+        if (!(rowIds instanceof Array)) rowIds = null
+        // 是否删除临时ID,默认为 false
+        if (typeof deleteTempId !== 'boolean') deleteTempId = false
+        // console.log('options:', { validate, rowIds })
+
+        let asyncCount = 0
+        let error = 0
+        let inputValues = cloneObject(this.inputValues)
+        let tooltips = Object.assign({}, this.tooltips)
+        let notPassedIds = cloneObject(this.notPassedIds)
+        // 用于存储合并后的值
+        let values = []
+        // 遍历inputValues来获取每行的值
+        for (let value of inputValues) {
+          let rowIdsFlag = false
+          // 如果带有rowIds,那么就只存这几行的数据
+          if (rowIds == null) {
+            rowIdsFlag = true
+          } else {
+            for (let rowId of rowIds) {
+              if (this.getCleanId(rowId) === this.getCleanId(value.id)) {
+                rowIdsFlag = true
+                break
+              }
+            }
+          }
+
+          if (!rowIdsFlag) continue
+
+          this.columns.forEach(column => {
+            let inputId = column.key + value.id
+            if (column.type === FormTypes.checkbox) {
+              let checked = this.checkboxValues[inputId]
+              if (column.customValue instanceof Array) {
+                value[column.key] = checked ? column.customValue[0] : column.customValue[1]
+              } else {
+                value[column.key] = checked
+              }
+
+            } else if (column.type === FormTypes.select) {
+              let selected = this.selectValues[inputId]
+              if (selected instanceof Array) {
+                value[column.key] = cloneObject(selected)
+              } else {
+                value[column.key] = selected
+              }
+
+            } else if (column.type === FormTypes.date || column.type === FormTypes.datetime) {
+              value[column.key] = this.jdateValues[inputId]
+
+            } else if (column.type === FormTypes.input_pop) {
+              value[column.key] = this.jInputPopValues[inputId]
+
+            } else if (column.type === FormTypes.upload) {
+              value[column.key] = cloneObject(this.uploadValues[inputId] || null)
+
+            } else if (column.type === FormTypes.image || column.type === FormTypes.file) {
+              let currUploadObj = cloneObject(this.uploadValues[inputId] || null)
+              if (currUploadObj) {
+                value[column.key] = currUploadObj['path'] || null
+              }
+
+            } else if (column.type === FormTypes.popup) {
+              if (!value[column.key]) {
+                value[column.key] = this.popupValues[inputId] || null
+              }
+            } else if (column.type === FormTypes.popupJsh) {
+              if (!value[column.key]) {
+                value[column.key] = this.popupJshValues[inputId] || null
+              }
+            } else if (column.type === FormTypes.radio) {
+              value[column.key] = this.radioValues[inputId]
+            } else if (column.type === FormTypes.sel_search) {
+              value[column.key] = this.searchSelectValues[inputId]
+            } else if (column.type === FormTypes.list_multi) {
+              if (!this.multiSelectValues[inputId] || this.multiSelectValues[inputId].length === 0) {
+                value[column.key] = ''
+              } else {
+                value[column.key] = this.multiSelectValues[inputId].join(',')
+              }
+            } else if (column.type === FormTypes.slot) {
+              value[column.key] = this.slotValues[inputId]
+            }
+
+
+            // 检查表单验证
+            if (validate === true) {
+              const handleValidateOneInput = (results) => {
+                tooltips[inputId] = results[0]
+                if (tooltips[inputId].passed === false) {
+                  error++
+                  // if (error++ === 0) {
+                  // let element = document.getElementById(inputId)
+                  // while (element.className !== 'tr') {
+                  //   element = element.parentElement
+                  // }
+                  // this.jumpToId(inputId, element)
+                  // }
+                }
+                tooltips[inputId].visible = false
+                notPassedIds = results[1]
+              }
+              asyncCount++
+              let results = this.validateOneInputAsync(value[column.key], value, column, notPassedIds, false, 'getValues', (results) => {
+                handleValidateOneInput(results)
+                asyncCount--
+              })
+              handleValidateOneInput(results)
+            }
+          })
+          // 删除 tempId
+          if (deleteTempId && this.isTempId(value.id)) {
+            delete value.id
+          } else {
+            value.id = this.getCleanId(value.id)
+          }
+
+          values.push(value)
+        }
+
+        if (validate === true) {
+          this.tooltips = tooltips
+          this.notPassedIds = notPassedIds
+        }
+
+        const timer = setInterval(() => {
+          if (asyncCount === 0) {
+            clearInterval(timer)
+            if (typeof callback === 'function') {
+              callback({ error, values })
+            }
+          }
+        }, 10)
+
+        return { error, values }
+      },
+
+      /** 获取表格表单里的值(同步版) */
+      getValuesSync(options = {}) {
+        return this.getValuesAsync(options)
+      },
+
+      /** 获取表格表单里的值 */
+      getValues(callback, validate = true, rowIds) {
+        this.getValuesAsync({ validate, rowIds }, ({ error, values }) => {
+          if (typeof callback === 'function') {
+            callback(error, values)
+          }
+        })
+      },
+      /** getValues的Promise版 */
+      getValuesPromise(validate = true, rowIds, deleteTempId) {
+        return new Promise((resolve, reject) => {
+          this.getValuesAsync({ validate, rowIds, deleteTempId }, ({ error, values }) => {
+            if (error === 0) {
+              resolve(values)
+            } else {
+              reject(VALIDATE_NO_PASSED)
+            }
+          })
+        })
+      },
+      /** 获取被删除项的id */
+      getDeleteIds() {
+        return cloneObject(this.deleteIds)
+      },
+      /** 获取所有的数据,包括values、deleteIds */
+      getAll(validate, deleteTempId) {
+        return new Promise((resolve, reject) => {
+          let deleteIds = this.getDeleteIds()
+          this.getValuesPromise(validate, null, deleteTempId).then((values) => {
+            resolve({ values, deleteIds })
+          }).catch(error => {
+            reject(error)
+          })
+        })
+      },
+      /** Sync 获取所有的数据,包括values、deleteIds */
+      getAllSync(validate, rowIds, deleteTempId) {
+        let result = this.getValuesSync({ validate, rowIds, deleteTempId })
+        result.deleteIds = this.getDeleteIds()
+        return result
+      },
+      // slot 获取值
+      _getValueForSlot(rowId) {
+        return this.getValuesSync({ rowIds: [rowId] }).values[0]
+      },
+      _getAllValuesForSlot() {
+        return cloneObject({
+          inputValues: this.inputValues,
+          selectValues: this.selectValues,
+          checkboxValues: this.checkboxValues,
+          jdateValues: this.jdateValues,
+          jInputPopValues: this.jInputPopValues,
+          slotValues: this.slotValues,
+          uploadValues: this.uploadValues,
+          popupValues: this.popupValues,
+          popupJshValues: this.popupJshValues,
+          radioValues: this.radioValues,
+          multiSelectValues: this.multiSelectValues,
+          searchSelectValues: this.searchSelectValues,
+        })
+      },
+      /** 设置某行某列的值 */
+      setValues(values) {
+        values.forEach(item => {
+          let { rowKey, values: newValues } = item
+          rowKey = this.getCleanId(rowKey)
+          for (let newValueKey in newValues) {
+            if (newValues.hasOwnProperty(newValueKey)) {
+              let newValue = newValues[newValueKey]
+              let edited = false // 已被修改
+              this.inputValues.forEach(value => {
+                // 在inputValues中找到了该字段
+                if (rowKey === this.getCleanId(value.id)) {
+                  if (value.hasOwnProperty(newValueKey)) {
+                    edited = true
+                    value[newValueKey] = newValue
+                  }
+                }
+              })
+              let modelKey = `${newValueKey}${this.caseId}${rowKey}`
+              // 在 selectValues 中寻找值
+              if (!edited) {
+                if (newValue !== 0 && !newValue) {
+                  edited = this.setOneValue(this.selectValues, modelKey, undefined)
+                } else {
+                  edited = this.setOneValue(this.selectValues, modelKey, newValue)
+                }
+              }
+              // 在 checkboxValues 中寻找值
+              if (!edited) {
+                edited = this.setOneValue(this.checkboxValues, modelKey, newValue)
+              }
+              // 在 jdateValues 中寻找值
+              if (!edited) {
+                edited = this.setOneValue(this.jdateValues, modelKey, newValue)
+              }
+              // 在 jInputPopValues 中寻找值
+              if (!edited) {
+                edited = this.setOneValue(this.jInputPopValues, modelKey, newValue)
+              }
+              // 在 slotValues 中寻找值
+              if (!edited) {
+                edited = this.setOneValue(this.slotValues, modelKey, newValue)
+              }
+              // 在 uploadValues 中寻找值
+              if (!edited) {
+                edited = this.setOneValue(this.uploadValues, modelKey, newValue)
+              }
+              // 在 popupValues 中寻找值
+              if (!edited) {
+                edited = this.setOneValue(this.popupValues, modelKey, newValue)
+              }
+              // 在 popupJshValues 中寻找值
+              if (!edited) {
+                edited = this.setOneValue(this.popupJshValues, modelKey, newValue)
+              }
+              // 在 radioValues 中寻找值
+              if (!edited) {
+                edited = this.setOneValue(this.radioValues, modelKey, newValue)
+              }
+              // 在 multiSelectValues 中寻找值
+              if (!edited) {
+                edited = this.setOneValue(this.multiSelectValues, modelKey, newValue)
+              }
+              // 在 searchSelectValues 中寻找值
+              if (!edited) {
+                edited = this.setOneValue(this.searchSelectValues, modelKey, newValue)
+              }
+            }
+          }
+        })
+        // 强制更新formValues
+        this.forceUpdateFormValues()
+      },
+      setOneValue(valuesObject, modelKey, value) {
+        let key = this.valuesHasOwnProperty(valuesObject, modelKey)
+        if (key) {
+          this.$set(valuesObject, key, value)
+          return true
+        }
+        return false
+      },
+      valuesHasOwnProperty(values, ownProperty) {
+        let key = ownProperty
+        if (values.hasOwnProperty(key)) {
+          return key
+        }
+        if (values.hasOwnProperty(key + this.tempId)) {
+          return key + this.tempId
+        }
+        return null
+      },
+
+      /** 跳转到指定位置 */
+      // jumpToId(id, element) {
+      //   if (element == null) {
+      //     element = document.getElementById(id)
+      //   }
+      //   if (element != null) {
+      //     console.log(this.getElement('tbody').scrollTop, element.offsetTop)
+      //     this.getElement('tbody').scrollTop = element.offsetTop
+      //     console.log(this.getElement('tbody').scrollTop, element.offsetTop)
+      //   }
+      // },
+
+      /**
+       * 验证单个表单,异步版
+       *
+       * @param value 校验的值
+       * @param row 校验的行
+       * @param column 校验的列
+       * @param notPassedIds 没有通过校验的 id
+       * @param update 是否更新到vue中
+       * @param validType 校验触发的方式(input、blur等)
+       * @param callback
+       */
+      validateOneInputAsync(value, row, column, notPassedIds, update = false, validType = 'input', callback) {
+        let tooltips = Object.assign({}, this.tooltips)
+        // let notPassedIds = cloneObject(this.notPassedIds)
+        let inputId = column.key + row.id
+        tooltips[inputId] = tooltips[inputId] ? tooltips[inputId] : {}
+
+        let [passed, message] = this.validateValue(column, value)
+
+        const nextThen = res => {
+          let [passed, message] = res
+          // !(passed == null && tooltips[inputId].visible != null)
+          if (passed != null) {
+            tooltips[inputId].visible = !passed
+            tooltips[inputId].passed = passed
+            let index = notPassedIds.indexOf(inputId)
+            let borderColor = null, boxShadow = null
+            if (!passed) {
+              tooltips[inputId].title = this.replaceProps(column, message)
+              borderColor = 'red'
+              boxShadow = `0 0 0 2px rgba(255, 0, 0, 0.2)`
+              if (index === -1) notPassedIds.push(inputId)
+            } else {
+              if (index !== -1) notPassedIds.splice(index, 1)
+            }
+
+            let element = document.getElementById(inputId)
+            if (element != null) {
+              // select 在 .ant-select-selection 上设置 border-color
+              if (column.type === FormTypes.select) {
+                element = element.getElementsByClassName('ant-select-selection')[0]
+              }
+              // jdate 在 input 上设置 border-color
+              if (column.type === FormTypes.date || column.type === FormTypes.datetime) {
+                element = element.getElementsByTagName('input')[0]
+              }
+              // upload 在 .ant-upload .ant-btn 上设置 border-color
+              if (column.type === FormTypes.upload || column.type === FormTypes.file || column.type === FormTypes.image) {
+                element = element.getElementsByClassName('ant-upload')[0].getElementsByClassName('ant-btn')[0]
+              }
+              element.style.borderColor = borderColor
+              element.style.boxShadow = boxShadow
+              if (element.tagName === 'SPAN') {
+                element.style.display = 'block'
+              }
+            }
+          }
+          // 是否更新到data
+          if (update) {
+            this.tooltips = tooltips
+            this.notPassedIds = notPassedIds
+          }
+
+          if (typeof callback === 'function') {
+            callback([tooltips[inputId], notPassedIds])
+          }
+
+        }
+
+        if (typeof passed === 'function') {
+          let executed = false
+          passed(validType, value, { id: this.getCleanId(row.id) }, { ...column }, (flag, msg) => {
+            if (executed) return
+            executed = true
+            if (typeof msg === 'string') {
+              message = msg
+            }
+            if (flag == null) {
+              nextThen([null, message])
+            } else {
+              nextThen([!!flag, message])
+            }
+          }, this)
+        } else {
+          nextThen([passed, message])
+        }
+
+        return [tooltips[inputId], notPassedIds]
+      },
+
+      /** 验证单个表单 */
+      validateOneInput(value, row, column, notPassedIds, update = false, validType = 'input') {
+        return this.validateOneInputAsync(value, row, column, notPassedIds, update, validType)
+      },
+      /** 通过规则验证值是否正确 */
+      validateValue(column, value) {
+        let rules = column.validateRules
+        let passed = true, message = ''
+        // 判断有没有验证规则或验证规则格式正不正确,若条件不符合则默认通过
+        if (rules instanceof Array) {
+          for (let rule of rules) {
+            // 当前值是否为空
+            let isNull = (value == null || value === '')
+            // 验证规则:非空
+            if (rule.required === true && isNull) {
+              passed = false
+            } else // 使用 else-if 是为了防止一个 rule 中出现两个规则
+            // 验证规则:唯一校验
+            if (rule.unique === true || rule.pattern === 'only') {
+              let { values } = this.getValuesSync({ validate: false })
+              let findCount = 0
+              for (let val of values) {
+                if (val[column.key] === value) {
+                  if (++findCount >= 2) {
+                    passed = false
+                    break
+                  }
+                }
+              }
+            } else
+            // 验证规则:正则表达式
+            if (!!rule.pattern && !isNull) {
+
+              // 兼容 online 的规则
+              let foo = [
+                { title: '6到16位数字', value: 'n6-16', pattern: /^\d{6,18}$/ },
+                { title: '6到16位任意字符', value: '*6-16', pattern: /^.{6,16}$/ },
+                { title: '6到18位字母', value: 's6-18', pattern: /^[a-z|A-Z]{6,18}$/ },
+                { title: '网址', value: 'url', pattern: /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/ },
+                { title: '电子邮件', value: 'e', pattern: /^([\w]+\.*)([\w]+)@[\w]+\.\w{3}(\.\w{2}|)$/ },
+                { title: '手机号码', value: 'm', pattern: /^1[3456789]\d{9}$/ },
+                { title: '邮政编码', value: 'p', pattern: /^[1-9]\d{5}$/ },
+                { title: '字母', value: 's', pattern: /^[A-Z|a-z]+$/ },
+                { title: '数字', value: 'n', pattern: /^-?\d+(\.?\d+|\d?)$/ },
+                { title: '整数', value: 'z', pattern: /^-?\d+$/ },
+                { title: '非空', value: '*', pattern: /^.+$/ },
+                { title: '金额', value: 'money', pattern: /^(([1-9][0-9]*)|([0]\.\d{0,2}|[1-9][0-9]*\.\d{0,2}))$/ },
+              ]
+              let flag = false
+              for (let item of foo) {
+                if (rule.pattern === item.value && item.pattern) {
+                  passed = new RegExp(item.pattern).test(value)
+                  flag = true
+                  break
+                }
+              }
+              if (!flag) passed = new RegExp(rule.pattern).test(value)
+            } else
+            // 校验规则:自定义函数校验
+            if (typeof rule.handler === 'function') {
+              return [rule.handler, rule.message]
+            }
+            // 如果没有通过验证,则跳出循环。如果通过了验证,则继续验证下一条规则
+            if (!passed) {
+              message = rule.message
+              break
+            }
+          }
+        }
+        return [passed, message]
+      },
+
+      /** 动态更新表单的值 */
+      updateFormValues() {
+        let trs = this.getElement('tbody').getElementsByClassName('tr')
+        let trEls = []
+        for (let tr of trs) {
+          trEls.push(tr)
+        }
+        // 获取新增的 tr
+        let newTrEls = trEls
+        if (this.visibleTrEls.length > 0) {
+          newTrEls = []
+          for (let tr of trEls) {
+            let isNewest = true
+            for (let vtr of this.visibleTrEls) {
+              if (vtr.id === tr.id) {
+                isNewest = false
+                break
+              }
+            }
+            if (isNewest) {
+              newTrEls.push(tr)
+            }
+          }
+        }
+        this.visibleTrEls = trEls
+        // 向新增的tr中赋值
+        newTrEls.forEach(tr => {
+          let { idx } = tr.dataset
+          let value = this.inputValues[idx]
+          for (let key in value) {
+            if (value.hasOwnProperty(key)) {
+              let elid = `${key}${value.id}`
+              let el = document.getElementById(elid)
+              if (el) {
+                el.value = value[key]
+              }
+            }
+          }
+        })
+      },
+      /** 强制更新FormValues */
+      forceUpdateFormValues() {
+        this.visibleTrEls = []
+        this.updateFormValues()
+      },
+
+      // 重新计算所有统计列
+      recalcAllStatisticsColumns() {
+        if (this.hasStatisticsColumn) {
+          Object.keys(this.statisticsColumns).forEach(key => this.recalcOneStatisticsColumn(key))
+        }
+      },
+      // 重新计算单个统计列
+      recalcOneStatisticsColumn(key) {
+        if (this.hasStatisticsColumn) {
+          if (this.statisticsColumns.hasOwnProperty(key)) {
+            // 计算合计值
+            let count = 0
+            this.inputValues.forEach(item => {
+              let value = item[key]
+              if (value && count !== '-') {
+                try {
+                  count += Number.parseFloat(value)
+                } catch (e) {
+                  count = '-'
+                }
+              }
+            })
+            this.statisticsColumns[key] = count.toFixed(2)
+          }
+        }
+      },
+
+      /** 获取某个统计字段的值 */
+      getStatisticsValue(key) {
+        if (this.hasStatisticsColumn) {
+          if (this.statisticsColumns.hasOwnProperty(key)) {
+            return this.statisticsColumns[key]
+          }
+        }
+        return null
+      },
+
+      /** 全选或取消全选 */
+      handleChangeCheckedAll() {
+        let selectedRowIds = []
+        if (!this.getSelectAll) {
+          this.rows.forEach(row => {
+            if ((this.disabledRowIds || []).indexOf(row.id) === -1) {
+              selectedRowIds.push(row.id)
+            }
+          })
+        }
+        this.selectedRowIds = selectedRowIds
+      },
+      /** 左侧行选择框change事件 */
+      handleChangeLeftCheckbox(event) {
+        let { id } = event.target
+
+        if ((this.disabledRowIds || []).indexOf(id) !== -1) {
+          return
+        }
+
+        let index = this.selectedRowIds.indexOf(id)
+        if (index !== -1) {
+          this.selectedRowIds.splice(index, 1)
+        } else {
+          this.selectedRowIds.push(id)
+        }
+
+      },
+      handleClickAdd() {
+        this.add()
+      },
+      handleConfirmDelete() {
+        this.removeSelectedRows()
+      },
+      handleClickClearSelection() {
+        this.clearSelection()
+      },
+      clearSelection() {
+        this.selectedRowIds = []
+      },
+      /** 用于搜索下拉框中的内容 */
+      handleSelectFilterOption(input, option, column) {
+        if (column.allowSearch === true || column.allowInput === true) {
+          return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
+        }
+        return true
+      },
+      /** select 搜索时的事件,用于动态添加options */
+      handleSearchSelect(value, id, row, col) {
+        if (col.allowSearch !== true && col.allowInput === true) {
+          // 是否找到了对应的项,找不到则添加这一项
+          let flag = false
+          for (let option of col.options) {
+            if (option.value.toLocaleString() === value.toLocaleString()) {
+              flag = true
+              break
+            }
+          }
+          // !!value :不添加空值
+          if (!flag && !!value) {
+            // searchAdd 是否是通过搜索添加的
+            col.options.push({ title: value, value: value, searchAdd: true })
+          }
+
+        }
+      },
+      // blur 失去焦点
+      handleBlurSearch(value, id, row, col) {
+        if (col.allowInput === true) {
+          // 删除无用的因搜索(用户输入)而创建的项
+          if (typeof value === 'string') {
+            let indexs = []
+            col.options.forEach((option, index) => {
+              if (option.value.toLocaleString() === value.toLocaleString()) {
+                delete option.searchAdd
+              } else if (option.searchAdd === true) {
+                indexs.push(index)
+              }
+            })
+            // 翻转删除数组中的项
+            for (let index of indexs.reverse()) {
+              col.options.splice(index, 1)
+            }
+          }
+
+        }
+        // 做单个表单验证
+        this.validateOneInput(value, row, col, this.notPassedIds, true, 'blur')
+      },
+
+      /** 触发已拖动事件 */
+      emitDragged(oldIndex, newIndex) {
+        this.$emit('dragged', { oldIndex, newIndex, target: this })
+      },
+
+      handleDragMoveStart(event) {
+        this.dragging = true
+        this.$refs.scrollView.style.overflow = 'hidden'
+      },
+
+      /** 拖动结束,交换inputValue中的值 */
+      handleDragMoveEnd(event) {
+        this.dragging = false
+        this.$refs.scrollView.style.overflow = 'auto'
+
+        let { oldIndex, newIndex, item: { dataset: { idx: dataIdx } } } = event
+
+        // 由于动态显示隐藏行导致index有误差,需要算出真实的index
+        let diff = Number.parseInt(dataIdx) - oldIndex
+        oldIndex += diff
+        newIndex += diff
+
+        this.rowResort(oldIndex, newIndex)
+        this.emitDragged(oldIndex, newIndex)
+      },
+
+      /** 行重新排序 */
+      rowResort(oldIndex, newIndex) {
+        const sort = (array) => {
+          // 存储旧数据,并删除旧项目
+          let temp = array[oldIndex]
+          array.splice(oldIndex, 1)
+          // 向新项目里添加旧数据
+          array.splice(newIndex, 0, temp)
+        }
+
+        sort(this.rows)
+        sort(this.inputValues)
+
+        this.recalcSortNumber()
+
+        this.forceUpdateFormValues()
+      },
+
+      /** 重新计算排序字段的数值 */
+      recalcSortNumber() {
+        if (this.dragSort) {
+          // 重置排序字段
+          this.inputValues.forEach((val, idx) => val[this.dragSortKey] = (idx + 1))
+        }
+      },
+
+      /** 当前行向上移一位 */
+      _handleRowMoveUp(rowIndex) {
+        if (rowIndex > 0) {
+          let newIndex = rowIndex - 1
+          this.rowResort(rowIndex, newIndex)
+          this.emitDragged(rowIndex, newIndex)
+        }
+      },
+
+      /** 当前行向下移一位 */
+      _handleRowMoveDown(rowIndex) {
+        if (rowIndex < (this.rows.length - 1)) {
+          let newIndex = rowIndex + 1
+          this.rowResort(rowIndex, newIndex)
+          this.emitDragged(rowIndex, newIndex)
+        }
+      },
+
+      /** 在当前行下面插入一行 */
+      _handleRowInsertDown(rowIndex) {
+        let insertIndex = (rowIndex + 1)
+        this.insert(insertIndex)
+      },
+
+      /* --- common function begin --- */
+
+      /** 鼠标移入 */
+      handleMouseoverCommono(row, column) {
+        let inputId = column.key + row.id
+        if (this.notPassedIds.indexOf(inputId) !== -1) {
+          this.showOrHideTooltip(inputId, true, true)
+        }
+      },
+      /** 鼠标移出 */
+      handleMouseoutCommono(row, column) {
+        let inputId = column.key + row.id
+        this.showOrHideTooltip(inputId, false)
+      },
+      /** input事件 */
+      handleInputCommono(target, index, row, column) {
+        let oldValue = this.inputValues[index][column.key] || ''
+        let { value, dataset, selectionStart } = target
+        let type = FormTypes.input
+        let change = true
+        if (`${dataset.inputNumber}` === 'true') {
+          type = FormTypes.inputNumber
+          // 判断输入的值是否匹配数字正则表达式,不匹配就还原
+          if (!/^-?\d+\.?\d*$/.test(value) && (value !== '' && value !== '-')) {
+            change = false
+            value = oldValue
+            target.value = value
+            if (typeof selectionStart === 'number') {
+              target.selectionStart = selectionStart - 1
+              target.selectionEnd = selectionStart - 1
+            }
+          }
+        }
+        // 存储输入的值
+        this.inputValues[index][column.key] = value
+        // 做单个表单验证
+        this.validateOneInput(value, row, column, this.notPassedIds, true, 'input')
+
+        if (type === FormTypes.inputNumber) {
+          this.recalcOneStatisticsColumn(column.key)
+        }
+
+        // 触发valueChange 事件
+        if (change) {
+          this.elemValueChange(type, row, column, value)
+        }
+      },
+      /** slot Change */
+      handleChangeSlotCommon(value, id, row, column) {
+        this.slotValues = this.bindValuesChange(value, id, 'slotValues')
+        // 做单个表单验证
+        this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
+        // 触发valueChange 事件
+        this.elemValueChange(FormTypes.slot, row, column, value)
+      },
+      handleBlurCommono(target, index, row, column) {
+        let { value, dataset } = target
+        if (dataset && `${dataset.inputNumber}` === 'true') {
+          // 判断输入的值是否匹配数字正则表达式,不匹配就置空
+          if (!/^-?\d+\.?\d*$/.test(value)) {
+            value = ''
+          } else {
+            value = Number.parseFloat(value)
+          }
+          target.value = value
+        }
+        // 做单个表单验证
+        this.validateOneInput(value, row, column, this.notPassedIds, true, 'blur')
+      },
+      handleChangeCheckboxCommon(event, row, column) {
+        let { id, checked } = event.target
+        this.checkboxValues = this.bindValuesChange(checked, id, 'checkboxValues')
+
+        // 触发valueChange 事件
+        this.elemValueChange(FormTypes.checkbox, row, column, checked)
+      },
+      handleChangeSelectCommon(value, id, row, column) {
+        this.selectValues = this.bindValuesChange(value, id, 'selectValues')
+        // 做单个表单验证
+        this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
+
+        // 触发valueChange 事件
+        this.elemValueChange(FormTypes.select, row, column, value)
+      },
+      handleChangePopupJshCommon(value, id, row, column,index) {
+        this.popupJshValues = this.bindValuesChange(value, id, 'popupJshValues')
+        // 做单个表单验证
+        this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
+
+        // 触发valueChange 事件
+        this.elemValueChange(FormTypes.popupJsh, row, column, value)
+      },
+      handleChangeJDateCommon(value, id, row, column, showTime) {
+        this.jdateValues = this.bindValuesChange(value, id, 'jdateValues')
+        this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
+
+        // 触发valueChange 事件
+        if (showTime) {
+          this.elemValueChange(FormTypes.datetime, row, column, value)
+        } else {
+          this.elemValueChange(FormTypes.date, row, column, value)
+        }
+      },
+      handleChangeJInputPopCommon(value, id, row, column){
+        this.jInputPopValues = this.bindValuesChange(value, id, 'jInputPopValues')
+        // 做单个表单验证
+        this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
+        // 触发valueChange 事件
+        this.elemValueChange(FormTypes.input_pop, row, column, value)
+      },
+      handleChangeUpload(info, id, row, column) {
+        let { file } = info
+        let value = {
+          name: file.name,
+          type: file.type,
+          size: file.size,
+          status: file.status,
+          percent: file.percent
+        }
+        if (column.responseName && file.response) {
+          value['responseName'] = file.response[column.responseName]
+        }
+        if (file.status === 'done') {
+          value['path'] = file.response[column.responseName]
+        } else if (file.status === 'error') {
+          value['message'] = file.response.message || '未知错误'
+        }
+        this.uploadValues = this.bindValuesChange(value, id, 'uploadValues')
+      },
+      handleMoreOperation(id,flag){
+        //console.log("this.uploadValues[id]",this.uploadValues[id])
+        let path = ''
+        if(this.uploadValues && this.uploadValues[id]){
+          path = this.uploadValues[id].path
+        }
+        this.$refs.filePop.show(id,path,flag)
+      },
+      handleFileSuccess(obj){
+        if(obj.id){
+          this.uploadValues = this.bindValuesChange(obj, obj.id, 'uploadValues')
+        }
+      },
+      /** 记录用到数据绑定的组件的值 */
+      bindValuesChange(value, id, key) {
+        // let values = Object.assign({}, this[key])
+        // values[id] = value
+        // return values
+        this.$set(this[key], id, value)
+        return this[key]
+      },
+
+      /** 显示或隐藏tooltip */
+      showOrHideTooltip(inputId, show, force = false) {
+        if (!this.tooltips[inputId] && !force) {
+          return
+        }
+
+        let tooltip = this.tooltips[inputId] || {}
+        if (tooltip.visible !== show) {
+          tooltip.visible = show
+          this.$set(this.tooltips, inputId, tooltip)
+        }
+      },
+
+      /** value 触发valueChange事件 */
+      elemValueChange(type, rowSource, columnSource, value) {
+        let column = Object.assign({}, columnSource)
+        // 将caseId去除
+        let row = Object.assign({}, rowSource)
+        row.id = this.getCleanId(row.id)
+        // 获取整行的数据
+        let { values } = this.getValuesSync({ validate: false, rowIds: [row.id] })
+        if (values.length > 0) {
+          Object.assign(row, values[0])
+        }
+        this.$emit('valueChange', { type, row, column, value, target: this })
+      },
+
+      /** 获取干净的ID(不包含任何杂质的ID) */
+      getCleanId(id) {
+        id = this.removeCaseId(id)
+        id = this.removeTempId(id)
+        return id
+      },
+
+      /** 判断某个ID是否包含了caseId */
+      hasCaseId(id) {
+        id = id + ""
+        return id && id.startsWith(this.caseId)
+      },
+
+      /** 将caseId去除 */
+      removeCaseId(id) {
+        if (this.hasCaseId(id)) {
+          return id.substring(this.caseId.length, id.length)
+        }
+        return id
+      },
+
+      // 判断 id 是否是临时Id
+      isTempId(id) {
+        return (id || '').endsWith(this.tempId)
+      },
+
+      /** 将tempId去除 */
+      removeTempId(id) {
+        if (this.isTempId(id)) {
+          return id.substring(0, id.length - this.tempId.length)
+        }
+        return id;
+      },
+
+      handleClickDelFile(id) {
+        this.uploadValues[id] = null
+      },
+      handleClickDownloadFile(id) {
+        let { path } = this.uploadValues[id] || {}
+        if (path) {
+          let url = getFileAccessHttpUrl(path)
+          window.open(url)
+        }
+      },
+      handleClickDownFileByUrl(id){
+        let { url,path } = this.uploadValues[id] || {}
+        if (!url || url.length===0) {
+          if(path && path.length>0){
+            url = getFileAccessHttpUrl(path.split(',')[0])
+          }
+        }
+        if(url){
+          window.open(url)
+        }
+      },
+      handleClickShowImageError(id) {
+        let currUploadObj = this.uploadValues[id] || null
+        if (currUploadObj && currUploadObj['message']) {
+          this.$error({ title: '上传出错', content: '错误信息:' + currUploadObj['message'], maskClosable: true })
+        }
+      },
+
+      /* --- common function end --- */
+
+      /* --- 以下是辅助方法,多用于动态构造页面中的数据 --- */
+
+      /** 辅助方法:打印日志 */
+      log() {
+        if (this.$attrs.logger) {
+          console.log.apply(null, arguments)
+        }
+      },
+
+      getVM() {
+        return this
+      },
+
+      /** 辅助方法:指定a-select 和 j-data 的父容器 */
+      getParentContainer(node) {
+        let element = (() => {
+          // nodeType 8	: Comment	: 注释
+          if (this.$el && this.$el.nodeType !== 8) {
+            return this.$el
+          }
+          let doc = document.getElementById(this.caseId + 'inputTable')
+          if (doc != null) {
+            return doc
+          }
+          return node.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
+        })()
+
+        // 递归判断是否带有 overflow: hidden;的父元素
+        const ifParent = (child) => {
+          let currentOverflow = null
+          if (child['currentStyle']) {
+            currentOverflow = child['currentStyle']['overflow']
+          } else if (window.getComputedStyle) {
+            currentOverflow = window.getComputedStyle(child)['overflow']
+          }
+          if (currentOverflow != null) {
+            if (currentOverflow === 'hidden') {
+              // 找到了带有 hidden 的标签,判断它的父级是否还有 hidden,直到遇到完全没有 hidden 或 body 的时候才停止递归
+              let temp = ifParent(child.parentNode)
+              return temp != null ? temp : child.parentNode
+            } else
+            // 当前标签没有 hidden ,如果有父级并且父级不是 body 的话就继续递归判断父级
+            if (child.parentNode && child.parentNode.tagName.toLocaleLowerCase() !== 'body') {
+              return ifParent(child.parentNode)
+            } else {
+              // 直到 body 都没有遇到有 hidden 的标签
+              return null
+            }
+          } else {
+            return child
+          }
+        }
+
+        let temp = ifParent(element)
+        return (temp != null) ? temp : element
+      },
+
+      /** 辅助方法:替换${...}变量 */
+      replaceProps(col, value) {
+        if (value && typeof value === 'string') {
+          value = value.replace(/\${title}/g, col.title)
+          value = value.replace(/\${key}/g, col.key)
+          value = value.replace(/\${defaultValue}/g, col.defaultValue)
+        }
+        return value
+      },
+
+      /** view辅助方法:构建 tr style */
+      buildTrStyle(index) {
+        return {
+          'top': `${rowHeight * index}px`
+        }
+      },
+      /** view辅助方法:构建 td style */
+      buildTdStyle(col) {
+        const isEmptyWidth = (column) => (column.type === FormTypes.hidden || column.width === '0px' || column.width === '0' || column.width === 0)
+
+        let style = {}
+        // 计算宽度
+        if (col.width) {
+          style['width'] = col.width
+        } else if (this.columns) {
+          style['width'] = `${(100 - 4 * 2) / (this.columns.filter(column => !isEmptyWidth(column))).length}%`
+        } else {
+          style['width'] = '120px'
+        }
+        // checkbox 居中显示
+        let isCheckbox = col.type === FormTypes.checkbox
+        if (isCheckbox) {
+          style['align-items'] = 'center'
+          style['text-align'] = 'center'
+          style['padding-left'] = '0'
+          style['padding-right'] = '0'
+        }
+        if (isEmptyWidth(col)) {
+          style['padding-left'] = '0'
+          style['padding-right'] = '0'
+        }
+        return style
+      },
+      /** view辅助方法:构造props */
+      buildProps(row, col) {
+        let props = {}
+        // 解析props
+        if (typeof col.props === 'object') {
+          for (let prop in col.props) {
+            if (col.props.hasOwnProperty(prop)) {
+              props[prop] = this.replaceProps(col, col.props[prop])
+            }
+          }
+        }
+        // 判断select是否允许输入
+        if (col.type === FormTypes.select && (col.allowInput === true || col.allowSearch === true)) {
+          props['showSearch'] = true
+        }
+
+        // 判断是否是禁用的列
+        props['disabled'] = (typeof col['disabled'] === 'boolean' ? col['disabled'] : props['disabled'])
+
+        // 判断是否为禁用的行
+        if (props['disabled'] !== true) {
+          props['disabled'] = ((this.disabledRowIds || []).indexOf(row.id) !== -1)
+        }
+
+        // 判断是否禁用全部组件
+        if (this.disabled === true) {
+          props['disabled'] = true
+        }
+
+        return props
+      },
+      /** upload 辅助方法:获取 headers */
+      uploadGetHeaders(row, column) {
+        let headers = {}
+        if (column.token === true) {
+          headers['X-Access-Token'] = this.accessToken
+        }
+        return headers
+      },
+      /** 上传请求地址 */
+      getUploadAction(value) {
+        if (!value) {
+          return window._CONFIG['domianURL'] + '/sys/common/upload'
+        } else {
+          return value
+        }
+      },
+      /** 预览图片地址 */
+      getCellImageView(id) {
+        let currUploadObj = this.uploadValues[id] || null
+        if (currUploadObj) {
+          if(currUploadObj['url']){
+            return currUploadObj['url'];
+          }else if(currUploadObj['path']){
+            let readpath = currUploadObj['path'].split(',')[0]
+            return getFileAccessHttpUrl(readpath)
+          }
+        }
+        return ''
+      },
+      /** popup回调 */
+      popupCallback(value, others, id, row, column, index) {
+        // 存储输入的值
+        this.popupValues[id] = value
+        if (others) {
+          Object.keys(others).map((key) => {
+            this.columns.map(k=>{
+              if(k.key === key){
+                let tempId = id.substring(id.indexOf(this.caseIdPrefix))
+                if(k.type === 'date'){
+                  this.handleChangeJDateCommon(others[key], key+tempId, {id:tempId}, k, false)
+                }else if(k.type === 'datetime'){
+                  this.handleChangeJDateCommon(others[key], key+tempId, {id:tempId}, k, true)
+                }else{
+                  this.inputValues[index][key] = others[key]
+                }
+              }
+            })
+          })
+        }
+        // 做单个表单验证
+        this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
+        // 触发valueChange 事件
+        this.elemValueChange('input', row, column, value)
+        // 更新form表单的值
+        this.$nextTick(() => {
+          this.forceUpdateFormValues()
+        })
+      },
+      /** popupJsh回调 */
+      popupJshCallback(value, others, id, row, column, index) {
+        // 存储输入的值
+        this.popupJshValues[id] = value
+        if (others) {
+          Object.keys(others).map((key) => {
+            this.columns.map(k=>{
+              if(k.key === key){
+                let tempId = id.substring(id.indexOf(this.caseIdPrefix))
+                if(k.type === 'date'){
+                  this.handleChangeJDateCommon(others[key], key+tempId, {id:tempId}, k, false)
+                }else if(k.type === 'datetime'){
+                  this.handleChangeJDateCommon(others[key], key+tempId, {id:tempId}, k, true)
+                }else{
+                  this.popupJshValues[index][key] = others[key]
+                }
+              }
+            })
+          })
+        }
+        // 做单个表单验证
+        this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
+        // 触发valueChange 事件
+        this.elemValueChange('input', row, column, value)
+        // 更新form表单的值
+        this.$nextTick(() => {
+          this.forceUpdateFormValues()
+        })
+      },
+      /** select输入框回显 */
+      getSelectValue(id) {
+        return this.selectValues[id]
+      },
+      /** popup输入框回显 */
+      getPopupValue(id) {
+        return this.popupValues[id]
+      },
+      /** popupJsh输入框回显 */
+      getPopupJshValue(id) {
+        return this.popupJshValues[id]
+      },
+      /** popupJsh构造传值 */
+      getPopupJshRows(row) {
+        let { values } = this.getValuesSync({ validate: false, rowIds: [row.id] })
+        return JSON.stringify(values[0])
+      },
+      handleRadioChange(value, id, row, column) {
+        this.radioValues = this.bindValuesChange(value, id, 'radioValues')
+        // 做单个表单验证
+        this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
+        // 触发valueChange 事件
+        this.elemValueChange(FormTypes.radio, row, column, value)
+      },
+      handleMultiSelectChange(value, id, row, column) {
+        this.multiSelectValues = this.bindValuesChange(value, id, 'multiSelectValues')
+        // 做单个表单验证
+        this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
+        // 触发valueChange 事件
+        this.elemValueChange(FormTypes.list_multi, row, column, value)
+      },
+      handleSearchSelectChange(value, id, row, column) {
+        this.searchSelectValues = this.bindValuesChange(value, id, 'searchSelectValues')
+        this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
+        this.elemValueChange(FormTypes.sel_search, row, column, value)
+      },
+      filterOption(input, option) {
+        return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
+      },
+      getEllipsisWord(content, len){
+        if(!content || content.length==0){
+          return ''
+        }
+        if(content.length>len){
+          return content.substr(0,len)
+        }
+        return content;
+      },
+      /** 回车后自动跳到下一个input **/
+      autoJumpNextInputBill() {
+        let that = this
+        let inputDom = $(".ant-modal-cust-warp:visible").find("#billModal");
+        inputDom.find("input:visible:not(:checkbox)").off("keydown").on("keydown", function(e){
+          //响应回车键按下的处理
+          e = event || window.event || arguments.callee.caller.arguments[0];
+          //捕捉是否按键为回车键,可百度JS键盘事件了解更多
+          if(e && e.keyCode==13) {
+            //捕捉inputDom下的文本输入框的个数
+            let inputs = inputDom.find("input:visible:not(:checkbox)");
+            let idx = inputs.index(this); // 获取当前焦点输入框所处的位置
+            if (idx == inputs.length - 1) { // 判断是否是最后一个输入框
+              let curKey = e.which;
+              if (curKey == 13) {
+                //新增行
+                that.handleClickAdd();
+                //进行下一行的自动聚焦
+                setTimeout(function() {
+                  inputs = inputDom.find("input:visible:not(:checkbox)");
+                  inputs[idx + 1].focus(); // 设置焦点
+                  inputs[idx + 1].select(); // 选中文字
+                },100)
+              }
+            } else {
+              inputs[idx + 1].focus(); // 设置焦点
+              inputs[idx + 1].select(); // 选中文字
+            }
+          }
+        })
+      },
+      /** 自动选中特殊的key **/
+      autoSelectBySpecialKey(specialKey, orderNum) {
+        let trs = this.getElement('tbody').getElementsByClassName('tr')
+        let trEls = []
+        if(trs && trs.length && orderNum>=1) {
+          trEls.push(trs[orderNum-1])
+        }
+        trEls.forEach(tr => {
+          let { idx } = tr.dataset
+          let value = this.inputValues[idx]
+          for (let key in value) {
+            if (value.hasOwnProperty(key)) {
+              let elid = `${key}${value.id}`
+              let el = document.getElementById(elid)
+              if (el && key === specialKey) {
+                el.select()
+              }
+            }
+          }
+        })
+      }
+    },
+    beforeDestroy() {
+      this.destroyCleanGroupRequest = true
+    },
+  }
+</script>
+
+<style lang="less" scoped>
+
+  .action-button {
+    margin-bottom: 8px;
+
+    .gap {
+      padding-left: 8px;
+    }
+
+  }
+
+  /* 设定边框参数 */
+  @borderColor: #e8e8e8;
+  @border: 1px solid @borderColor;
+  /* tr & td 之间的间距 */
+  @spacing: 2px;
+
+  .input-table {
+    max-width: 100%;
+    overflow-x: hidden;
+    overflow-y: hidden;
+    position: relative;
+    border: @border;
+
+    .thead, .tbody {
+
+      .tr, .td {
+        display: flex;
+      }
+
+      .td {
+
+        /*border-right: 1px solid red;*/
+        /*color: white;*/
+        /*background-color: black;*/
+        /*margin-right: @spacing !important;*/
+
+        padding-left: @spacing;
+        flex-direction: column;
+
+        &.td-cb, &.td-num {
+          min-width: 4%;
+          max-width: 45px;
+          margin-right: 0;
+          padding-left: 0;
+          padding-right: 0;
+          justify-content: center;
+          align-items: center;
+        }
+
+        &.td-ds {
+          margin-right: 0;
+          padding-left: 0;
+          padding-right: 0;
+          justify-content: center;
+          align-items: center;
+
+          .td-ds-icons {
+            position: relative;
+            cursor: move;
+            width: 100%;
+            /*padding: 25% 0;*/
+            height: 100%;
+
+            .anticon-align-left,
+            .anticon-align-right {
+              position: absolute;
+              top: 30%;
+            }
+
+            .anticon-align-left {
+              left: 25%;
+            }
+
+            .anticon-align-right {
+              right: 25%;
+            }
+          }
+
+
+        }
+
+      }
+
+    }
+
+    .thead {
+      overflow-y: scroll;
+      overflow-x: hidden;
+      border-bottom: @border;
+
+      /** 隐藏thead的滑块   */
+
+      &::-webkit-scrollbar-thumb {
+        box-shadow: none !important;
+        background-color: transparent !important;
+      }
+
+      .tr {
+        min-width: 100%;
+        overflow-y: scroll;
+      }
+
+      .td {
+        /*flex: 1;*/
+        padding: 8px @spacing;
+        justify-content: center;
+      }
+
+    }
+
+    .tbody {
+      position: relative;
+      top: 0;
+      left: 0;
+      overflow-x: hidden;
+      overflow-y: hidden;
+      min-height: 61px;
+      /*max-height: 400px;*/
+      min-width: 100%;
+
+      .tr-nodata {
+        color: #999;
+        line-height: 61px;
+        text-align: center;
+      }
+
+      .tr {
+        /*line-height: 50px;*/
+
+        border-bottom: @border;
+        transition: background-color 300ms;
+        width: 100%;
+        position: absolute;
+        left: 0;
+        z-index: 10;
+
+        &.tr-checked {
+          background-color: #fafafa;
+        }
+
+        &:hover {
+          background-color: #E6F7FF;
+        }
+
+      }
+
+      .tr-expand {
+        position: relative;
+        z-index: 9;
+        background-color: white;
+      }
+
+      .td {
+        /*flex: 1;*/
+        padding: 6px @spacing 6px 0;
+        justify-content: center;
+
+        &:last-child {
+          padding-right: @spacing;
+        }
+
+        input {
+          font-variant: tabular-nums;
+          box-sizing: border-box;
+          margin: 0;
+          list-style: none;
+          position: relative;
+          display: inline-block;
+          padding: 2px 2px;
+          width: 100%;
+          height: 32px;
+          font-size: 14px;
+          line-height: 1.5;
+          color: rgba(0, 0, 0, 0.65);
+          background-color: #fff;
+          border: 1px solid #d9d9d9;
+          border-radius: 4px;
+          transition: all 0.3s;
+          outline: none;
+
+          &:hover {
+            border-color: #4D90FE
+          }
+
+          &:focus {
+            border-color: #40a9ff;
+            box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
+            border-right-width: 1px !important;
+          }
+
+          &:disabled {
+            color: rgba(0, 0, 0, 0.25);
+            background: #f5f5f5;
+            cursor: not-allowed;
+          }
+
+          /* 设置placeholder的颜色 */
+
+          &::-webkit-input-placeholder { /* WebKit browsers */
+            color: #ccc;
+          }
+
+          &:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
+            color: #ccc;
+          }
+
+          &::-moz-placeholder { /* Mozilla Firefox 19+ */
+            color: #ccc;
+          }
+
+          &:-ms-input-placeholder { /* Internet Explorer 10+ */
+            color: #ccc;
+          }
+
+        }
+
+        .j-editable-image {
+          height: 32px;
+          max-width: 100px !important;
+          cursor: pointer;
+
+          &:hover {
+            opacity: 0.8;
+          }
+
+          &:active {
+            opacity: 0.6;
+          }
+
+        }
+
+        .td-span {
+          font-variant: tabular-nums;
+          box-sizing: border-box;
+          margin: 0;
+          list-style: none;
+          position: relative;
+          display: inline-block;
+          padding: 2px 2px;
+          width: 100%;
+          height: 32px;
+          font-size: 14px;
+          line-height: 1.9;
+          color: rgba(0, 0, 0, 0.65);
+          background-color: #fff;
+          border: 1px dashed #d9d9d9;
+          border-radius: 4px;
+          transition: all 0.3s;
+          outline: none;
+          overflow:hidden;
+          white-space:nowrap;
+          text-overflow:ellipsis;
+        }
+
+      }
+
+    }
+
+    .scroll-view {
+      overflow: auto;
+      overflow-y: scroll;
+    }
+
+    .thead, .thead .tr, .scroll-view {
+      @scrollBarSize: 6px;
+      /* 定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
+
+      &::-webkit-scrollbar {
+        width: @scrollBarSize;
+        height: @scrollBarSize;
+        background-color: transparent;
+      }
+
+      /* 定义滚动条轨道 */
+
+      &::-webkit-scrollbar-track {
+        background-color: #f0f0f0;
+      }
+
+      /* 定义滑块 */
+
+      &::-webkit-scrollbar-thumb {
+        background-color: #eee;
+        box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
+
+        &:hover {
+          background-color: #bbb;
+        }
+
+        &:active {
+          background-color: #888;
+        }
+      }
+
+    }
+
+    .thead .tr {
+
+      &::-webkit-scrollbar-track {
+        background-color: transparent;
+      }
+
+      /* IE模式下隐藏 */
+      -ms-overflow-style: none;
+      -ms-scroll-chaining: chained;
+      -ms-content-zooming: zoom;
+      -ms-scroll-rails: none;
+      -ms-content-zoom-limit-min: 100%;
+      -ms-content-zoom-limit-max: 500%;
+      -ms-scroll-snap-type: proximity;
+      -ms-scroll-snap-points-x: snapList(100%, 200%, 300%, 400%, 500%);
+    }
+
+  }
+
+</style>

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików