zhuliu 10 ماه پیش
والد
کامیت
d8295d6d1e
42فایلهای تغییر یافته به همراه23065 افزوده شده و 0 حذف شده
  1. 23 0
      file-contrast/.gitignore
  2. 24 0
      file-contrast/README.md
  3. 5 0
      file-contrast/babel.config.js
  4. 25 0
      file-contrast/jsconfig.json
  5. 20171 0
      file-contrast/package-lock.json
  6. 55 0
      file-contrast/package.json
  7. BIN
      file-contrast/public/favicon.ico
  8. 17 0
      file-contrast/public/index.html
  9. 177 0
      file-contrast/src/App.vue
  10. 8 0
      file-contrast/src/api/index.js
  11. 5 0
      file-contrast/src/api/permission.js
  12. 31 0
      file-contrast/src/assets/css/fix.scss
  13. 120 0
      file-contrast/src/assets/css/layout.less
  14. BIN
      file-contrast/src/assets/image/login_background.jpg
  15. BIN
      file-contrast/src/assets/image/logo.jpg
  16. BIN
      file-contrast/src/assets/logo-3.png
  17. BIN
      file-contrast/src/assets/logo.png
  18. 58 0
      file-contrast/src/components/HelloWorld.vue
  19. 8 0
      file-contrast/src/config/index.js
  20. 41 0
      file-contrast/src/main.js
  21. 67 0
      file-contrast/src/router/index.js
  22. 3 0
      file-contrast/src/store/getters.js
  23. 13 0
      file-contrast/src/store/index.js
  24. 21 0
      file-contrast/src/store/modules/user.js
  25. 127 0
      file-contrast/src/utils/axios.js
  26. 583 0
      file-contrast/src/utils/common.js
  27. 6 0
      file-contrast/src/utils/constants.js
  28. 53 0
      file-contrast/src/utils/file.js
  29. 134 0
      file-contrast/src/utils/formatTime.js
  30. 78 0
      file-contrast/src/utils/index.js
  31. 10 0
      file-contrast/src/utils/permissions.js
  32. 33 0
      file-contrast/src/utils/storage.js
  33. 238 0
      file-contrast/src/views/caseList/index.vue
  34. 187 0
      file-contrast/src/views/differenceDetails/components/differenceDetails.vue
  35. 26 0
      file-contrast/src/views/differenceDetails/index.vue
  36. 95 0
      file-contrast/src/views/index/index.vue
  37. 206 0
      file-contrast/src/views/layout/components/UserBar.vue
  38. 126 0
      file-contrast/src/views/layout/index.vue
  39. 25 0
      file-contrast/src/views/layout/mixins/index.js
  40. 110 0
      file-contrast/src/views/login/compoments/resePassword.vue
  41. 50 0
      file-contrast/src/views/login/index.vue
  42. 106 0
      file-contrast/vue.config.js

+ 23 - 0
file-contrast/.gitignore

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

+ 24 - 0
file-contrast/README.md

@@ -0,0 +1,24 @@
+# file-contrast
+
+## Project setup
+```
+npm install
+```
+
+### Compiles and hot-reloads for development
+```
+npm run serve
+```
+
+### Compiles and minifies for production
+```
+npm run build
+```
+
+### Lints and fixes files
+```
+npm run lint
+```
+
+### Customize configuration
+See [Configuration Reference](https://cli.vuejs.org/config/).

+ 5 - 0
file-contrast/babel.config.js

@@ -0,0 +1,5 @@
+module.exports = {
+  presets: [
+    '@vue/cli-plugin-babel/preset'
+  ]
+}

+ 25 - 0
file-contrast/jsconfig.json

@@ -0,0 +1,25 @@
+{
+  "compilerOptions": {
+    // "target": "es5",
+    // "module": "esnext",
+    // "baseUrl": "./",
+    // "moduleResolution": "node",
+    // "paths": {
+    //   "@/*": [
+    //     "src/*"
+    //   ]
+    // },
+    // "lib": [
+    //   "esnext",
+    //   "dom",
+    //   "dom.iterable",
+    //   "scripthost"
+    // ]
+    "baseUrl": "./",
+    "paths": {
+      "@/*": [
+        "src/*"
+      ]
+    }
+  }
+}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 20171 - 0
file-contrast/package-lock.json


+ 55 - 0
file-contrast/package.json

@@ -0,0 +1,55 @@
+{
+  "name": "file-contrast",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "axios": "^1.7.8",
+    "babel-eslint": "^10.1.0",
+    "core-js": "^3.8.3",
+    "element-ui": "^2.15.14",
+    "js-base64": "^3.7.7",
+    "js-cookie": "^3.0.5",
+    "less": "^4.2.1",
+    "less-loader": "^12.2.0",
+    "moment": "^2.30.1",
+    "sass": "^1.81.0",
+    "sass-loader": "^16.0.3",
+    "vue": "^2.6.14",
+    "vue-router": "^3.2.0",
+    "vuex": "^3.4.0"
+  },
+  "devDependencies": {
+    "@babel/core": "^7.12.16",
+    "@babel/eslint-parser": "^7.12.16",
+    "@vue/cli-plugin-babel": "~5.0.0",
+    "@vue/cli-plugin-eslint": "~5.0.0",
+    "@vue/cli-service": "~5.0.0",
+    "eslint": "^7.32.0",
+    "eslint-plugin-vue": "^8.0.3",
+    "vue-template-compiler": "^2.6.14"
+  },
+  "eslintConfig": {
+    "root": true,
+    "env": {
+      "node": true
+    },
+    "extends": [
+      "plugin:vue/essential",
+      "eslint:recommended"
+    ],
+    "parserOptions": {
+      "parser": "babel-eslint"
+    },
+    "rules": {}
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not dead"
+  ]
+}

BIN
file-contrast/public/favicon.ico


+ 17 - 0
file-contrast/public/index.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <title><%= htmlWebpackPlugin.options.title %></title>
+  </head>
+  <body>
+    <noscript>
+      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+    </noscript>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

+ 177 - 0
file-contrast/src/App.vue

@@ -0,0 +1,177 @@
+<template>
+  <div id="app">
+    <router-view />
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'app',
+  components: {
+  },
+  created() {
+
+  },
+  mounted(){
+    var time = new Date(this.$c.updateTime).getTime()
+    var time2 = new Date().getTime()
+    if(time2 < time){
+        this.$router.push({path: '/'})
+        return false
+    }
+  },
+  methods: {
+
+  }
+}
+</script>
+
+<style lang="less">
+textarea {
+  font-family: 微软雅黑;
+}
+html, body, #app {
+  width: 100%;
+  height: 100%;
+  padding: 0;
+  margin: 0;
+  background: #f4f4f4;
+}
+.height_100{
+  height: 100% !important;
+}
+.cursor{
+  cursor: pointer;
+  &:hover{
+    color:#66b1ff
+  }
+}
+.margin-left_0 {
+  margin-left: 0 !important;
+}
+.margin-top_10 {
+  margin-top: 10px !important;
+}
+.margin-left_10 {
+  margin-left: 10px !important;
+}
+.padding-left_10 {
+  padding-left: 10px;
+}
+.padding-right_10 {
+  padding-right: 10px;
+}
+.margin-left_20 {
+  margin-left: 20px;
+}
+.container-white-no-padding {
+  background: #fff !important;
+  padding: 0 !important;
+}
+.container-common-main {
+  background: #fff;
+  padding: 5px 20px !important;
+  margin-top: 20px !important;
+}
+.footer-common {
+  height: 70px !important;
+  text-align: right;
+}
+.margin-right_20 {
+  margin-right: 20px !important;
+}
+.width_100 {
+  width: 100% !important;
+}
+.margin-bottom_10 {
+  margin-bottom: 10px;
+}
+.float_right {
+  float: right
+}
+.float_left {
+  float: left;
+}
+.color-red {
+  color: red !important;
+}
+.color-black {
+  color: black;
+}
+.padding_0_5 {
+  padding: 0 5px;
+}
+.color-primary {
+  color: #409eff;
+}
+.margin-right_10 {
+  margin-right: 10px !important;
+}
+.text-align_center {
+  text-align: center !important;
+}
+.position_relative {
+  position: relative;
+}
+.no-body-padding-dialog {
+  .el-dialog__body {
+    padding: 0 !important;
+  }
+}
+.custom-table-header > th {
+  word-break: break-word;
+  background-color: #f8f8f9 !important;
+  color: #515a6e;
+  height: 40px;
+  font-size: 13px;
+}
+.el-header {
+  padding: 13px 0 !important;
+  .el-form {
+    .el-form-item {
+      margin-bottom: 0 !important
+    }
+  }
+}
+.custom-drawer-form {
+  .el-drawer__header {
+    padding: 0 20px !important;
+    margin: 15px 0 !important;
+    span {
+      font-size: 18px;
+      color: #303133;
+    }
+  }
+  .el-drawer__body {
+    border-top: 1px solid #e6e6e6;
+  }
+}
+.upload-file {
+  width: 100% !important;
+  .el-upload, .el-upload-dragger {
+    width: 100% !important;
+  }
+  .el-upload-dragger .el-icon-refresh {
+    font-size: 67px;
+    color: #C0C4CC;
+    margin: 40px 0 16px;
+    line-height: 50px;
+  }
+}
+.scrollable {-webkit-overflow-scrolling: touch;}
+::-webkit-scrollbar {width: 5px;height: 5px;}
+::-webkit-scrollbar-thumb {background-color: rgba(50, 50, 50, 0.3);}
+::-webkit-scrollbar-thumb:hover {background-color: rgba(50, 50, 50, 0.6);}
+::-webkit-scrollbar-track {background-color: rgba(50, 50, 50, 0.1);}
+::-webkit-scrollbar-track:hover {background-color: rgba(50, 50, 50, 0.2);}
+.el-aside {border-right: 1px solid #e6e6e6;background: #fff;}
+.el-header {background: #fff;border-bottom: 1px solid #e6e6e6;padding:13px 15px;display: flex;justify-content: space-between;align-items: center;}
+.el-header .left-panel {display: flex;align-items: center;}
+.el-header .right-panel {display: flex;align-items: center;}
+.el-header .right-panel > * + * {margin-left:10px;}
+.el-footer {background: #fff;border-top: 1px solid #e6e6e6;padding:13px 15px !important;}
+.el-main {padding:15px;}
+.el-main.nopadding {padding:0;background: #fff;}
+.el-main {flex-basis: 0}
+.el-container {height: 100%;}
+</style>

+ 8 - 0
file-contrast/src/api/index.js

@@ -0,0 +1,8 @@
+
+
+import permission from "./permission";
+
+export default {
+
+  ...permission
+}

+ 5 - 0
file-contrast/src/api/permission.js

@@ -0,0 +1,5 @@
+import axios from '@/utils/axios'
+
+export default {
+
+}

+ 31 - 0
file-contrast/src/assets/css/fix.scss

@@ -0,0 +1,31 @@
+/* 覆盖element-plus样式 */
+.el-menu {border: none!important;}
+.el-menu .el-menu-item a {color: inherit;text-decoration: none;display: block;width:100%;height:100%;position: absolute;top:0px;left:0px;}
+.el-form-item-msg {font-size: 12px;color: #999;clear: both;}
+.el-drawer__body {overflow: auto;padding:0;}
+.el-popconfirm__main {margin: 14px 0;}
+.el-card__header {border-bottom: 0;font-size: 17px;font-weight: bold;padding:15px 20px 0px 20px;}
+.el-dialog__title {font-size: 17px;font-weight: bold;}
+.el-drawer__header>:first-child {font-size: 17px;font-weight: bold;}
+.el-tree.menu .el-tree-node__content {height:36px;}
+.el-tree.menu .el-tree-node__content .el-tree-node__label .icon {margin-right: 5px;}
+.el-progress__text {font-size: 12px!important;}
+.el-progress__text i {font-size: 14.4px!important;}
+.el-step.is-horizontal .el-step__line {height:1px;}
+.el-step__title {font-size: 14px;}
+.drawerBG {background: #f6f8f9;}
+.el-button+.el-dropdown {margin-left: 10px;}
+.el-button-group+.el-dropdown {margin-left: 10px;}
+.el-tag+.el-tag {margin-left: 10px;}
+.el-button-group+.el-button-group {margin-left: 10px;}
+.el-tabs__nav-wrap::after {height: 1px;}
+.el-table th.is-sortable {transition: .1s;}
+.el-table th.is-sortable:hover {background: #eee;}
+.el-table .el-table__body-wrapper {background: #f6f8f9;}
+.el-col .el-card {margin-bottom: 15px;}
+.el-icon {font-size: inherit;}
+
+/* 覆盖tinymce样式 */
+.sceditor .tox-tinymce {border: 1px solid #DCDFE6;}
+.sceditor .tox .tox-statusbar {border-top: 1px solid #DCDFE6;}
+.sceditor .tox .tox-toolbar__primary {background: #f6f8f9;border-bottom: 1px solid #DCDFE6;}

+ 120 - 0
file-contrast/src/assets/css/layout.less

@@ -0,0 +1,120 @@
+.el-card__header {
+  padding: 10px 20px !important;
+  .card-header {
+    .el-form {
+      .el-form-item{
+        margin-bottom: 0 !important;
+      }
+    }
+  }
+}
+.pagination {
+  text-align: right;
+  // margin: 20px 0;
+}
+.vue-treeselect__control {
+  padding-left: 10px !important;
+}
+.vue-treeselect__single-value {
+  padding-top: 3px !important;
+  color: #606266 !important;
+}
+.vue-treeselect__placeholder {
+  padding-top: 3px !important;
+}
+.el-input__inner {
+  color: #606266 !important;
+}
+.el-cascader-menu {
+  height: 350px !important;
+}
+.el-cascader-menu__wrap {
+  height: 100% !important;
+  overflow-x: hidden !important;
+}
+.sc-form-table .sc-form-table-handle {text-align: center;}
+.sc-form-table .sc-form-table-handle span {display: inline-block;}
+.sc-form-table .sc-form-table-handle button {display: none;}
+.sc-form-table .hover-row .sc-form-table-handle span {display: none;}
+.sc-form-table .hover-row .sc-form-table-handle button {display: inline-block;}
+.analyse-menu {
+  .el-menu {
+    border-right: 0 !important;
+  }
+  .el-scrollbar__wrap {
+    overflow-x: hidden !important;
+  }
+  .el-menu-item.is-active {
+    background: #ecf5ff !important;
+    color: #409EFF!important;
+  }
+}
+.chart-option {
+  position: fixed;
+  display: inline;
+  right: 40px;
+  top: 450px;
+  padding: 10px;
+  background-color: rgba(0, 0, 0, 0.35);
+  color: #fff;
+  cursor: pointer;
+  z-index: 999;
+  &:hover {
+    background: rgba(0, 0, 0, .5);
+  }
+}
+.search-option {
+  position: fixed;
+  display: inline;
+  right: 40px;
+  top: 450px;
+  padding: 10px;
+  background-color: rgba(0, 0, 0, 0.35);
+  color: #fff;
+  cursor: pointer;
+  z-index: 999;
+  &:hover {
+    background: rgba(0, 0, 0, .5);
+  }
+}
+.tree-item-tabs {
+  .el-tabs__header {
+    margin-top: 35px !important;
+    margin-left: 20px;
+  }
+}
+.chart-title {
+  padding: 20px;
+  span {
+    font-weight: bold;
+    font-size: 16px;
+  }
+}
+.box-card {
+  margin-bottom: 20px;
+  cursor: pointer;
+  .el-card__body {
+    padding: 0 !important;
+  }
+  .body {
+    padding: 20px;
+    font-size: 14px;
+  }
+  .active-right-box {
+    position: absolute;
+    right: 0;
+    top: 0;
+    width: 0;
+    height: 0;
+    border-left: 50px solid transparent;
+    border-top: 50px solid green;
+  }
+  .active-right-text {
+    position: absolute;
+    right: 0;
+    top: 10px;
+    font-size: 12px;
+    color: #fff;
+    transform: rotate(45deg);
+  }
+}

BIN
file-contrast/src/assets/image/login_background.jpg


BIN
file-contrast/src/assets/image/logo.jpg


BIN
file-contrast/src/assets/logo-3.png


BIN
file-contrast/src/assets/logo.png


+ 58 - 0
file-contrast/src/components/HelloWorld.vue

@@ -0,0 +1,58 @@
+<template>
+  <div class="hello">
+    <h1>{{ msg }}</h1>
+    <p>
+      For a guide and recipes on how to configure / customize this project,<br>
+      check out the
+      <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
+    </p>
+    <h3>Installed CLI Plugins</h3>
+    <ul>
+      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
+      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
+    </ul>
+    <h3>Essential Links</h3>
+    <ul>
+      <li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
+      <li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
+      <li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
+      <li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
+      <li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
+    </ul>
+    <h3>Ecosystem</h3>
+    <ul>
+      <li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
+      <li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
+      <li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
+      <li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
+      <li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
+    </ul>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'HelloWorld',
+  props: {
+    msg: String
+  }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+h3 {
+  margin: 40px 0 0;
+}
+ul {
+  list-style-type: none;
+  padding: 0;
+}
+li {
+  display: inline-block;
+  margin: 0 10px;
+}
+a {
+  color: #42b983;
+}
+</style>

+ 8 - 0
file-contrast/src/config/index.js

@@ -0,0 +1,8 @@
+export default {
+    baseURL: "/api",
+    updateTime:'2023-08-04 08:00:00',
+    host: window.location.host,
+    url:process.env.NODE_ENV === 'production' ? 'https://xsip.cn' : 'http://192.168.2.24:8803',
+    staticURL: process.env.NODE_ENV === 'production' ? 'https://xsip.cn/onlinePreview' : 'http://192.168.2.24:8879/onlinePreview',
+    WebSocketPath: process.env.NODE_ENV === 'production' ? 'wss://xsip.cn' : 'ws://192.168.2.24:8879',
+}

+ 41 - 0
file-contrast/src/main.js

@@ -0,0 +1,41 @@
+import Vue from 'vue'
+import App from './App.vue'
+
+import './utils/axios'
+
+import router from './router'
+import store from './store'
+import constants from './utils/constants'
+
+import Element from 'element-ui'
+import 'element-ui/lib/theme-chalk/index.css'
+
+import { formatTableDate } from "./utils";
+import Config from './config'
+import Storage from './utils/storage'
+
+import api from './api'
+
+require('./assets/css/fix.scss')
+require('./assets/css/layout.less')
+
+//全局js
+import commonJS from '@/utils/common.js'
+Vue.prototype.$commonJS = commonJS
+
+Vue.config.productionTip = false
+
+Vue.prototype.$constants = constants
+Vue.prototype.$api = api
+Vue.prototype.$d = formatTableDate
+Vue.prototype.$p = Config.staticURL
+Vue.prototype.$c = Config
+Vue.prototype.$s = Storage
+
+Vue.use(Element)
+
+new Vue({
+  router,
+  store,
+  render: h => h(App),
+}).$mount('#app')

+ 67 - 0
file-contrast/src/router/index.js

@@ -0,0 +1,67 @@
+import Vue from 'vue'
+import VueRouter from 'vue-router'
+
+Vue.use(VueRouter)
+
+const originalPush = VueRouter.prototype.push
+VueRouter.prototype.push = function push(location) {
+  return originalPush.call(this, location).catch(err => err)
+}
+const routes = [
+  {
+    path: "/",
+    component: () => import('@/views/index'),
+  },
+
+  {
+    path:'/login',
+    name:'Login',
+    meta: {
+      title: '系统登录'
+    },
+    component: () => import("@/views/login/index.vue")
+  },
+  {
+    path: "",
+    component: () => import('@/views/layout/index.vue'),
+    children:[
+      {
+        path:'/caseList',
+        name:'CaseList',
+        meta: {
+          title: '案件列表'
+        },
+        component: () => import("@/views/caseList/index.vue")
+      },
+      {
+        path:'/differenceDetails',
+        name:'DifferenceDetails',
+        meta: {
+          title: '案件列表'
+        },
+        component: () => import("@/views/differenceDetails/index.vue")
+      },
+    ]
+  },
+  
+ 
+]
+
+const router = new VueRouter({
+  mode: 'history',
+  base: '/',
+  routes
+})
+
+router.beforeEach((to, from, next) => {
+  if (to.meta.title) {
+    document.title = to.meta.title;
+  }
+  next();
+})
+
+router.afterEach((to, from) => {
+
+});
+
+export default router

+ 3 - 0
file-contrast/src/store/getters.js

@@ -0,0 +1,3 @@
+export default {
+  userinfo: state => state.user.userinfo,
+}

+ 13 - 0
file-contrast/src/store/index.js

@@ -0,0 +1,13 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import user from './modules/user'
+import getters from './getters'
+
+Vue.use(Vuex)
+
+export default new Vuex.Store({
+  modules: {
+    user,
+  },
+  getters
+})

+ 21 - 0
file-contrast/src/store/modules/user.js

@@ -0,0 +1,21 @@
+import storage from '@/utils/storage'
+import constants from "@/utils/constants";
+import Api from '@/api'
+
+export default {
+  state: {
+    userinfo: storage.getObj(constants.userinfo),
+  },
+
+  mutations: {
+    SET_USERINFO: (state, userinfo) => {
+      state.userinfo = userinfo
+      storage.setObj(constants.userinfo, userinfo)
+    },
+    
+  },
+
+  actions: {
+
+  }
+}

+ 127 - 0
file-contrast/src/utils/axios.js

@@ -0,0 +1,127 @@
+"use strict";
+
+import Vue from 'vue';
+import axios from "axios";
+import constants from "@/utils/constants";
+import storage from "@/utils/storage";
+import { Notification, Loading } from 'element-ui'
+import Store from '@/store'
+import Config from '@/config'
+import Router from '@/router'
+
+// Full config:  https://github.com/axios/axios#request-config
+// axios.defaults.baseURL = process.env.baseURL || process.env.apiUrl || '';
+// axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
+// axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
+
+// 是否正在刷新的标记
+let isRefreshing = false
+// 重试队列,每一项将是一个待执行的函数形式
+let retryRequests = []
+let loadingInstance = {}
+let config = {
+  baseURL: Config.baseURL
+  // timeout: 60 * 1000, // Timeout
+  // withCredentials: true, // Check cross-site Access-Control
+};
+
+const _axios = axios.create(config);
+
+_axios.interceptors.request.use(
+  function (config) {
+    // Do something before request is sent
+    const token = storage.getStr(constants.token)
+    if (token) {
+      // config.headers[constants.tokenHeader] = token
+    }
+    config.headers['x-project-id'] = Store.getters.projectId || ''
+    if (config.loading) {
+      loadingInstance = Loading.service({})
+    }
+    // console.log(config)
+    return config;
+  },
+  function (error) {
+    // Do something with request error
+    return Promise.reject(error);
+  }
+);
+
+// Add a response interceptor
+_axios.interceptors.response.use(
+  function (response) {
+    const config = response.config
+    if (config.loading) {
+      loadingInstance.close()
+    }
+    let { code, message } = response.data
+    if (code === 200 || config.responseType === 'blob') {
+      isRefreshing = false
+      return response.data
+    }
+    if (code === 401) {
+      Router.push({
+        path: `${Store.getters.prefix}/login`
+      }).then(r => {})
+      return Promise.reject(response.data)
+    }
+    if (code === 402) {
+      Notification.error({
+        title: '权限不足',
+        message: message
+      })
+      return Promise.reject(response.data)
+    }
+    if(code == 806){
+      Notification.error({
+        title: '账号未启用',
+        message: message
+      })
+      return Promise.reject(response.data)
+    }
+    if (code === 500) {
+      // TODO
+      return Promise.reject(response.data)
+    } else if (code === 0) {
+      isRefreshing = false
+      Notification.error({
+        title: '请求错误',
+        message: message
+      })
+      return Promise.reject(response.data)
+    } else if (code !== 401) {
+    }
+    // Do something with response data
+    // return response;
+  },
+  function (error) {
+    // Do something with response error
+    Notification.error({
+      title: '系统错误',
+      message: error.response.data.message
+    })
+    isRefreshing = false
+    return Promise.reject(error);
+  }
+);
+
+Plugin.install = function (Vue) {
+  Vue.axios = _axios;
+  window.axios = _axios;
+  Object.defineProperties(Vue.prototype, {
+    axios: {
+      get() {
+        return _axios;
+      }
+    },
+    $axios: {
+      get() {
+        return _axios;
+      }
+    },
+  });
+};
+
+Vue.use(Plugin)
+
+export default _axios;

+ 583 - 0
file-contrast/src/utils/common.js

@@ -0,0 +1,583 @@
+import api from '@/api'
+import router from '../router'
+import { Message } from 'element-ui'
+import { Base64 } from 'js-base64';
+import Config from '@/config'
+import moment from "moment";
+export default {
+  /**
+   * 防抖
+   * @param {Function} func 
+   * @param {Number} duration 
+   * @returns 
+   */
+  debounce(func, duration = 500) {
+    let timer;
+    return () => {
+      clearTimeout(timer)
+      timer = setTimeout(func, duration)
+    }
+  },
+  /**
+   * 节流
+   * @param {Function} func 
+   * @param {Number} delay 
+   * @returns 
+   */
+  throttle(func, delay = 2000) {
+    let flag = true
+    return () => {
+      if (!flag) return;
+      flag = false;
+      setTimeout(() => {
+        func()
+        flag = true
+      }, delay)
+    }
+  },
+
+  /**
+   * 树形数据转成扁平数据
+   * @param {Array} data 树形数据
+   * @param {Object} prop 配置
+   * @returns 
+   */
+treeToArray(data,prop={children:children}){
+  let res = []
+  data.forEach(el=>{
+    res.push(el)
+    el[prop.children] && res.push(...this.treeToArray(el[prop.children])) 
+  })
+  return res
+},
+  
+  //对象转数组
+  objectToArray(val){
+    var data = []
+    if (Object.keys(val).length > 0) {
+      Object.keys(val).forEach(item => {
+        if(val[item]){
+          data.push(
+            {
+              key: item,
+              value: val[item]
+            }
+          )
+        }
+        
+      })
+    } else {
+      return []
+    }
+    return data
+  },
+  //转简单数组
+  ArrayToArray(data){
+    var arr = []
+    data.forEach(item => {
+      if (item.type == 3) {
+        arr.push({
+          key:item.value,
+          value:item.searchValue.map(itemValue => {
+            return itemValue.value
+          })
+        })
+      } else {
+        arr.push({
+          key:item.value,
+          value:item.searchValue.label
+        })
+      }
+    })
+    return arr
+  },
+
+  /**
+   * 获取uuid
+   * @param {*} len 长度
+   * @param {*} radix 进制
+   * @returns 
+   */
+  uuid(len, radix) {
+    var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
+    var uuid = [], i;
+    radix = radix || chars.length;
+    if (len) {
+      for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix];
+    } else {
+      var r;
+      uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
+      uuid[14] = '4';
+      for (i = 0; i < 36; i++) {
+        if (!uuid[i]) {
+          r = 0 | Math.random()*16;
+          uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
+        }
+      }
+    }
+    return uuid.join('');
+  },
+
+  /**
+   * 有空格添加引号
+   * @param {String} val 
+   * @returns 
+   */
+  AddQuotationMarks(val){
+    var sign = ['and','AND','OR','or','NOT','not']
+    if(typeof val != 'string'){
+      val = val.toString()
+    }
+    var arr = val.split(/(\s+ and | AND | OR | or | NOT | not \s+)/g)
+    var regex = /\s/
+    var regex1 = /^["].*["]$|^['].*[']$|^[“].*[”]$|^[‘].*[’]$/
+    var values = []
+    for(var i = 0;i<arr.length;i++){
+      if(sign.includes(arr[i].trim())){
+        values.push(arr[i])
+        continue;
+      }
+      if(regex.test(arr[i])){
+        if(!regex1.test(arr[i])){
+          var str = `"${arr[i]}"`
+          values.push(str)
+        }
+        continue;
+      }
+      values.push(arr[i])
+    }
+    return values
+  },
+
+  //object转字符串
+  objectToString(val) {
+    var data = []
+    if (val.constructor == Object) {
+      if (Object.keys(val).length > 0) {
+        Object.keys(val).forEach(item => {
+          data.push(
+            {
+              key: item,
+              value: val[item]
+            }
+          )
+        })
+      } else {
+        return ''
+      }
+    } else if (val.constructor == Array) {
+      if (val.length > 0) {
+        data = val
+      } else {
+        return ''
+      }
+    } else {
+      return val
+    }
+    // 生成字符串
+    var str = ''
+    data.forEach(item => {
+      var value = ''
+      if (Array.isArray(item.value)) {
+        value = '('
+        item.value.forEach((i,index)=>{
+          if(/\s/.test(i)){
+            value += `"${i}"`
+          }else{
+            value += i
+          }
+          if(index!= item.value.length- 1){
+            value += ' OR '
+          }
+        })
+        value += ')'
+      } else {
+        if (item.value) {
+          // var values = this.AddQuotationMarks(item.value)
+          // value = values.join('')
+          value = `(${item.value})`
+        } else {
+          return
+        }
+      }
+      if (str) {
+        str = str + ' AND ' + item.key + '=' + value
+      } else {
+        str = str + item.key + '=' + value
+      }
+
+    })
+    return str
+  },
+
+  /**
+   * 导出Excel
+   * @param {string} head excel头,可为空
+   * @param {String} body excel体,可为空
+   * @param {String} worksheetName  excel的worksheet名,可为空
+   * @param {String} ExcelName excel文件名,可为空
+   */
+  exportExcel(head, body, worksheetName='Sheet1', ExcelName){
+    // Worksheet名
+    const worksheet = worksheetName
+    const uri = 'data:application/vnd.ms-excel;base64,'
+
+    //文件名
+    if(!ExcelName){
+      ExcelName = moment().format('YYYYMMDDHHmmss')
+    }
+    // 下载的表格模板数据
+    const template = `<html xmlns:o="urn:schemas-microsoft-com:office:office"
+      xmlns:x="urn:schemas-microsoft-com:office:excel"
+      xmlns="http://www.w3.org/TR/REC-html40">
+      <head><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet>
+      <x:Name>${worksheet}</x:Name>
+      <x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet>
+      </x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]-->
+      </head><body><table><tr>${head}</tr>${body}</table></body></html>`
+    // 下载模板
+    let link = document.createElement('a')
+    link.setAttribute('href', uri + window.btoa(unescape(encodeURIComponent(template))))
+    link.setAttribute('download', ExcelName + '.xls')
+    link.click()
+    link = null
+  },
+  /**
+   * 获取字段列表
+   * @param {Array} array 
+   * @param {Function} fun 
+   * @param {Object} props 
+   * @returns 
+   */
+  getField(array, fun, props) {
+    var type = {
+      'Array': '3',
+      'DateTime': '2',
+      'String': '1'
+    }
+
+    //获取规范数据
+    var keys = []
+    var arr = []
+    if (props) {
+      keys = Object.keys(props)
+    }
+    //获取所需要的数据
+    var newArray = array.filter(item => {
+      return fun(item)
+    })
+    if (keys.length > 0) {
+      newArray.forEach(item => {
+        var obj = {}
+        keys.forEach(key => {
+          if (key == 'type') {
+            obj[key] = type[item[props[key]]] || item[props[key]]
+          }else if(key == 'value' && props.value == 'value'){
+            obj[key] = item.field
+          }else{
+            obj[key] = item[props[key]]
+          }
+
+        })
+        // 过滤不需要的数据
+        if (['id'].includes(obj.value) == false) {
+          arr.push(obj)
+        }
+      })
+      return arr;
+    }
+    return newArray
+  },
+
+  /**
+   * 获取表格数据
+   * @author zhuliu
+   * @param {object} row 当前行数据
+   * @param {object} field 对应的栏位
+   * @param {String} prop 数据类型为数组对象时所需要使用的字段
+   * @param {object} data 需要获取对应label值时的字典对象(包含数据以及配置及{data,prop})
+   * @param {object} data.prop
+   * @return {String} 返回值
+   * @example
+   * getColumnData({value:1},{type:'String',value:'value'}) //返回字符串1 
+   */
+  getColumnData(row, field, prop, data) {
+    if(field.type == 'function'){
+      return field.useFunction(row)
+    }
+    var text = this.getDataByField(row,field.value || field.field)
+    
+     if (field.type == 'Array') {
+      if (text && text.length > 0) {
+        if (prop) {
+          return text.map(item => {
+            return item[prop]
+          }).join('、')
+        }
+        return text.join('、')
+      } else {
+        return '--'
+      }
+    } else if (field.type == 'Object') {
+      if (!text) {
+        return '--'
+      }
+      if (prop) {
+        return text[prop]
+      }
+      return text.name
+    }
+    else {
+      
+      if (data) {
+        if (data.data.constructor == Array) {
+          var dataProp = data.prop || {
+            label: 'label',
+            value: 'value'
+          }
+          var obj = data.data.find(item => {
+            return item[dataProp.value] == text
+          })
+          if (obj) {
+            return obj[dataProp.label]
+          } else {
+            return '--'
+          }
+        } else {
+          return data.data[text]
+        }
+
+      }
+      if (text == '0') {
+        return '0'
+      }
+      return text || ''
+    }
+  },
+   /**
+   * 根据配置的栏位获取数据
+   */
+   getDataByField(row,field){
+    const parts = this.getFieldArray(field) 
+    let current = row;  
+      
+    for (let i = 0; i < parts.length; i++) {  
+        if (current && current.hasOwnProperty(parts[i])) {  
+            current = current[parts[i]];  
+        } else {  
+            // 如果路径中的某一部分不存在,则返回undefined或自定义的默认值  
+            return ''; // 或者你可以返回null、默认值等  
+        }  
+    }  
+    return current; 
+  },
+  getFieldArray(field){
+    return field.split('.')
+  },
+
+  //查看图片1
+  checkViewer(guid) {
+    if(!guid){
+      return ''
+    }
+    return `/api/fileManager/downloadFile?fileId=${guid}`
+  },
+
+  //查看图片2
+  checkGuid(guid,type='pdf') {
+    if(!guid){
+      return ''
+    }
+    var src = this.checkViewer(guid)
+    var previewUrl = src + '&fullfilename='+guid +'.'+type
+    return `${Config.staticURL}/onlinePreview?url=` + encodeURIComponent(Base64.encode(Config.url+previewUrl))
+  },
+
+  //跳转预览界面
+  previewFile(data,guid,type='pdf'){
+    var FileUrl = this.checkGuid(guid,type)
+    const router1 = router.resolve({
+      path: '/checkFile',
+      query: {
+        // ...data,
+        fileName:data.fileName,
+        FileUrl: FileUrl,
+        guid:guid
+      }
+    })
+    window.open(router1.href, '_blank');
+  },
+
+   //查看专利详情
+   checkPatentDetails(patentNo,projectId) {
+      router.push(
+        {
+          path: '/patentDetails/' + patentNo,
+          query: {
+            projectId: projectId,
+          }
+        }
+      )
+  },
+
+  /**
+   * 查看显示栏位管理
+   * @param {*} type //表格类型
+   * @param {*} patentObj 参数配置
+   * @param {*} url 接口名称
+   * @returns 
+   */
+  async getCustomField(type, patentObj = {},url) {
+    var params = {
+      tableName: type,
+      ...patentObj,
+    }
+    var apiItem = ''
+    if(url){
+      apiItem = url
+    }else{
+      // apiItem = Object.keys(patentObj).length > 0 ? 'getTableColumns':'getCustomField'
+      apiItem = 'getTableColumns'
+    }
+    
+    var a = []
+    await api[apiItem](params).then((response) => {
+      if (response.code == 200) {
+        a = response.data.data
+      }
+    }).catch(error => {
+      return []
+    });
+    return a
+  },
+
+
+  /**
+   * 获取排序字段(sortProp:{order})
+   * @param {Object} param0 {sort,column, prop, order,sortProp,defaultValue,multiple}
+   * @returns 
+   */
+  getSortData({ sort, column, prop, order, sortProp, defaultValue, multiple }) {
+    var sortProp1 = sortProp || {
+      "orderBy": "orderBy",
+      "orderType": 'orderType'
+    }
+    if (multiple) {
+      var sortData = sort
+    } else {
+      var sortData = []
+    }
+    if (order == null) {
+      if (defaultValue) {
+        this.sort = [defaultValue]
+      }
+      this.sort = [
+        {
+          "orderBy": "createTime",
+          "orderType": 1
+        }
+      ]
+    } else {
+      var orderType = {
+        ascending: 0,
+        descending: 1
+      }
+      var params = sortData.find(item => {
+        return item[sortProp1.orderBy] == prop
+      })
+      if (params) {
+        params[sortProp1.orderType] = orderType[order]
+      } else {
+        params = {}
+        params[sortProp1.orderBy] = prop
+        params[sortProp1.orderType] = orderType[order]
+        sortData.push(params)
+      }
+    }
+    return sortData
+  },
+
+  //显示表头数据
+  renderHeaderMethods(h, { column }, message) {
+    if(!column.sortable){
+      return column.label
+    }
+    var message = '点击升序'
+    switch (column.order) {
+      case "":
+        message = '点击升序'
+        break;
+      case "ascending":
+        message = '点击降序'
+        break;
+      case 'descending':
+        message = '点击取消排序'
+        break
+    }
+    return [
+      ,
+      h(
+        'el-tooltip',
+        {
+          props: {
+            content: message, // 鼠标悬停时要展示的文字提示
+            placement: 'top' // 悬停内容展示的位置
+          }
+        },
+        [h('span', column.label)] // 图标
+      )
+    ]
+  },
+
+
+  // 校验文件是否全部上传并处理文件的guid
+  allUploadFile(form) {
+    var arr =[]
+    let allUpload = this.validFile(form.systemFileList,arr)
+    if (allUpload) {
+      Message.warning('文件未全部上传,请耐心等待')
+      return false
+    }
+    if(form.systemFileList && form.systemFileList.length>0){
+      form.fileGuids = form.systemFileList.map(item => {
+        return item.guid
+      })
+    }else{
+      form.fileGuids = []
+    }
+    
+  },
+  //校验文件是否全部上传
+  validFile(data,arr) {
+    if (data && data.length > 0) {
+      for(var i =0;i<data.length;i++){
+        if(data[i] && data[i].guid){
+          arr.push(data[i].guid)
+        }else{
+          return true
+        }
+      }
+      return false
+    } else {
+      return false
+    }
+  },
+  //校验是否全部上传
+  checkUploadFile(data) {
+    if(!data){
+      return []
+    }
+    var arr = []
+    let allUpload = this.validFile(data,arr)
+    if (allUpload) {
+      Message.warning('文件未全部上传,请耐心等待')
+      return false
+    }
+
+    return arr
+  },
+
+
+
+
+}

+ 6 - 0
file-contrast/src/utils/constants.js

@@ -0,0 +1,6 @@
+export default {
+  tokenHeader: "Authorization",
+  token: "token",
+  userinfo: "userinfo",
+  prefix: "prefix",
+}

+ 53 - 0
file-contrast/src/utils/file.js

@@ -0,0 +1,53 @@
+import SparkMD5 from 'spark-md5'
+
+const chunkSize = 10 * 1024 * 1024
+/**
+ * 分片读取文件 MD5
+ */
+export const getFileMD5 = (file, callback) => {
+  const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
+  const fileReader = new FileReader()
+  // 计算分片数
+  const totalChunks = Math.ceil(file.size / chunkSize)
+  // console.log('总分片数:' + totalChunks)
+  let currentChunk = 0
+  const spark = new SparkMD5.ArrayBuffer()
+  loadNext()
+  fileReader.onload = function (e) {
+    try {
+      spark.append(e.target.result)
+    } catch (error) {
+      // console.log('获取Md5错误:' + currentChunk)
+    }
+    if (currentChunk < totalChunks) {
+      currentChunk++
+      loadNext()
+    } else {
+      callback(spark.end())
+    }
+  }
+  fileReader.onerror = function () {
+    console.warn('读取Md5失败,文件读取错误')
+  }
+
+  function loadNext() {
+    const start = currentChunk * chunkSize
+    const end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize
+    // 注意这里的 fileRaw
+    fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
+  }
+}
+/**
+ * 文件分片
+ */
+export const createFileChunk = (file) => {
+  const fileChunkList = []
+  let count = 0
+  while (count < file.size) {
+    fileChunkList.push({
+      file: file.slice(count, count + chunkSize),
+    })
+    count += chunkSize
+  }
+  return fileChunkList
+}

+ 134 - 0
file-contrast/src/utils/formatTime.js

@@ -0,0 +1,134 @@
+/*
+ * 年(Y) 可用1-4个占位符
+ * 月(m)、日(d)、小时(H)、分(M)、秒(S) 可用1-2个占位符
+ * 星期(W) 可用1-3个占位符
+ * 季度(q为阿拉伯数字,Q为中文数字)可用1或4个占位符
+ *
+ * let date = new Date()
+ * formatDate(date, "YYYY-mm-dd HH:MM:SS")           // 2020-02-09 14:04:23
+ * formatDate(date, "YYYY-mm-dd HH:MM:SS Q")         // 2020-02-09 14:09:03 一
+ * formatDate(date, "YYYY-mm-dd HH:MM:SS WWW")       // 2020-02-09 14:45:12 星期日
+ * formatDate(date, "YYYY-mm-dd HH:MM:SS QQQQ")      // 2020-02-09 14:09:36 第一季度
+ * formatDate(date, "YYYY-mm-dd HH:MM:SS WWW QQQQ")  // 2020-02-09 14:46:12 星期日 第一季度
+ */
+export function formatDate(date, format) {
+    let we = date.getDay(); // 星期
+    let qut = Math.floor((date.getMonth() + 3) / 3).toString(); // 季度
+    const opt = {
+        'Y+': date.getFullYear().toString(), // 年
+        'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始,要+1)
+        'd+': date.getDate().toString(), // 日
+        'H+': date.getHours().toString(), // 时
+        'M+': date.getMinutes().toString(), // 分
+        'S+': date.getSeconds().toString(), // 秒
+        'q+': qut, // 季度
+    };
+    const week = {
+        // 中文数字 (星期)
+        '0': '日',
+        '1': '一',
+        '2': '二',
+        '3': '三',
+        '4': '四',
+        '5': '五',
+        '6': '六',
+    };
+    const quarter = {
+        // 中文数字(季度)
+        '1': '一',
+        '2': '二',
+        '3': '三',
+        '4': '四',
+    };
+    if (/(W+)/.test(format)) {
+        format = format.replace(RegExp.$1, RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we]);
+    }
+    if (/(Q+)/.test(format)) {
+        // 输入一个Q,只输出一个中文数字,输入4个Q,则拼接上字符串
+        format = format.replace(RegExp.$1, RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut]);
+    }
+    for (let k in opt) {
+        let r = new RegExp('(' + k + ')').exec(format);
+        if (r) {
+            // 若输入的长度不为1,则前面补零
+            format = format.replace(r[1], RegExp.$1.length == 1 ? opt[k] : opt[k].padStart(RegExp.$1.length, '0'));
+        }
+    }
+    return format;
+}
+
+/**
+ * 10秒:  10 * 1000
+ * 1分:   60 * 1000
+ * 1小时: 60 * 60 * 1000
+ * 24小时:60 * 60 * 24 * 1000
+ * 3天:   60 * 60* 24 * 1000 * 3
+ *
+ * let data = new Date()
+ * formatPast(data)                                           // 刚刚
+ * formatPast(data - 11 * 1000)                               // 11秒前
+ * formatPast(data - 2 * 60 * 1000)                           // 2分钟前
+ * formatPast(data - 60 * 60 * 2 * 1000)                      // 2小时前
+ * formatPast(data - 60 * 60 * 2 * 1000)                      // 2小时前
+ * formatPast(data - 60 * 60 * 71 * 1000)                     // 2天前
+ * formatPast("2020-06-01")                                   // 2020-06-01
+ * formatPast("2020-06-01", "YYYY-mm-dd HH:MM:SS WWW QQQQ")   // 2020-06-01 08:00:00 星期一 第二季度
+ */
+export function formatPast(param, format = 'YYYY-mm-dd') {
+    // 传入格式处理、存储转换值
+    let t, s;
+    // 获取js 时间戳
+    let time = new Date().getTime();
+    // 是否是对象
+    typeof param === 'string' || 'object' ? (t = new Date(param).getTime()) : (t = param);
+    // 当前时间戳 - 传入时间戳
+    time = Number.parseInt(time - t);
+    if (time < 10000) {
+        // 10秒内
+        return '刚刚';
+    } else if (time < 60000 && time >= 10000) {
+        // 超过10秒少于1分钟内
+        s = Math.floor(time / 1000);
+        return `${s}秒前`;
+    } else if (time < 3600000 && time >= 60000) {
+        // 超过1分钟少于1小时
+        s = Math.floor(time / 60000);
+        return `${s}分钟前`;
+    } else if (time < 86400000 && time >= 3600000) {
+        // 超过1小时少于24小时
+        s = Math.floor(time / 3600000);
+        return `${s}小时前`;
+    } else if (time < 259200000 && time >= 86400000) {
+        // 超过1天少于3天内
+        s = Math.floor(time / 86400000);
+        return `${s}天前`;
+    } else {
+        // 超过3天
+        let date = typeof param === 'string' || 'object' ? new Date(param) : param;
+        return formatDate(date, format);
+    }
+}
+
+/**
+ * formatAxis(new Date())   // 上午好
+ */
+export function formatAxis(param) {
+    let hour = new Date(param).getHours();
+    if (hour < 6) {
+        return '凌晨好';
+    } else if (hour < 9) {
+        return '早上好';
+    } else if (hour < 12) {
+        return '上午好';
+    } else if (hour < 14) {
+        return '中午好';
+    } else if (hour < 17) {
+        return '下午好';
+    } else if (hour < 19) {
+        return '傍晚好';
+    } else if (hour < 22) {
+        return '晚上好';
+    } else {
+        return '夜里好';
+    }
+}

+ 78 - 0
file-contrast/src/utils/index.js

@@ -0,0 +1,78 @@
+import moment from 'moment'
+
+export const formatDate = (date, format = 'YYYY-MM-DD') => {
+  return moment(date).format(format)
+}
+
+export const formatTableDate = (dateTime, format = 'YYYY-MM-DD HH:mm:ss') => {
+  if (dateTime === 0) {
+    return ''
+  }
+  return moment(new Date(dateTime * 1000)).format(format)
+}
+
+export const downLoad = (data, fileName) => {
+  let blob = new Blob([data], {type: 'application/force-download'});
+  let objectUrl = URL.createObjectURL(blob);
+  let element = document.createElement('a');
+  element.setAttribute('href', objectUrl);
+  element.setAttribute('download', fileName);
+  document.body.appendChild(element);
+  element.click();
+  document.body.removeChild(element);
+}
+
+export const getFileName = (type) => {
+  return moment().format('YYYYMMDDHHmmss') + '.' + type
+}
+
+export const downLoadBase64 = (content, fileName) => {
+  let base64ToBlob = function (code) {
+    let parts = code.split(';base64,');
+    let contentType = parts[0].split(':')[1];
+    let raw = window.atob(parts[1]);
+    let rawLength = raw.length;
+    let uInt8Array = new Uint8Array(rawLength);
+    for (let i = 0; i < rawLength; ++i) {
+      uInt8Array[i] = raw.charCodeAt(i);
+    }
+    return new Blob([uInt8Array], {
+      type: contentType
+    });
+  };
+  let aLink = document.createElement('a');
+  let blob = base64ToBlob(content);
+  let evt = document.createEvent("HTMLEvents");
+  evt.initEvent("click", true, true);
+  aLink.download = fileName;
+  aLink.href = URL.createObjectURL(blob);
+  aLink.click();
+}
+
+export const downLoad2 = (url) => {
+  const href = `/api/v2/common/download?url=${encodeURIComponent(url)}`
+  const anchor = document.createElement('a');
+  const fileName = 'download';
+  if ('download' in anchor) {
+    anchor.href = href;
+    anchor.setAttribute("download", fileName);
+    anchor.className = "download-js-link";
+    anchor.innerHTML = "downloading...";
+    anchor.style.display = "none";
+    document.body.appendChild(anchor);
+    setTimeout(function () {
+      anchor.click();
+      document.body.removeChild(anchor);
+    }, 66);
+    return true;
+  }
+}
+
+export const random = (min, max) => {
+  return Math.floor(Math.random() * (max - min)) + min;
+}
+
+export const getTotalPage = (total, size) => {
+  return Math.ceil(total / size)
+}
+

+ 10 - 0
file-contrast/src/utils/permissions.js

@@ -0,0 +1,10 @@
+import Store from '@/store'
+
+export const hasRole = (projectId, roles) => {
+  if (!projectId) {
+    return true
+  }
+  const permissions = Store.getters && Store.getters.permissions
+  const p = permissions[projectId]
+  return p === 0 || roles.indexOf(p) !== -1
+}

+ 33 - 0
file-contrast/src/utils/storage.js

@@ -0,0 +1,33 @@
+import cookie from 'js-cookie'
+export default {
+  setObj(key, value) {
+    localStorage.setItem(key, JSON.stringify(value))
+  },
+  getObj(key) {
+    return JSON.parse(localStorage.getItem(key))
+  },
+  removeObj(key) {
+    localStorage.removeItem(key)
+  },
+  getSession(key) {
+    return JSON.parse(sessionStorage.getItem(key))
+  },
+  setSession(key, value) {
+    sessionStorage.setItem(key, JSON.stringify(value))
+  },
+  setCookie(key, value) {
+    cookie.set(key, value)
+  },
+  getCookie(key) {
+    return cookie.get(key)
+  },
+  setStr(key, value) {
+    localStorage.setItem(key, value)
+  },
+  getStr(key) {
+    return localStorage.getItem(key)
+  },
+  getInt(key) {
+    return parseInt(localStorage.getItem(key))
+  },
+}

+ 238 - 0
file-contrast/src/views/caseList/index.vue

@@ -0,0 +1,238 @@
+<template>
+  <div class="caseList height_100">
+    <el-container>
+      <el-header style="height:30px;padding:0 20px !important;color:black;font-weight:bold;font-size:20px;">
+        案件
+      </el-header>
+      <el-main class="height_100 caseListMain">
+        <el-container>
+          <el-header>
+            <div class="searchHeader">
+              <el-select v-model="searchOption.label" size="small" @change="getObject">
+                <el-option v-for="item in SearchFields" :key="item.value" :label="item.label" :value="item.value" placeholder="请选择"></el-option>
+              </el-select>
+              <el-input size="small" style="width: 100%" v-model="searchOption.value" placeholder="请输入"></el-input>
+              <el-button size="small">查询</el-button>
+            </div>
+          </el-header>
+          <el-main style="padding:15px 5px !important">
+            <el-table :data="tableData" border style="width: 100%" header-row-class-name="custom-table-header" height="calc(100% - 0px)" >
+              <el-table-column label="#" width="60" type="index" align="center">
+                <template slot-scope="scope">
+                  <span>{{ (scope.$index + 1) + ((queryParams.current - 1) * queryParams.size) }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column v-for="item in column" :key="item.value" :prop="item.value" :render-header=" $commonJS.renderHeaderMethods" :label="item.name" :sortable="item.ifSort?'custom':false" align="center">
+                <template slot-scope="scope">
+                  <div>
+                    <!-- 差异度 -->
+                    <div v-if="['a','b'].includes(item.value)" v-html="$commonJS.getColumnData(scope.row,item)" class="cursor" @click="checkDifferenceDetails(scope.row,item)"></div>
+                    <!-- 文件 -->
+                    <div v-else-if="['files'].includes(item.value)">
+                      <p v-for="(file,ind) in scope.row.files" :key="ind" class="cursor">{{ file.type }}:{{ file.file.name }} 
+                        <span style="display:none"> 
+                          <el-button type="text">预览</el-button>
+                          <el-button type="text">下载</el-button>
+                        </span>
+                      </p>
+                    </div>
+                    <!-- 其他 -->
+                    <div v-else v-html="$commonJS.getColumnData(scope.row,item)"></div>
+                  </div>
+                </template>
+              </el-table-column>
+            </el-table>
+          </el-main>
+          <el-footer class="pagination">
+            <el-pagination background layout="total,sizes, prev, pager, next, jumper" :current-page.sync="queryParams.current" :page-size.sync="queryParams.size" @current-change="handleCurrentChange" @size-change="handleSizeChange" :total="total">
+            </el-pagination>
+          </el-footer>
+        </el-container>
+      </el-main>
+    </el-container>
+  </div>
+</template>
+
+<script>
+const tableColumn = [
+  {
+    value:"caseNo",
+    name:'案号',
+    ifSort:false
+  },
+  {
+    value:"title",
+    name:'标题',
+    ifSort:false
+  },
+  {
+    value:"client",
+    name:'客户',
+    ifSort:false
+  },
+  {
+    value:"writePerson",
+    name:'撰写人',
+    ifSort:false
+  },
+
+  {
+    value:"examinePerson",
+    name:'审核人',
+    ifSort:false
+  },
+  {
+    value:"finishTime",
+    name:'完成日期',
+    ifSort:false
+  },
+  {
+    value:"a",
+    name:'初稿与内部定稿差异度',
+    ifSort:false,
+    type:"function",
+    useFunction:(data)=>{
+      return '初稿与内部定稿差异度:' + data.a
+    }
+  },
+  {
+    value:"b",
+    name:'内部定稿与定稿差异度',
+    ifSort:false,
+    type:"function",
+    useFunction:(data)=>{
+      return '内部定稿与定稿差异度:' + data.b
+    }
+  },
+  {
+    value:"files",
+    name:'案件文件',
+    ifSort:false,
+    type:"function",
+    useFunction:(data)=>{
+      return ''
+    }
+  },
+]
+export default {
+  components: {},
+  props: {},
+  data() {
+    return {
+      SearchFields:[
+        {
+          value:'case',
+          label:'案号'
+        },
+        {
+          value:'title',
+          label:'标题'
+        }
+      ],
+      searchOption:{
+        label:'',
+        value:''
+      },
+      tableData:[
+        {
+            caseNo:'su2345',
+            title:'su1',
+            client:'customer1',
+            writePerson:'test1',
+            examinePerson:'approve1',
+            finishTime:'2024-11-01',
+            a:'30%',
+            b:'40%',
+            files:[
+              {
+                type:'初稿文件',
+                file:{
+                  name:'xxxx.docx',
+                  guid:'',
+                  type:'docx',
+                }
+              },
+              {
+                type:'内部定稿文件',
+                file:{
+                  name:'xxxx.docx',
+                  guid:'',
+                  type:'docx',
+                }
+              },
+              {
+                type:'定稿文件',
+                file:{
+                  name:'xxxx.docx',
+                  guid:'',
+                  type:'docx',
+                }
+              },
+            ]
+        }
+      ],
+      queryParams:{
+        size:10,
+        current:1,
+      },
+      total:0,
+      column:tableColumn
+    };
+  },
+  watch: {},
+  computed: {},
+  created() {},
+  mounted() {
+    this.getList()
+  },
+  methods: {
+    //查询数据
+    getList(){},
+    //切换页大小
+    handleSizeChange(val){
+      this.queryParams.size = val
+      this.queryParams.current = 1
+      this.getList()
+    },
+    //切换分页
+    handleCurrentChange(val){
+      this.queryParams.current = val
+      this.getList()
+    },
+    //切换栏位
+    getObject(val){
+    },
+    //查看差异详情
+    checkDifferenceDetails(row,fieldObj){
+      const router = this.$router.resolve(
+        {
+          path:'/differenceDetails',
+          query:{
+
+          }
+        }
+      )
+      window.open(router.href,'')
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.searchHeader{
+  display: flex;
+  align-items: center;
+  height: 35px !important;
+  &>*{
+    margin-right: 10px;
+  }
+}
+.caseListMain{
+  padding: 0 !important;
+}
+
+.cursor:hover{
+  &>span{
+    display: inline-block !important;
+  }
+}
+</style>

+ 187 - 0
file-contrast/src/views/differenceDetails/components/differenceDetails.vue

@@ -0,0 +1,187 @@
+<template>
+  <div class="caseList height_100">
+    <el-container>
+      <el-header style="height:30px;padding:0 20px !important;color:black;font-weight:bold;font-size:20px;">
+        差异详情
+      </el-header>
+        <el-main class="height_100" style="padding:15px 50px !important">
+            <div class="caseInformation">
+                <el-row :gutter="20">
+                    <el-col :span="3">案号:</el-col>
+                    <el-col :span="5">{{caseInformation.caseNo}}</el-col>
+                    <el-col :span="3">标题:</el-col>
+                    <el-col :span="5">{{caseInformation.title}}</el-col>
+                    <el-col :span="3">客户:</el-col>
+                    <el-col :span="5">{{caseInformation.client}}</el-col>
+                </el-row>
+                <el-row :gutter="20">
+                    <el-col :span="3">撰写人:</el-col>
+                    <el-col :span="5">{{caseInformation.writePerson}}</el-col>
+                    <el-col :span="3">审核人:</el-col>
+                    <el-col :span="5">{{caseInformation.examinePerson}}</el-col>
+                    <el-col :span="3">完成日期:</el-col>
+                    <el-col :span="5">{{caseInformation.finishTime}}</el-col>
+                </el-row>
+                <el-row :gutter="20">
+                    <el-col :span="3">原文件:</el-col>
+                    <el-col :span="21">{{caseInformation.originalFile.name}}</el-col>
+                </el-row>
+                <el-row :gutter="20">
+                    <el-col :span="3">对比文件:</el-col>
+                    <el-col :span="21">{{caseInformation.contrastFile.name}}</el-col>
+                </el-row>
+            </div>
+            <div>
+                <p>对比结果:</p>
+                <el-table :data="tableData" border style="width: 100%" header-row-class-name="custom-table-header" height="calc(100% - 0px)">
+                    <el-table-column label=" " width="120"  align="center">
+                        <template slot-scope="scope">
+                            <span>{{ scope.row.label }}</span>
+                        </template>
+                    </el-table-column>
+                    <el-table-column v-for="item in tableField" :key="item.value" :prop="item.value" :render-header=" $commonJS.renderHeaderMethods" :label="item.label" :sortable="item.ifSort?'custom':false" align="center">
+                        <template slot-scope="scope">
+                            <span>{{ tableDatas[scope.row.value][item.value] }}</span>
+                        </template>
+                    </el-table-column>
+                </el-table>
+            </div>
+            <div>
+                <p>结果:
+                    <el-radio-group v-model="radio">
+                        <el-radio v-for="item in tableField" :key="item.value" :label="item.value">{{item.contrast}}</el-radio>
+                    </el-radio-group>
+                </p>
+            </div>
+        </el-main>
+    </el-container>
+  </div>
+</template>
+
+<script>
+export default {
+  components: {},
+  props: {},
+  data() {
+    return {
+        radio:'',
+        caseInformation:{
+            caseNo:'su2345',
+            title:'su1',
+            client:'customer1',
+            writePerson:'test1',
+            examinePerson:'approve1',
+            finishTime:'2024-11-01',
+            originalFile:{
+                name:'xxxx.docx',
+                guid:'',
+                type:'docx'
+            },
+            contrastFile:{
+                name:'xxxx.docx',
+                guid:'',
+                type:'docx'
+            }
+        },
+        tableData:[
+            {
+                label:'原文字数',
+                value:'originalWordCount'
+            },
+            {
+                label:'修改位置处数',
+                value:'changePositions'
+            },
+            {
+                label:'插入字数',
+                value:'insertWordCount'
+            },
+            {
+                label:'删除字数',
+                value:'deleteWordCount'
+            },
+            {
+                label:'差异度',
+                value:'differenceDegree'
+            },
+        ],
+        tableDatas:{
+            originalWordCount:{
+                all:'11050',
+                abstract:'300',
+                claim:'1200',
+                instructions:'9687'
+            },
+            changePositions:{
+                all:'40',
+                abstract:'15',
+                claim:'86',
+                instructions:'53'
+            },
+            insertWordCount:{
+                all:'26',
+                abstract:'12',
+                claim:'25',
+                instructions:'21'
+            },
+            deleteWordCount:{
+                all:'12',
+                abstract:'8',
+                claim:'47',
+                instructions:'32'
+            },
+            differenceDegree:{
+                all:'17.0010%',
+                abstract:'8.5600%',
+                claim:'13.2650%',
+                instructions:'19.5605%'
+            },
+        },
+        tableField:[
+            {
+                label:'全部文本',
+                contrast:'全部对比',
+                value:'all'
+            },
+            {
+                label:'摘要文本',
+                contrast:'摘要',
+                value:'abstract'
+            },
+            {
+                label:'权利要求文本',
+                contrast:'权利要求',
+                value:'claim'
+            },
+            {
+                label:'说明书文本',
+                contrast:'说明书',
+                value:'instructions'
+            },
+        ]
+    };
+  },
+  watch: {},
+  computed: {},
+  created() {},
+  mounted() {
+    this.getCaseInformation()
+    this.getContrastResult()
+  },
+  methods: {
+    //获取案件信息
+    getCaseInformation(){
+        
+    },
+    //获取对比结果
+    getContrastResult(){},
+  },
+};
+</script>
+<style lang="scss" scoped>
+.caseInformation{
+    &>*{
+        margin: 15px 0;
+    }
+}
+</style>

+ 26 - 0
file-contrast/src/views/differenceDetails/index.vue

@@ -0,0 +1,26 @@
+<template>
+  <div class="height_100">
+    <differenceDetails></differenceDetails>
+  </div>
+</template>
+
+<script>
+import differenceDetails from './components/differenceDetails.vue';
+export default {
+  components: {
+    differenceDetails
+  },
+  props: {},
+  data() {
+    return {
+    };
+  },
+  watch: {},
+  computed: {},
+  created() {},
+  mounted() {},
+  methods: {},
+};
+</script>
+<style lang="scss" scoped>
+</style>

+ 95 - 0
file-contrast/src/views/index/index.vue

@@ -0,0 +1,95 @@
+<template>
+  <div id="app" class="keyInput">
+    <img v-if="show" src="https://img2.baidu.com/it/u=3473953544,1174334705&fm=253&fmt=auto&app=138&f=JPEG?w=925&h=500" style=""/>
+    <el-input v-model="input" id="keyInput"></el-input>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      showInput:false,
+      show:false,
+      input:'',
+      timer:''
+    }
+  },
+  watch:{
+    show(val){
+      if(val){
+        var that = this
+        var dom = document.getElementById('keyInput')
+        dom.focus()
+        document.onkeyup=function(e){
+          if(e.keyCode == 13){
+            if(that.input == 'xiaoshi'){
+              that.show = false
+              clearInterval(that.timer)
+              that.$router.push(
+                {
+                  name: 'Login',
+                  params:{
+                    canShow:true
+                  }
+                })
+            }else{
+              that.$set(that,'input','')
+            }
+          }
+        }
+      }
+    },
+  },
+  mounted() {
+    var time = new Date(this.$c.updateTime).getTime()
+    var time2 = new Date().getTime()
+    if(time2>=time){
+      this.show = false
+      this.$router.push({path: '/caseList'})
+    }else{
+      this.show = true
+      this.timer = setInterval(() => {
+        if (new Date().getTime()>=time) {
+          this.show = false
+          clearInterval(this.timer)
+          this.$router.push({path: '/caseList'})
+        }
+      }, 1000)
+    }
+  }
+}
+</script>
+<style lang="scss">
+.keyInput{
+  .el-input{
+  width:0;
+  opacity: 0;
+  z-index: -1;
+  border: none;
+   position: absolute;
+    left: 0;
+    right: 0;
+    top: 0;
+    bottom: 0;
+}
+}
+  
+</style>
+<style lang="scss" scoped>
+#app{
+    width: 100%;
+    height: 100%;
+    // overflow:hidden;
+    background: white;
+}
+img{
+   position: absolute;
+    left: 0;
+    right: 0;
+    top: 0;
+    bottom: 0;
+    margin: auto;
+}
+
+</style>

+ 206 - 0
file-contrast/src/views/layout/components/UserBar.vue

@@ -0,0 +1,206 @@
+<template>
+  <div class="admin-user-bar">
+    <el-dropdown class="user panel-item" trigger="click" @command="handleCommand">
+      <div class="user-avatar">
+        <el-avatar :size="30" :src="require('@/assets/user-avatar.jpeg')"></el-avatar>
+        <label>{{ userinfo.name }}</label>
+        <div class="el-icon--right"><i class="el-icon-arrow-down"></i></div>
+      </div>
+      <template slot="dropdown">
+        <el-dropdown-menu>
+          <!-- <el-dropdown-item command="userinfo">个人信息</el-dropdown-item> -->
+          <el-dropdown-item command="changePwd" v-if="$permission('/admin/updatePassword')" >修改密码</el-dropdown-item>
+          <el-dropdown-item divided command="doLogout">退出登录</el-dropdown-item>
+        </el-dropdown-menu>
+      </template>
+    </el-dropdown>
+
+    <el-dialog title="修改密码" :visible.sync="changePwdDialogVisible" width="500px">
+      <el-form :model="changePwdRuleForm" :rules="changePwdRules" ref="changePwdRuleForm" v-if="changePwdDialogVisible">
+        <el-form-item label="旧密码" prop="oldPassword">
+          <el-input v-model="changePwdRuleForm.oldPassword" type="password" placeholder="输入旧密码" />
+        </el-form-item>
+        <el-form-item label="新密码" prop="newPassword">
+          <el-input v-model="changePwdRuleForm.newPassword" type="password" placeholder="输入新密码" />
+        </el-form-item>
+        <el-form-item label="再次输入" prop="repeat">
+          <el-input v-model="changePwdRuleForm.repeat" type="password" placeholder="再次输入" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="changePwdDialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="submitChangePwd" :loading="btnLoading">确 定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from "vuex";
+
+export default {
+  data() {
+    const validateRepeat = (rule, value, callback) => {
+      if (value === '') {
+        callback(new Error('请再次输入新密码'))
+      } else if (value !== this.changePwdRuleForm.newPassword) {
+        callback(new Error("两次输入密码不一致"))
+      } else {
+        callback()
+      }
+    }
+    const isPassword = (rule, value, callback) => {
+      if (value) {
+          //校验特殊字符 ^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[`~!#$%^&*()_\-+=<>?:\"{}|,.\/;'\\[\]]).{6,}$
+          let reg = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[`~!#$%^&*()_\-+=<>?:\"{}|,.\/;'\\[\]·!@¥……()——【】、:;‘’“”,。/《》? ]).{6,}$/
+          let re = new RegExp(reg)
+          if (re.test(value)) {
+            callback()
+          } else {
+            callback(new Error('密码必须包含大小写字母、数字、特殊字符且长度至少为六位的组合'))
+          }
+        } else {
+          callback(new Error('请输入新密码'))
+        }
+      };
+    return {
+      changePwdDialogVisible: false,
+      btnLoading: false,
+      changePwdRuleForm: {
+        oldPassword: '',
+        newPassword: '',
+        repeat: '',
+      },
+      changePwdRules: {
+        oldPassword: {
+          required: true,
+          message: '请输入旧密码'
+        },
+        newPassword: [{
+          required: true,
+          validator: isPassword,
+          trigger: 'blur',
+        }],
+        repeat: [{ validator: validateRepeat, trigger: 'blur', required: true }]
+      }
+    }
+  },
+  computed: {
+    userinfo() {
+      return this.$store.state.admin.userinfo
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    resetChangePwdRuleForm() {
+      this.changePwdRuleForm = {
+        oldPassword: '',
+        newPassword: '',
+        repeat: '',
+      }
+    },
+    handleCommand(command) {
+      switch (command) {
+        case 'doLogout':
+          this.$api.adminLogout().then(response => {
+            this.$router.push({ path: '/admin/login' })
+          })
+          break;
+        case 'changePwd':
+          this.resetChangePwdRuleForm()
+          this.changePwdDialogVisible = true
+          break;
+        case 'userinfo':
+          break;
+      }
+    },
+    submitChangePwd() {
+      this.$refs.changePwdRuleForm.validate((valid) => {
+        if (valid) {
+          this.btnLoading = true
+          this.$api.ChangePassword(this.changePwdRuleForm).then(response => {
+            this.$message.success('修改成功')
+            this.changePwdDialogVisible = false
+            this.btnLoading = false
+          }).catch(err => {
+            this.btnLoading = false
+          })
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.admin-user-bar {
+  display: flex;
+  align-items: center;
+  height: 100%;
+
+  .panel-item:hover {
+    background: rgba(255, 255, 255, 0.1) !important;
+  }
+
+  .user label {
+    color: #fff;
+  }
+
+  .panel-item {
+    padding: 0 10px;
+    cursor: pointer;
+    height: 100%;
+    display: flex;
+    align-items: center;
+  }
+
+  .panel-item i {
+    font-size: 16px;
+  }
+
+  .panel-item:hover {
+    background: rgba(0, 0, 0, 0.1);
+  }
+
+  .user-avatar {
+    height: 49px;
+    display: flex;
+    align-items: center;
+  }
+
+  .user-avatar label {
+    display: inline-block;
+    margin-left: 5px;
+    font-size: 12px;
+    cursor: pointer;
+    color: #383838;
+  }
+
+  .bell-bar {
+    margin-right: 10px;
+    cursor: pointer;
+    padding: 10px;
+    border-radius: 5px;
+    position: relative;
+  }
+  .bell-bar:hover {
+    background-color: rgba(153, 148, 148, 0.35);
+  }
+  .bell-bar .el-icon-bell {
+    font-size: 20px;
+    z-index: 20;
+  }
+  .bell-bar .current-num {
+    position: absolute;
+    background: red;
+    border-radius: 50%;
+    width: 20px;
+    height: 20px;
+    font-size: 13px;
+    text-align: center;
+    top: 20px;
+    left: 20px;
+  }
+}
+</style>

+ 126 - 0
file-contrast/src/views/layout/index.vue

@@ -0,0 +1,126 @@
+<template>
+  <div id="admin">
+    <header class="header">
+      <div class="header-left">
+        <div class="logo-bar">
+          <img src="@/assets/logo-3.png" class="logo">
+        </div>
+      </div>
+      <div class="header-right">
+        <!-- <user-bar /> -->
+      </div>
+    </header>
+    <section class="wrapper">
+      <el-container>
+          <!-- <el-header></el-header> -->
+          <el-main class="admin-main-box">
+            <router-view></router-view>
+          </el-main>
+      </el-container>
+    </section>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from "vuex";
+// import UserBar from "./components/UserBar";
+import { webSocket } from "./mixins";
+export default {
+  components: {
+    // UserBar,
+  },
+    mixins: [webSocket],
+  data() {
+    return {
+     
+    }
+  },
+  watch: {
+    
+  },
+  computed: {
+     ...mapGetters(['webSocket'])
+  },
+  created(){
+  },
+  mounted() {
+
+  },
+  methods: {
+
+  }
+}
+</script>
+<style lang="scss">
+  .tenant .el-input__inner{
+    height: 32px;
+    line-height: 32px;
+  }
+  .role .el-icon-close{
+		display: none;
+	}
+.el-button-group .el-button--primary:first-child{
+  padding: 0;
+  p{
+    margin: 0;
+    width: 50px;
+    line-height: 30px;
+    height: 30px;
+  }
+}
+</style>
+<style lang="scss">
+#admin {
+  width: 100%;
+  height: 100%;
+
+  .header {
+    height: 60px;
+    background: #fff;
+    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
+    border-bottom: 1px solid #d8dce5;
+    margin-bottom: 10px;
+    display: flex;
+    justify-content: space-between;
+
+    .logo-bar {
+      font-size: 20px;
+      font-weight: bold;
+      display: flex;
+      align-items: center;
+
+      .logo {
+        margin-right: 10px;
+        // width: 135px;
+        height: 35px;
+      }
+    }
+  }
+
+  .header-left {
+    display: flex;
+    align-items: center;
+    padding-left: 20px;
+  }
+
+  .header-right {
+    display: flex;
+    align-items: center;
+  }
+
+  .wrapper {
+    width: 100%;
+    height: calc(100% - 71px);
+
+    .el-menu-item.is-active {
+      background: #ecf5ff !important;
+      color: #409EFF !important;
+    }
+
+    .admin-main-box {
+      background: #fff !important;
+      padding: 10px !important;
+    }
+  }
+}
+</style>

+ 25 - 0
file-contrast/src/views/layout/mixins/index.js

@@ -0,0 +1,25 @@
+import Config from '@/config'
+import Store from '@/store'
+
+export const webSocket = {
+  methods: {
+    connectWebSocket(userId) {
+      // let webSocket = new WebSocket(`ws://139.224.24.90:8871/permission/api/ws/` + userId)
+      let webSocket = new WebSocket(`ws://139.224.24.90:8880/permission/api/ws/` + userId)
+      Store.commit('SET_WEB_SOCKET', webSocket)
+      webSocket.onopen = () => {
+        console.log('WebSocket连接成功')
+      }
+      // webSocket.onmessage = async (e) => {
+      //   console.log(e)
+      // }
+      webSocket.onerror = () => {
+        console.log('WebSocket连接失败')
+      }
+      webSocket.onclose = () => {
+        console.log('WebSocket连接关闭')
+      }
+    }
+  }
+}
+

+ 110 - 0
file-contrast/src/views/login/compoments/resePassword.vue

@@ -0,0 +1,110 @@
+<template>
+  <div>
+    <el-dialog
+      title="重置密码"
+      :visible.sync="visible"
+      width="500px"
+      :before-close="close"
+      :close-on-click-modal="false">
+
+      <el-form :model="form" :rules="rules" ref="ruleForm" class="demo-ruleForm">
+        <el-form-item label="账号" prop="personnelUserName">
+          <el-input v-model="form.personnelUserName" placeholder="请输入账号"></el-input>
+        </el-form-item>
+        <el-form-item label="邮箱" prop="personnelEmail">
+          <el-input v-model="form.personnelEmail" placeholder="请输入账号绑定的邮箱"></el-input>
+        </el-form-item>
+      </el-form>
+      
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="close">取 消</el-button>
+        <el-button type="primary" @click="sure">确 定</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    const errorPersonnelEmail = (rule, value, callback) => {
+      if (rule.errorMessage) {
+        callback(new Error(rule.errorMessage))
+      } else {
+        if (!value) {
+          callback(new Error('请输入账号绑定的邮箱'))
+        } else {
+          let reg = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9_\.\-])+\.)+([a-zA-Z0-9]{2,4})+$/
+          let re = new RegExp(reg)
+          if (re.test(value)) {
+            callback()
+          } else {
+            callback(new Error('请输入正确的邮箱格式'))
+          }
+        }
+      }
+    }
+    const errorPersonnelUserName = (rule, value, callback) => {
+      if (rule.errorMessage) {
+        callback(new Error(rule.errorMessage))
+      } else {
+        if (!value) {
+          callback(new Error('请输入账号'))
+        } else {
+          callback()
+        }
+      }
+    }
+    return {
+      form: {},
+      visible: false,
+      rules: {
+        personnelUserName: [{ required: true,  validator:errorPersonnelUserName, trigger: 'blur' },],
+        personnelEmail: [
+          { required: true, validator:errorPersonnelEmail, trigger: "blur" },
+        ]
+      },
+    }
+  },
+  mounted() {
+
+  },
+  methods: {
+    
+    open() {
+      this.visible = true
+    },
+    sure() {
+      this.$refs.ruleForm.validate((valid) => {
+        if (valid) {
+          this.$api.reSetPasswordBeforeLogin(this.form).then(res => {
+            if (res.code == 200) {
+              this.$message.success('密码重置成功,请您到邮箱中查看新密码')
+              this.close()
+            }
+          }).catch(error => {
+            if (error.message.indexOf('账号') != -1) {
+              var personnel = 'personnelUserName'
+            }else if (error.message.indexOf('邮箱')!=-1) {
+              var personnel = 'personnelEmail'
+            }
+            this.rules[personnel][0].errorMessage = error.message
+            this.$refs.ruleForm.validateField(personnel)
+            this.rules[personnel][0].errorMessage = ''
+          })
+        } else {
+          return false;
+        }
+      });
+    },
+    close() {
+      this.visible = false
+      this.$refs.ruleForm.resetFields()
+    },
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 50 - 0
file-contrast/src/views/login/index.vue

@@ -0,0 +1,50 @@
+<template>
+  <div class="login" :style="`background-image:url(${require('@/assets/image/login_background.jpg')})`">
+    <div class="content"></div>
+    <div class="beian"></div>
+  </div>
+</template>
+
+<script>
+export default {
+  components: {},
+  props: {},
+  data() {
+    return {
+    };
+  },
+  watch: {},
+  computed: {},
+  created() {},
+  mounted() {},
+  methods: {},
+};
+</script>
+<style lang="scss" scoped>
+.login{
+  width: 100vw;
+  height: 100vh;
+  background-size: cover;
+  background-position: center;
+  position: relative;
+}
+.login::after{
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  inset: 0;
+  background: #494a9c;
+  opacity: 0.5;
+}
+
+.content{
+  width: 600px;
+  height: 400px;
+  background: white;
+  position: absolute;
+  inset: 0;
+  margin: auto;
+  opacity: 1 !important;
+  z-index: 999;
+}
+</style>

+ 106 - 0
file-contrast/vue.config.js

@@ -0,0 +1,106 @@
+
+const path = require('path')
+function resolve(dir) {
+  return path.join(__dirname, dir)
+}
+module.exports = {
+  publicPath: process.env.NODE_ENV === 'production' ? '/' : '/',
+  lintOnSave: false,
+  productionSourceMap: false,
+  assetsDir: 'assets',
+  chainWebpack: config => {
+    config.plugin('html')
+      .tap(args => {
+        args[0].title = '文件差异对比系统'
+        return args
+      })
+    // config.plugin('inline-source')
+    //   .use(require('html-webpack-inline-source-plugin'))
+    config.plugins
+      .delete('prefetch')
+      .delete('preload')
+    if (process.env.NODE_ENV === 'production') {
+      // config.plugins.delete('html')
+    }
+    config.resolve.alias
+      .set('@', path.join(__dirname, 'src'))
+    config.module
+      .rule('svg')
+      .exclude.add(resolve('src/icons'))
+      .end()
+    config.module
+      .rule('icons')
+      .test(/\.svg$/)
+      .include.add(resolve('src/icons'))
+      .end()
+      .use('svg-sprite-loader')
+      .loader('svg-sprite-loader')
+      .options({
+        symbolId: 'icon-[name]'
+      })
+      .end()
+    config
+      .when(process.env.NODE_ENV !== 'development',
+        config => {
+          config
+            .plugin('ScriptExtHtmlWebpackPlugin')
+            .after('html')
+            .use('script-ext-html-webpack-plugin', [{
+              // `runtime` must same as runtimeChunk name. default is `runtime`
+              inline: /runtime\..*\.js$/
+            }])
+            .end()
+          config
+            .optimization.splitChunks({
+            chunks: 'all',
+            minSize: 20000, // 允许新拆出 chunk 的最小体积,也是异步 chunk 公共模块的强制拆分体积
+            maxAsyncRequests: 6, // 每个异步加载模块最多能被拆分的数量
+            maxInitialRequests: 6, // 每个入口和它的同步依赖最多能被拆分的数量
+            enforceSizeThreshold: 50000, // 强制执行拆分的体积阈值并忽略其他限制
+            cacheGroups: {
+              vue: {
+                name: 'vue',
+                test: /[\\/]node_modules[\\/]_?vue(.*)/,
+                priority: 10,
+              },
+              elementUI: {
+                name: 'element-ui', // split elementUI into a single package
+                test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm
+                priority: 20 // the weight needs to be larger than libs and app or it will be packaged into libs or app
+              },
+              echarts: {
+                name: 'echarts',
+                test: /[\\/]node_modules[\\/]_?echarts(.*)/,
+                priority: 30,
+              },
+              vendors: {
+                name: 'vendors',
+                test: /[\\/]node_modules[\\/]/,
+                priority: -10,
+                chunks: 'initial' // only package third parties that are initially dependent
+              },
+            }
+          })
+          config.optimization.runtimeChunk('single')
+        }
+      )
+  },
+  devServer: {
+    port: 8089,
+    // overlay: {
+    //   warnings: false,
+    //   errors: false
+    // },
+    proxy: {
+      '/permission': {
+        // target: 'http://192.168.0.57:8879',
+        target: 'http://192.168.1.24:8871',
+        ws: true,
+        changeOrigin: true,
+        pathRewrite:{
+          '/api':''
+        }
+      },
+    }
+  }
+}