wucl 2 years ago
commit
330ed2798c
50 changed files with 29869 additions and 0 deletions
  1. 13 0
      .env.development
  2. 7 0
      .env.production
  3. 9 0
      .env.test
  4. 21 0
      .gitignore
  5. 21 0
      LICENSE
  6. 31 0
      README.md
  7. 5 0
      babel.config.js
  8. 26699 0
      package-lock.json
  9. 50 0
      package.json
  10. 6 0
      postcss.config.js
  11. BIN
      public/favicon.ico
  12. 17 0
      public/index.html
  13. 11 0
      src/App.vue
  14. 11 0
      src/api/index.js
  15. 11 0
      src/api/modules/account.js
  16. 15 0
      src/api/modules/cycle.js
  17. 17 0
      src/api/modules/dashboard.js
  18. 20 0
      src/api/modules/document.js
  19. 14 0
      src/api/modules/question.js
  20. 33 0
      src/assets/css/tailwind.css
  21. BIN
      src/assets/logo.png
  22. 44 0
      src/components/Dashboard.vue
  23. 17 0
      src/components/Footer.vue
  24. 68 0
      src/components/Navbar.vue
  25. 86 0
      src/components/Sidebar.vue
  26. 99 0
      src/components/custom/BaseTable/parentTable.vue
  27. 102 0
      src/components/custom/Pagination/index.vue
  28. 109 0
      src/components/custom/YPageListLayout/index.vue
  29. 25 0
      src/main.js
  30. 216 0
      src/pages/Cycle.vue
  31. 234 0
      src/pages/DashBoard.vue
  32. 304 0
      src/pages/Document.vue
  33. 229 0
      src/pages/Home.vue
  34. 166 0
      src/pages/Login.vue
  35. 140 0
      src/pages/Question.vue
  36. 63 0
      src/router/index.js
  37. 84 0
      src/store/index.js
  38. 62 0
      src/utils/WebSocketClient.js
  39. 21 0
      src/utils/auth.js
  40. 513 0
      src/utils/index.js
  41. 109 0
      src/utils/request.js
  42. 58 0
      src/utils/scroll-to.js
  43. 12 0
      src/vueConfig/globalComponents/index.js
  44. 10 0
      src/vueConfig/index.js
  45. 6 0
      src/vueConfig/prototype/api.js
  46. 6 0
      src/vueConfig/prototype/utils.js
  47. 4 0
      src/vueConfig/prototype/vueCookies.js
  48. 4 0
      src/vueConfig/prototype/webStorage.js
  49. 7 0
      tailwind.js
  50. 60 0
      vue.config.js

+ 13 - 0
.env.development

@@ -0,0 +1,13 @@
+# just a flag
+ENV = 'development'
+
+# base api
+VUE_APP_BASE_API = '/apo/'
+VUE_APP_BASE_API2 = '/apo'
+VUE_APP_BASE_WEB = ''
+
+VUE_CLI_BABEL_TRANSPILE_MODULES = true
+
+
+
+# 只有以VUE_APP_开头的变量才会被webpack.DefinePlugin静态嵌入到客户端侧的包中

+ 7 - 0
.env.production

@@ -0,0 +1,7 @@
+# just a flag
+ENV = 'production'
+
+# base api
+VUE_APP_BASE_API = '/apo/'
+VUE_APP_BASE_API2 = '/apo'
+VUE_APP_BASE_WEB = '/apo'

+ 9 - 0
.env.test

@@ -0,0 +1,9 @@
+NODE_ENV = test
+
+# just a flag
+ENV = 'staging'
+
+# base api
+VUE_APP_BASE_API = '/apo/'
+VUE_APP_BASE_API2 = '/apo'
+VUE_APP_BASE_WEB = '/apo'

+ 21 - 0
.gitignore

@@ -0,0 +1,21 @@
+.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?

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Joe
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 31 - 0
README.md

@@ -0,0 +1,31 @@
+# vue-tailwind-admin
+A simple admin template built using [TailwindCSS](https://tailwindcss.com) & [Vue.js](https://vuejs.org). This project is also running [Vuex](https://vuex.vuejs.org) in order to control the sidebar state throughout components.
+
+## Live Demo
+[https://angry-mahavira-9b32ec.netlify.com/](https://angry-mahavira-9b32ec.netlify.com/)
+
+## Screenshot
+![alt text](https://camo.githubusercontent.com/cfc9183fa58b5017902e7c8878d4b0ce35bfd0fb/68747470733a2f2f692e6779617a6f2e636f6d2f63373433643165366262653663653762633265646264363635353663343931302e706e67)
+
+## 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
babel.config.js

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

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


+ 50 - 0
package.json

@@ -0,0 +1,50 @@
+{
+  "name": "kps",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "axios": "^0.19.2",
+    "chart.js": "^2.9.4",
+    "core-js": "^3.6.4",
+    "element-ui": "^2.15.13",
+    "js-md5": "^0.7.3",
+    "vue": "^2.6.11",
+    "vue-chartjs": "^3.5.0",
+    "vue-cookies": "^1.8.3",
+    "vue-router": "^3.1.5",
+    "vuex": "^3.1.2"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "^4.2.0",
+    "@vue/cli-plugin-eslint": "^4.2.0",
+    "@vue/cli-service": "^4.2.0",
+    "babel-eslint": "^10.0.3",
+    "eslint": "^6.7.2",
+    "eslint-plugin-vue": "^6.1.2",
+    "tailwindcss": "^1.2.0",
+    "vue-template-compiler": "^2.6.11"
+  },
+  "eslintConfig": {
+    "root": true,
+    "env": {
+      "node": true
+    },
+    "extends": [
+      "plugin:vue/essential",
+      "eslint:recommended"
+    ],
+    "parserOptions": {
+      "parser": "babel-eslint"
+    },
+    "rules": {}
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions"
+  ]
+}

+ 6 - 0
postcss.config.js

@@ -0,0 +1,6 @@
+module.exports = {
+	"plugins": [
+ 		require('tailwindcss')('tailwind.js'),
+ 		require('autoprefixer')()
+	]
+}

BIN
public/favicon.ico


+ 17 - 0
public/index.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+  <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>

+ 11 - 0
src/App.vue

@@ -0,0 +1,11 @@
+<template>
+  <div id="app">
+    <router-view />
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'app'
+}
+</script>

+ 11 - 0
src/api/index.js

@@ -0,0 +1,11 @@
+const ApiObj = {}
+const files = require.context('./modules', false, /\.js$/)
+const requireAll = requireContext => requireContext.keys().forEach(key => {
+  const keyName = key.replace('./', '').replace('.js', '')
+  ApiObj[keyName] = files(key).default // 读取文件中的default 模块
+  //console.log(keyName)
+})
+requireAll(files)
+//console.log(ApiObj)
+
+export default ApiObj

+ 11 - 0
src/api/modules/account.js

@@ -0,0 +1,11 @@
+import request from '@/utils/request'
+import md5 from 'js-md5'
+
+export default {
+  login(params) {
+    return request.post(`user/login`, {
+      account: params.account,
+      password: md5(params.password),
+    })
+  },
+}

+ 15 - 0
src/api/modules/cycle.js

@@ -0,0 +1,15 @@
+import request from '@/utils/request'
+
+export default {
+  list(params) {
+    return request.get(`/cycle`,{params:params})
+  },
+  add(params) {
+    return request.post(`cycle`, params)
+  },
+
+  delScore(params){
+    return request.delete(`professor/${params}`)
+  }
+  
+}

+ 17 - 0
src/api/modules/dashboard.js

@@ -0,0 +1,17 @@
+import request from '@/utils/request'
+
+export default {
+    getAnalysisList() {
+        return request.get(`/analysis/current`)
+    },
+  
+    getOriginList() {
+        return request.get(`/professor/current`)
+    },
+    getOriginListByCycleId(param){
+        return request.get(`/professor/cycle/${param}`)
+    },
+    getAnalysisListByCycleId(param) {
+        return request.get(`/analysis/cycle/${param}`)
+    },
+}

+ 20 - 0
src/api/modules/document.js

@@ -0,0 +1,20 @@
+import request from '@/utils/request'
+
+export default {
+  list(params) {
+    return request.get(`/document`,{params:params})
+  },
+  simpleAll() {
+    return request.get(`document/simpleAll`)
+  },
+  add(params) {
+    return request.post(`document`, params)
+  },
+  edit(params) {
+    return request.put(`document`, params)
+  },
+   delete(params) {
+     return request.delete(`document/${params}`)
+   },
+  
+}

+ 14 - 0
src/api/modules/question.js

@@ -0,0 +1,14 @@
+import request from '@/utils/request'
+
+export default {
+  list(params) {
+    return request.get(`/question`,{params:params})
+  },
+  simpleAll() {
+    return request.get(`question/simpleAll`)
+  },
+  add(params) {
+    return request.post(`question`, params)
+  },
+  
+}

+ 33 - 0
src/assets/css/tailwind.css

@@ -0,0 +1,33 @@
+@import url('https://fonts.googleapis.com/css?family=Poppins:300,400,500,600,700');
+
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+body {
+    font-family: Poppins, sans-serif;
+}
+
+body::-webkit-scrollbar {
+    display: none;
+}
+
+.overlay {
+    position: relative; 
+}
+
+/* 1024 -1 so it doesnt show when it hits 1024px */
+@media only screen and (max-width: 1023px) {
+    .overlay:after {
+        content: " ";
+        z-index: 29;
+        display: block;
+        height: 100%;
+        top: 80px;
+        left: 0;
+        right: 0;
+        background: rgba(0, 0, 0, 0.5);
+        pointer-events: none;
+        position: fixed;
+    }
+}

BIN
src/assets/logo.png


+ 44 - 0
src/components/Dashboard.vue

@@ -0,0 +1,44 @@
+<template>
+  <div class="leading-normal tracking-normal" id="main-body">
+    <div class="flex flex-wrap">
+
+      <Sidebar />
+
+      <div class="w-full bg-gray-100 pl-0 lg:pl-64 min-h-screen" :class="sideBarOpen ? 'overlay' : ''" id="main-content">
+
+        <Navbar />
+
+        <div class="p-6 bg-gray-100 mb-20">
+          <router-view />
+        </div>
+
+
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+import $router from '@/router'
+import Sidebar from './Sidebar'
+import Navbar from './Navbar'
+
+export default {
+  name: 'Dashboard',
+  computed: {
+    ...mapState(['sideBarOpen'])
+  },
+  components: {
+    Sidebar,
+    Navbar,
+  },
+
+  created(){
+      const token = this.$store.state.token
+      if (!token){
+        $router.push(`/login`)
+      }
+  }
+}
+</script>

+ 17 - 0
src/components/Footer.vue

@@ -0,0 +1,17 @@
+<template>
+    <div class="w-full border-t-2 px-8 py-6 lg:flex justify-between items-center">
+        <p class="mb-2 lg:mb-0">© Copyright 2020</p>
+
+        <div class="flex">
+            <a href="#" class="mr-6 hover:text-gray-900">Terms of Service</a>
+            <a href="#" class="mr-6 hover:text-gray-900">Privacy Policy</a>
+            <a href="#" class="hover:text-gray-900">About Us</a>
+        </div>
+    </div>
+</template>
+
+<script>
+export default {
+    name: 'Footer'
+}
+</script>

+ 68 - 0
src/components/Navbar.vue

@@ -0,0 +1,68 @@
+<template>
+    <div class="sticky top-0 z-40">
+            <div class="w-full h-20 px-6 bg-gray-100 border-b flex items-center justify-between">
+
+              <!-- left navbar -->
+              <div class="flex">
+
+                <!-- mobile hamburger -->
+                <div class="inline-block lg:hidden flex items-center mr-4">
+                  <button class="hover:text-blue-500 hover:border-white focus:outline-none navbar-burger" @click="toggleSidebar()">
+                    <svg class="h-5 w-5" v-bind:style="{ fill: 'black' }" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><title>Menu</title><path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"/></svg>
+                  </button>
+                </div>
+
+                <!-- search bar -->
+                <!-- <div class="relative text-gray-600">
+                  <input type="search" name="serch" placeholder="Search products..." class="bg-white h-10 w-full xl:w-64 px-5 rounded-lg border text-sm focus:outline-none">
+                  <button type="submit" class="absolute right-0 top-0 mt-3 mr-4">
+                    <svg class="h-4 w-4 fill-current" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 56.966 56.966" style="enable-background:new 0 0 56.966 56.966;" xml:space="preserve" width="512px" height="512px">
+                      <path d="M55.146,51.887L41.588,37.786c3.486-4.144,5.396-9.358,5.396-14.786c0-12.682-10.318-23-23-23s-23,10.318-23,23  s10.318,23,23,23c4.761,0,9.298-1.436,13.177-4.162l13.661,14.208c0.571,0.593,1.339,0.92,2.162,0.92  c0.779,0,1.518-0.297,2.079-0.837C56.255,54.982,56.293,53.08,55.146,51.887z M23.984,6c9.374,0,17,7.626,17,17s-7.626,17-17,17  s-17-7.626-17-17S14.61,6,23.984,6z"/>
+                    </svg>
+                  </button>
+                </div> -->
+                <div class="flex  items-center ">
+                    <p class="font-semibold text-2xl text-blue-500 pl-4" style="letter-spacing: 2px;">四川大友评估专家打分后台系统</p>
+                </div>
+              </div>
+
+              <!-- right navbar -->
+              <div class="flex items-center relative">
+                <img src="https://a7sas.net/wp-content/uploads/2019/07/4060.jpeg" class="w-12 h-12 rounded-full shadow-lg" @click="dropDownOpen = !dropDownOpen">
+              </div>
+
+            </div>
+
+            <!-- dropdown menu -->
+            <div class="absolute bg-gray-100 border border-t-0 shadow-xl text-gray-700 rounded-b-lg w-48 bottom-10 right-0 mr-6" :class="dropDownOpen ? '' : 'hidden'">
+                <span class="block px-4 py-2 hover:bg-gray-200" @click="logout">退出</span>
+            </div>
+            <!-- dropdown menu end -->
+
+    </div>
+</template>
+
+<script>
+import { mapState } from 'vuex'
+
+export default {
+    name: 'Navbar',
+    computed: {
+        ...mapState(['sideBarOpen'])
+    },
+    data() {
+        return {
+            dropDownOpen: false
+        }
+    },
+    methods: {
+        toggleSidebar() {
+            this.$store.dispatch('toggleSidebar')
+        },
+        logout(){
+          this.$store.dispatch('logout');
+          this.$router.push("/login")
+        }
+    }
+}
+</script>

File diff suppressed because it is too large
+ 86 - 0
src/components/Sidebar.vue


+ 99 - 0
src/components/custom/BaseTable/parentTable.vue

@@ -0,0 +1,99 @@
+<template>
+    <el-table
+      ref="table"
+      slot="table"
+      size="medium"
+      :data="data"
+      :isBoard="isBoard"
+      :show-summary="showSummary"
+      :summary-method="summaryMethod"
+      fit
+      highlight-current-row
+      :header-row-style="{ color: '#333333' }"
+      style="
+        border: 1px solid #ebeced;
+        color: #333333;
+      "
+      @selection-change="selectionChange"
+      @sort-change="sortChange"
+      @row-click="goToDetail"
+    >
+  
+      //@row-dblclick="goToDetail"
+      <slot></slot>
+    </el-table>
+  </template>
+  
+  <script>
+    import {underscoreName} from "@/utils/index";
+  
+    export default {
+      name: "parentTable",
+      props: {
+        data: {
+          default() {
+            return [];
+          },
+          require: true,
+          type: Array,
+        },
+        noPage: {
+          default: false,
+          type: Boolean,
+        },
+        isBoard: {
+          default: -1,
+          type: Number,
+        },
+        hasStatic: {
+        default: false,
+        type: Boolean,
+        },
+        showSummary:false,
+        summaryMethod:null,
+        selectionChange:{
+          type: Function,
+          default: function () {
+  
+          }
+        }
+  
+      },
+      computed: {
+        maxHeight() {
+          if(this.isBoard > 0){
+            return this.isBoard  ////如果是首页传过来的,不需要适配高度,直接指定该值返回就好了
+          }
+          else {
+            let plus = this.noPage ? 100 : 0;
+            let sub = this.hasStatic ? 130 : 0;
+            let clientHeight = document.documentElement.clientHeight;
+            return clientHeight - 350 + plus - sub;
+          }
+        },
+      },
+      methods: {
+        // selectionChange(val) {
+        //   this.$emit("selectionChange", val);
+        // },
+        goToDetail(){
+          this.$emit("goToDetail");
+        },
+        sortChange(val) {
+          let sortParam = {};
+          if (val.order === "ascending") {
+            sortParam.ascs = underscoreName(val.prop);
+            sortParam.descs = null;
+          } else if (val.order === "descending") {
+            sortParam.descs = underscoreName(val.prop);
+            sortParam.ascs = null;
+          } else {
+            sortParam.descs = "id";
+            sortParam.ascs = null;
+          }
+  
+          this.$emit("sortTable", sortParam);
+        },
+      },
+    };
+  </script>

+ 102 - 0
src/components/custom/Pagination/index.vue

@@ -0,0 +1,102 @@
+<template>
+    <div :class="{'hidden':hidden}" class="pagination-container">
+      <el-pagination
+        :background="background"
+        :current-page.sync="currentPage"
+        :page-size.sync="pageSize"
+        :layout="layout"
+        :page-sizes="pageSizes"
+        :total="total"
+        v-bind="$attrs"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+      />
+    </div>
+  </template>
+  
+  <script>
+  import { scrollTo } from '@/utils/scroll-to'
+  
+  export default {
+    name: 'Pagination',
+    props: {
+      total: {
+        required: true,
+        type: Number
+      },
+      page: {
+        type: Number,
+        default: 1
+      },
+      limit: {
+        type: Number,
+        default: 10
+      },
+      pageSizes: {
+        type: Array,
+        default() {
+          return [10, 20, 30, 50]
+        }
+      },
+      layout: {
+        type: String,
+        default: 'total, sizes, prev, pager, next, jumper'
+      },
+      background: {
+        type: Boolean,
+        default: true
+      },
+      autoScroll: {
+        type: Boolean,
+        default: true
+      },
+      hidden: {
+        type: Boolean,
+        default: false
+      }
+    },
+    computed: {
+      currentPage: {
+        get() {
+          return this.page
+        },
+        set(val) {
+          this.$emit('update:page', val)
+        }
+      },
+      pageSize: {
+        get() {
+          return this.limit
+        },
+        set(val) {
+          this.$emit('update:limit', val)
+        }
+      }
+    },
+    methods: {
+      handleSizeChange(val) {
+        this.$emit('pagination', { page: this.currentPage, limit: val })
+        if (this.autoScroll) {
+          scrollTo(0, 800)
+        }
+      },
+      handleCurrentChange(val) {
+        this.$emit('pagination', { page: val, limit: this.pageSize })
+        if (this.autoScroll) {
+          scrollTo(0, 800)
+        }
+      }
+    }
+  }
+  </script>
+  
+  <style scoped>
+  .pagination-container {
+    background: #fff;
+    padding: 32px 16px;
+    text-align: center;
+  }
+  .pagination-container.hidden {
+    display: none;
+  }
+  </style>

+ 109 - 0
src/components/custom/YPageListLayout/index.vue

@@ -0,0 +1,109 @@
+<template>
+    <div class="y-page-list-layout">
+      <div class="y-info" v-if="$slots.left || $slots.right">
+        <div class="y-left">
+          <slot name="left"></slot>
+        </div>
+        <div class="y-right">
+          <slot name="right"></slot>
+          <el-dropdown trigger="click" v-show="showExportBox">
+            <el-button type="primary" round>
+              导出<i class="el-icon-arrow-down el-icon--right"></i>
+            </el-button>
+            <el-dropdown-menu slot="dropdown">
+              <div class="y-page-list-export-box" ref="exportBtnBox"></div>
+            </el-dropdown-menu>
+          </el-dropdown>
+        </div>
+      </div>
+      <div class="y-twostart">
+        <slot name="twostart"></slot>
+      </div>
+      <template>
+        <slot name="table"></slot>
+      </template>
+      <pagination style="border: 1px solid #EBECED" v-if="pageList.total>0" :total="pageList.total" :page.sync="pagePara.current" :limit.sync="pagePara.size" @pagination="getPageList" />
+    </div>
+  </template>
+  
+  <script>
+    import Pagination from '@/components/custom/Pagination'
+      export default {
+          name: "y-page-list-layout",
+        components: {Pagination},
+        props: {
+          pageList: {
+            type: Object,
+            default:function () {
+              return {
+                total:0
+              }
+            }
+          },
+          pagePara:{
+            type: Object,
+            default:function () {
+              return {}
+            }
+          },
+          getPageList: {
+            type: Function,
+            default: function(){
+  
+            }
+          }
+        },
+        data(){
+            return{
+              showExportBox:false
+            }
+        },
+        // mounted(){
+        //     const that = this
+        //   if (that.$slots.right && that.$slots.right.length > 0){
+        //     let exportCount = 0
+        //     that.$slots.right.forEach(function (item) {
+        //       if (item.elm.innerText && item.elm.innerText.indexOf('导出') > -1) {
+        //         exportCount++
+        //       }
+        //     })
+        //     if (exportCount > 1){
+        //       that.$slots.right.forEach(function (item) {
+        //         if (item.elm.innerText && item.elm.innerText.indexOf('导出') > -1) {
+        //           that.$refs.exportBtnBox.append(item.elm)
+        //         }
+        //       })
+        //       that.showExportBox = true
+        //     }
+        //   }
+        // },
+      }
+  </script>
+  <style lang="css">
+    .el-input--small .el-input__inner {
+        border-radius: 20px;
+    }
+    .el-input-group {
+        .el-input__inner {
+        border-radius: 0 !important;
+        }
+    }
+    .y-page-list-export-box{
+      padding: 15px;
+    }
+    .y-page-list-layout{
+     margin-top: 8px;
+     background-color: #fff;
+     padding: 17px;
+     
+   }
+   .y-info{
+       display: flex;
+       justify-content:space-between;
+       margin-bottom: 12px;
+     }
+   .y-twostart{
+     margin-left: 20px;
+     margin-bottom: 10px;
+   }
+  </style>

+ 25 - 0
src/main.js

@@ -0,0 +1,25 @@
+import Vue from 'vue'
+import App from './App.vue'
+import router from './router'
+import ElementUI from 'element-ui'
+import 'element-ui/lib/theme-chalk/index.css'
+
+
+import store from './store'
+
+import '@/assets/css/tailwind.css'
+
+import './vueConfig'
+
+
+Vue.config.productionTip = false
+
+Vue.use(ElementUI)
+
+
+new Vue({
+  render: h => h(App),
+  router,
+  store
+}).$mount('#app')
+

+ 216 - 0
src/pages/Cycle.vue

@@ -0,0 +1,216 @@
+<template>
+  <div class="app-container">
+    <!-- <div class="title-container">
+        <breadcrumb id="breadcrumb-container" class="breadcrumb-container"/>
+      </div> -->
+
+    <y-page-list-layout :page-list="pageData" :page-para="listQuery" :getList="getList">
+      <template slot="left">
+        <el-input v-model="listQuery.itemName" placeholder="请输项目名称" clearable
+          style="margin-left: 20px;width: 320px;float: left;">
+        </el-input>
+        <el-select v-model="listQuery.cycleName" placeholder="请选择轮数" clearable
+          style="margin-left: 20px;width: 320px;float: left;">
+          <el-option v-for="(c, index) in cycleNames" :label="c" :value="c" />
+        </el-select>
+        <el-button class="filter-item" style="margin-left: 10px;float: left;" type="primary" @click="searchList" round>搜索
+        </el-button>
+        <el-button class="filter-item" style="float: left;" round type="warning" @click="resetSearch()">重置
+        </el-button>
+      </template>
+      <parentTable v-loading="listLoading" :data="pageData.records" slot="table" style="width: 100%;"
+        :showSummary="false">
+        <el-table-column label="项目名称" align="center">
+          <template slot-scope="{row}">
+            <span>{{ row.itemName }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="问卷类型" align="center">
+          <template slot-scope="{row}">
+            <span>{{ row.type }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="范围" align="center">
+          <template slot-scope="{row}">
+            <span>{{ row.scope }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="轮数" align="center">
+          <template slot-scope="{row}">
+            <span>{{ row.cycleName }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="下发时间" align="center">
+          <template slot-scope="{row}">
+            <span>{{ row.created }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="评分结果" align="center">
+          <template slot-scope="{row}">
+            <el-button type="primary" @click="getOriginListByCycleId(row.id)">评分明细</el-button>
+            <el-button type="success" @click="getAnalysisListByCycleId(row.id)">评分统计</el-button>
+          </template>
+        </el-table-column>
+      </parentTable>
+    </y-page-list-layout>
+    <el-dialog title="评分明细" :visible.sync="dialogVisible" width="50%" center>
+      <el-table :data="originData" border style="width: 100%" stripe highlight-current-row>
+        <el-table-column prop="professorNo" label="专家编号" align="center">
+        </el-table-column>
+        <el-table-column prop="questionName" label="评分项" align="center">
+        </el-table-column>
+        <el-table-column prop="score" label="专家评分值" align="center">
+        </el-table-column>
+        <el-table-column prop="created" label="专家评分时间" align="center">
+        </el-table-column>
+        <el-table-column label="操作" align="center">
+          <template slot-scope="{row}">
+            <el-button type="danger" @click="remove(row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="dialogVisible = false">确定</el-button>
+      </span>
+    </el-dialog>
+    <el-dialog title="评分统计" :visible.sync="dialogVisibleX" width="50%" center>
+      <el-table :data="analysisData" border style="width: 100% ;" stripe highlight-current-row>
+        <el-table-column prop="questionName" label="评分项" align="center">
+        </el-table-column>
+        <el-table-column prop="avgScore" label="平均得分" align="center">
+        </el-table-column>
+      </el-table>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="dialogVisibleX = false">确定</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import YPageListLayout from '@/components/custom/YPageListLayout'
+
+export default {
+  name: 'Cycle',
+  components: {
+    YPageListLayout,
+  },
+  data() {
+    return {
+      pageData: {},
+      total: 10,
+      listLoading: false,
+      listQuery: {
+        page: 1,
+        size: 10,
+        descs: 'id',
+      },
+      listQueryKey: 'keyword',
+      dialogVisible: false,
+      cycleNames: ['第一轮', '第二轮', '第三轮', '第四轮', '第五轮', '第六轮', '第七轮', '第八轮', '第九轮', '第十轮'],
+      originData: [],
+      dialogVisibleX: false,
+      analysisData: []
+    }
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    remove(row) {
+      if (row.id) {
+        this.$confirm('此操作将删除专家评分, 是否继续?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }).then(() => {
+          this.$api.cycle.delScore(row.id).then(res => {
+            if (res.code === 200) {
+              this.$notify({
+                title: '成功',
+                message: '删除成功',
+                type: 'success'
+              });
+              this.getOriginListByCycleId(row.cycleId);
+            }
+            else {
+              this.$notify.error({
+                title: '错误',
+                message: '删除失败'
+              });
+            }
+          })
+        })
+
+      }
+    },
+    getAnalysisListByCycleId(id) {
+      this.dialogVisibleX = true;
+      const that = this
+      this.$api.dashboard.getAnalysisListByCycleId(id)
+        .then((res) => {
+          let tScore = res.data[0].avgScore
+          if (tScore == null) {
+            that.analysisData = null;
+          } else {
+            that.analysisData = res.data
+          }
+          setTimeout(() => {
+          }, 200)
+        })
+        .catch(() => {
+          that.listLoading = false
+        })
+    },
+    getOriginListByCycleId(id) {
+      this.dialogVisible = true;
+      const that = this
+      this.$api.dashboard.getOriginListByCycleId(id)
+        .then((res) => {
+          if (res.data[0].score == null) {
+            that.originData = null;
+          } else {
+            that.originData = res.data
+          }
+          setTimeout(() => {
+          }, 200)
+        })
+        .catch(() => {
+          that.listLoading = false
+        })
+    },
+    resetSearch() {
+      this.$router.push({ query: {} });
+      this.listQuery = {
+        current: 1,
+        size: 10,
+        descs: 'id',
+      }
+      this.getList()
+    },
+    searchList() {
+      // 重置分页
+      this.listQuery.page = 1
+      this.listQuery.size = 10
+      this.getList()
+    },
+    getList() {
+      const that = this
+      that.listLoading = true
+      const key = {}
+      key[that.listQueryKey] = that.listQuery.description
+      this.$api.cycle.list(Object.assign({}, that.listQuery, key))
+        .then((res) => {
+          that.pageData = res.data
+          setTimeout(() => {
+            that.listLoading = false
+          }, 200)
+        })
+        .catch(() => {
+          that.listLoading = false
+        })
+    },
+  },
+}
+</script>
+
+  

+ 234 - 0
src/pages/DashBoard.vue

@@ -0,0 +1,234 @@
+<template>
+    <div class="board-container">
+        <div class="header">
+            <img src="../assets/logo.png" style="margin-left: 10px; margin-top: 10px;" >
+        </div>
+        <div class="title_info">
+            <el-card shadow="always" style="background-color: rgb(201,235,251); border-radius: 5px;">
+                <p class="item_name">{{ itemName }}</p>
+                <p class="scope_cycleName">{{type}} {{ scope }} 类 {{ cycleName }} 专家评分公示</p>
+            </el-card>
+        </div>
+        <div class="analysis_collect">
+            <span class="name"><i class="el-icon-s-data"></i>评分项平均得分</span>
+            <el-table :data="analysisData" border style="width: 100% ;" stripe highlight-current-row>
+                <el-table-column prop="questionName" label="评分项" align="center">
+                </el-table-column>
+                <el-table-column prop="avgScore" label="平均得分" align="center">
+                </el-table-column>
+            </el-table>
+        </div>
+        <div class="origin_collect">
+            <span class="name"><i class="el-icon-s-data"></i>专家评分值</span>
+            <el-table :data="originData" border style="width: 100%" stripe highlight-current-row>
+                <el-table-column prop="professorNo" label="专家编号" align="center">
+                </el-table-column>
+                <el-table-column prop="questionName" label="评分项" align="center">
+                </el-table-column>
+                <el-table-column prop="score" label="专家评分值" align="center">
+                </el-table-column>
+                <el-table-column prop="created" label="专家评分时间" align="center">
+                </el-table-column>
+            </el-table>
+        </div>
+        <div class="user_size">
+           <span v-for="(u,index) in userSize">
+                <i class="el-icon-s-custom" style="color: green; font-size: xx-large;"></i>
+           </span>
+        </div>
+    </div>
+</template>
+
+<script>
+import YPageListLayout from '@/components/custom/YPageListLayout'
+import WebSocketClient from '../utils/WebSocketClient.js'
+
+export default {
+    name: 'Dashboard',
+    components: {
+        YPageListLayout,
+    },
+    data() {
+        return {
+            analysisData: [],
+            originData: [],
+            listLoading: false,
+            itemName: "",
+            scope: "",
+            cycleName: "",
+            type:null,
+            messages: [],
+            error: null,
+            client: {
+                messages:[
+                    {data:"*"},
+                    {userSize:0}
+                ]
+            },
+            userSize:0
+
+        }
+    },
+    watch:{
+        client:{
+            deep: true,
+            handler(newValue,oldValue){
+                let data = newValue.messages[(newValue.messages.length)-1]
+                if (data != undefined){
+                    if (data.data !=null){
+                        this.getAnalysisList();
+                        this.getOriginList();
+                    }
+                    if (data.userSize != undefined){
+                        this.userSize = (data.userSize)-1;
+                    }
+                }
+               
+            }
+        }
+    },
+    mounted(){
+        this.init();
+    },
+    created() {
+        this.getAnalysisList();
+        this.getOriginList();
+    },
+    methods: {
+        init(){
+            if (typeof(WebSocket) === "undefined"){
+                console.log("您的浏览器不支持socket")
+            }else{
+                // 创建 WebSocketClient 实例并连接到服务端 API 接口
+                this.client = new WebSocketClient('wss://kps.scdayou.com/ws/apo/ws')
+                this.client.connect()
+            }
+        },
+        getAnalysisList() {
+            const that = this
+            this.$api.dashboard.getAnalysisList()
+                .then((res) => {
+                    let tScore = res.data[0].avgScore
+                    if (tScore==null){
+                        that.analysisData = null;
+                    }else{
+                        that.analysisData = res.data
+                    }
+                    setTimeout(() => {
+                    }, 200)
+                })
+                .catch(() => {
+                    that.listLoading = false
+                })
+        },
+        getOriginList() {
+            const that = this
+            this.$api.dashboard.getOriginList()
+                .then((res) => {
+                    that.itemName = res.data[0].itemName;
+                    that.scope = res.data[0].scope;
+                    that.cycleName = res.data[0].cycleName;
+                    that.type = res.data[0].type;
+                    if (res.data[0].score ==null){
+                        that.originData = null;
+                    }else{
+                        that.originData = res.data;
+                    
+                    }
+                    setTimeout(() => {
+                    }, 200)
+                })
+                .catch(() => {
+                    that.listLoading = false
+                })
+        },
+    },
+    destoryed() {
+    // 在组件销毁前断开 WebSocket 连接
+        this.client.disconnect()
+        console.log("websocket 断开")
+    },
+    beforeDestory(){
+        this.client.disconnect()
+        console.log("websocket 断开")
+    }
+
+}
+</script>
+<style lang="css" scoped>
+.header {
+    height: 80px;
+    padding: 0;
+    background-color:  #ffffff;
+    vertical-align: middle;
+    display: flex;
+    position: relative;
+    width: 100%;
+}
+
+.left {
+    margin: 0;
+    padding-left: 10px;
+    width: 30%;
+    height: 100%;
+}
+
+.board-container {
+    /* display: flex;
+    width: 100%;
+    justify-content: center;
+    overflow: auto; */
+    top: -10;
+    background-image: linear-gradient(to top, #ace0f9 0%, #ffffff 100%);
+    height: 1200px;
+    overflow:auto;
+}
+
+.item_name {
+    font-size: xx-large;
+    letter-spacing: 1px;
+    font-weight: 900;
+}
+
+.scope_cycleName {
+    font-size: large;
+    letter-spacing: 1px;
+    font-weight: bold;
+    color: red;
+   
+}
+
+.title_info {
+    margin-top: 1%;
+    width: 70%;
+    margin-left: 15%;
+    text-align: center;
+}
+
+.analysis_collect {
+    margin-top: 2%;
+    width: 70%;
+    margin-left: 15%;
+}
+
+.origin_collect {
+    margin-top: 2%;
+    width: 70%;
+    margin-bottom: 2%;
+    margin-left: 15%;
+}
+.name{
+    /* font-family: 'Courier New', Courier, monospace; */
+    margin-bottom: 5px;
+    letter-spacing: 1px;
+    font-weight: bold;
+    font-size: small;
+}
+.user_size{
+    position:absolute;
+    right:30px;
+    top:1%;
+    height: 40px;
+}
+
+</style>

+ 304 - 0
src/pages/Document.vue

@@ -0,0 +1,304 @@
+<template>
+  <div class="app-container">
+    <!-- <div class="title-container">
+        <breadcrumb id="breadcrumb-container" class="breadcrumb-container"/>
+      </div> -->
+
+    <y-page-list-layout :page-list="pageData" :page-para="listQuery" :getList="getList">
+      <template slot="left">
+        <el-button type="primary" round style="float: left" @click="openDialog">新增</el-button>
+        <el-input v-model="listQuery.itemName" placeholder="请输入项目名称" clearable
+          style="margin-left: 20px;width: 320px;float: left;">
+        </el-input>
+        <el-button class="filter-item" style="margin-left: 10px;float: left;" type="primary" @click="searchList" round>搜索
+        </el-button>
+        <el-button class="filter-item" style="float: left;" round type="warning" @click="resetSearch()">重置
+        </el-button>
+      </template>
+      <parentTable v-loading="listLoading" :data="pageData.records" slot="table" style="width: 100%;"
+        :showSummary="false">
+        <el-table-column type="expand">
+          <template slot-scope="{row}">
+            <parentTable style="font-size: 5px;color: #8c939d" v-loading="listLoading" inline :data="row.questions">
+              <el-table-column label="题目名称" align="center">
+                <template slot-scope="{row}">
+                  <span>{{ row.name }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="创建时间" align="center" width="550">
+                <template slot-scope="{row}">
+                  <span>{{ row.created }}</span>
+                </template>
+              </el-table-column>
+            </parentTable>
+          </template>
+        </el-table-column>
+        <el-table-column label="项目名称" align="center">
+          <template slot-scope="{row}">
+            <span>{{ row.itemName }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="类型" align="center" width="200">
+          <template slot-scope="{row}">
+            <span>{{ row.type }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="范围" align="center" width="200">
+          <template slot-scope="{row}">
+            <span>{{ row.scope }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="创建时间" align="center" width="200">
+          <template slot-scope="{row}">
+            <span>{{ row.created }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="200">
+          <template slot-scope="{row}">
+            <el-button type="success" @click="openCycleDiglog(row)">下发问卷</el-button>
+          </template>
+        </el-table-column>
+      </parentTable>
+    </y-page-list-layout>
+    <el-dialog title="新增问卷" :visible.sync="dialogVisible" width="50%" center>
+      <el-form :model="documentForm" ref="dForm" label-width="100px" class="demo-ruleForm" :rules="rules">
+        <el-form-item label="项目名称" prop="itemName">
+          <el-input v-model.trim="documentForm.itemName"></el-input>
+        </el-form-item>
+        <el-form-item label="问卷类型" prop="type">
+          <el-select v-model="documentForm.type" placeholder="请选择类型" @change="selectScope">
+            <el-option v-for="(o, index) in options" :label="o.type" :value="o.type" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="范围" prop="scope">
+          <el-select v-model="documentForm.scope" placeholder="请选择范围">
+            <el-option v-for="(s, index) in scopes" :label="s" :value="s"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="选择题目" prop="questions">
+          <el-transfer filterable :filter-method="filterMethod" filter-placeholder="请输入题目名称"
+            v-model="documentForm.questions" :data="questionList">
+          </el-transfer>
+        </el-form-item>
+      </el-form>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="dialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="addDocument('dForm')">确 定</el-button>
+      </span>
+    </el-dialog>
+    <el-dialog title="下发问卷" :visible.sync="dialogVisibleX" width="20%" center>
+      <el-form :model="cycleForm" ref="cForm" label-width="100px" class="demo-ruleForm">
+        <el-form-item label="第几轮" prop="cycleName">
+          <el-select v-model="cycleForm.cycleName" placeholder="请选择">
+            <el-option v-for="(c, index) in cycleNames" :label="c" :value="c" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="dialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="addCycle">确 定</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import YPageListLayout from '@/components/custom/YPageListLayout'
+export default {
+  name: 'Document',
+  components: {
+    //Breadcrumb,
+    YPageListLayout,
+    // parentTable
+  },
+  data() {
+    return {
+      pageData: { records: [] },
+      total: 10,
+      listLoading: false,
+      listQuery: {
+        page: 1,
+        size: 10,
+        descs: 'id',
+        itemName: null
+      },
+      dialogVisible: false,
+      dialogVisibleX: false,
+      documentForm: {},
+      options: [
+        {
+          type: "城镇土地综合定级因素表",
+          scope: ["名称", "定级因素", "一级因子", "二级因子"]
+        },
+        {
+          type: "城镇商服用地定级因素表",
+          scope: ["名称", "定级因素", "一级因子", "二级因子"]
+        },
+        {
+          type: "城镇住宅用地定级因素表",
+          scope: ["名称", "定级因素", "一级因子", "二级因子"]
+        },
+        {
+          type: "城镇工业用地定级因素表",
+          scope: ["名称", "定级因素", "一级因子", "二级因子"]
+        },
+        {
+          type: "城镇公共管理与公共服务用地定级因素表",
+          scope: ["名称", "定级因素", "一级因子", "二级因子"]
+        },
+        {
+          type: "集体建设用地定级因素表",
+          scope: ["名称", "定级因素", "一级因子", "二级因子"]
+        },
+        {
+          type: "耕地定级指标体系表",
+          scope: ["因素层", "因子层"]
+        },
+        {
+          type: "园地定级指标体系表",
+          scope: ["因素", "因子", "评价指标"]
+        },
+        {
+          type: "林地定级指标体系表",
+          scope: ["林地类型", "因素", "因子", "评价指标"]
+        },
+        {
+          type: "草地定级指标体系表",
+          scope: ["因素", "因子", "评价指标", "天然牧草地", "人工牧草地", "其他草地"]
+        },
+      ],
+      scopes: [],
+      questionList: [],
+      filterMethod(query, item) {
+        return item.name.indexOf(query) > -1;
+      },
+      rules: {
+        itemName: [
+          { required: true, message: '请输入项目名称', trigger: 'blur' }
+        ],
+        type: [
+          { required: true, message: '请选择问卷类型', trigger: 'blur' }
+        ],
+        scope: [
+          { required: true, message: '请选择问卷范围', trigger: 'blur' }
+        ],
+        questions: [
+          { required: true, message: '请选择题目', trigger: 'blur' }
+        ]
+      },
+      cycleNames: ['第一轮', '第二轮', '第三轮', '第四轮', '第五轮', '第六轮', '第七轮', '第八轮', '第九轮', '第十轮'],
+      cycleForm: {
+        documentId: null,
+        cycleName: null
+      }
+    }
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    addCycle() {
+      if (this.cycleForm.documentId) {
+        if (this.cycleForm.cycleName) {
+          this.$api.cycle.add(this.cycleForm).then(res => {
+            if (res.code === 200) {
+              this.$notify({
+                title: '成功',
+                message: '下发成功',
+                type: 'success'
+              });
+              this.cycleForm = {};
+              this.dialogVisibleX = false;
+            } else {
+              this.$notify.error({
+                title: '错误',
+                message: '下发失败'
+              });
+            }
+          })
+        }
+      }
+    },
+    openCycleDiglog(document) {
+      this.dialogVisibleX = true;
+      this.cycleForm.documentId = document.id;
+    },
+    openDialog() {
+      this.dialogVisible = true;
+      this.getQuestions();
+    },
+    getQuestions() {
+      this.$api.question.simpleAll().then(res => {
+        if (res.code === 200) {
+          this.questionList = res.data
+        }
+      })
+    },
+    selectScope(value) {
+      const options = this.options
+      for (let i in options) {
+        if (options[i].type === value) {
+          this.scopes = options[i].scope
+          break
+        }
+      }
+    },
+    resetSearch() {
+      this.$router.push({ query: {} });
+      this.listQuery = {
+        current: 1,
+        size: 10,
+        descs: 'id',
+      }
+      this.getList()
+    },
+    searchList() {
+      // 重置分页
+      this.listQuery.page = 1
+      this.listQuery.size = 10
+      this.getList()
+    },
+    getList() {
+      const that = this
+      this.$api.document.list(that.listQuery)
+        .then((res) => {
+          that.pageData = res.data
+          setTimeout(() => {
+          }, 200)
+        })
+        .catch(() => {
+          that.listLoading = false
+        })
+    },
+    addDocument(formName) {
+      this.$refs[formName].validate(valid => {
+        if (valid) {
+          this.$api.document.add(this.documentForm).then(res => {
+            if (res.code === 200) {
+              this.$notify({
+                title: '成功',
+                message: '新增成功',
+                type: 'success'
+              });
+              this.documentForm = {};
+              this.dialogVisible = false;
+              this.getList();
+            } else {
+              this.$notify.error({
+                title: '错误',
+                message: '新增失败'
+              });
+            }
+          })
+        } else {
+          console.log("error submit")
+          return false
+        }
+      })
+
+
+    }
+  },
+}
+</script>
+
+  

File diff suppressed because it is too large
+ 229 - 0
src/pages/Home.vue


File diff suppressed because it is too large
+ 166 - 0
src/pages/Login.vue


+ 140 - 0
src/pages/Question.vue

@@ -0,0 +1,140 @@
+<template>
+  <div class="app-container">
+    <!-- <div class="title-container">
+        <breadcrumb id="breadcrumb-container" class="breadcrumb-container"/>
+      </div> -->
+
+    <y-page-list-layout :page-list="pageData" :page-para="listQuery" :getList="getList">
+      <template slot="left">
+        <el-button type="primary" round style="float: left" @click="dialogVisible = true">新增</el-button>
+        <el-input v-model="listQuery.itemName" placeholder="请输题目名称" clearable
+          style="margin-left: 20px;width: 320px;float: left;">
+        </el-input>
+        <el-button class="filter-item" style="margin-left: 10px;float: left;" type="primary" @click="searchList" round>搜索
+        </el-button>
+        <el-button class="filter-item" style="float: left;" round type="warning" @click="resetSearch()">重置
+        </el-button>
+      </template>
+      <parentTable v-loading="listLoading" :data="pageData.records" slot="table" style="width: 100%;"
+        :showSummary="false">
+        <el-table-column label="题目名称" align="center">
+          <template slot-scope="{row}">
+            <span>{{ row.name }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="创建时间" align="center">
+          <template slot-scope="{row}">
+            <span>{{ row.created }}</span>
+          </template>
+        </el-table-column>
+      </parentTable>
+    </y-page-list-layout>
+    <el-dialog title="新增题目" :visible.sync="dialogVisible" width="30%" center>
+      <el-form :model="questionForm" label-width="100px" class="demo-ruleForm">
+        <el-form-item label="题目名称" prop="name">
+          <el-input v-model.trim="questionForm.name"></el-input>
+        </el-form-item>
+      </el-form>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="dialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="addQuestion">确 定</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import YPageListLayout from '@/components/custom/YPageListLayout'
+
+export default {
+  name: 'Question',
+  components: {
+    YPageListLayout,
+  },
+  data() {
+    return {
+      pageData: {},
+      total: 10,
+      listLoading: false,
+      listQuery: {
+        page: 1,
+        size: 10,
+        descs: 'id',
+      },
+      listQueryKey: 'keyword',
+      dialogVisible: false,
+      questionForm: {
+        name: null
+      }
+    }
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    resetSearch() {
+      this.$router.push({ query: {} });
+      this.listQuery = {
+        current: 1,
+        size: 10,
+        descs: 'id',
+      }
+      this.getList()
+    },
+    searchList() {
+      // 重置分页
+      this.listQuery.page = 1
+      this.listQuery.size = 10
+      this.getList()
+    },
+    getList() {
+      const that = this
+      that.listLoading = true
+      const key = {}
+      key[that.listQueryKey] = that.listQuery.description
+      console.log(this.$api)
+      this.$api.question.list(Object.assign({}, that.listQuery, key))
+        .then((res) => {
+          that.pageData = res.data
+          setTimeout(() => {
+            that.listLoading = false
+          }, 200)
+        })
+        .catch(() => {
+          that.listLoading = false
+        })
+    },
+    addQuestion() {
+      let questionName = this.questionForm.name;
+      if (questionName) {
+        this.$api.question.add(this.questionForm).then(res => {
+          if (res.code === 200) {
+            this.$notify({
+              title: '成功',
+              message: '新增成功',
+              type: 'success'
+            });
+            this.questionName = null;
+            this.dialogVisible = false;
+            this.getList();
+          } else {
+            this.$notify.error({
+              title: '错误',
+              message: '新增失败'
+            });
+          }
+          this.questionName = null;
+          this.dialogVisible = false;
+          this.getList();
+        })
+      } else {
+        this.$notify.error({
+          title: '错误',
+          message: '题目名称不能为空'
+        });
+      }
+    }
+  },
+}
+</script>
+
+  

+ 63 - 0
src/router/index.js

@@ -0,0 +1,63 @@
+import Vue from 'vue'
+import VueRouter from 'vue-router'
+import store from '@/store'
+
+import Dashboard from '@/components/Dashboard'
+//import DashboardHome from '@/pages/Home'
+import Question from '@/pages/Question'
+import Document from '@/pages/Document'
+import Cycle from '@/pages/Cycle'
+import Login from '@/pages/Login'
+import DashBoard from '@/pages/DashBoard'
+
+Vue.use(VueRouter);
+
+
+const originalPush = VueRouter.prototype.push
+VueRouter.prototype.push = function push(location, onResolve, onReject) {
+  if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
+  return originalPush.call(this, location).catch(err => err)
+}
+// todo 定义无菜单权限得页面, 角色权限页面只会在菜单配置得列表页面,其他都定义在这里如详情页面
+// 目前都定义在这里
+
+const { userInfo } = store.getters
+
+
+const routes = [
+  { path: '/', redirect: { name: 'Login' } },
+  { path: '/login', name: 'Login', component: Login },
+
+  {
+    path: '/pages', name: 'Dashboard', component: Dashboard, children: [
+      {
+        path: "/document", component: Document, name: 'Documnet'
+      },
+      {
+        path: '/question', name: 'Question', component: Question
+      },
+      {
+        path: '/cycle', name: 'Cycle', component: Cycle
+      }
+    ]
+  },
+  { path: '/dashboard', name: 'DashBoard', component: DashBoard }
+]
+
+
+const createRouter = () => new VueRouter({
+  mode: 'hash',
+  routes
+});
+
+
+const router = createRouter();
+
+// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
+export function resetRouter() {
+  const newRouter = createRouter();
+  router.matcher = newRouter.matcher // reset router
+}
+
+export const constantRoutes = routes;
+export default router

+ 84 - 0
src/store/index.js

@@ -0,0 +1,84 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import API from '@/api'
+import { getToken, setToken, removeToken } from '@/utils/auth'
+import router, { resetRouter } from '@/router'
+
+// // https://webpack.js.org/guides/dependency-management/#requirecontext
+// const modulesFiles = require.context('./modules', true, /\.js$/)
+
+// // you do not need `import app from './modules/app'`
+// // it will auto require all vuex module from modules file
+// const modules = modulesFiles.keys().reduce((modules, modulePath) => {
+//   // set './app.js' => 'app'
+//   const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
+//   const value = modulesFiles(modulePath)
+//   modules[moduleName] = value.default
+//   return modules
+// }, {})
+
+Vue.use(Vuex)
+
+const store = new Vuex.Store({
+    state: {
+        sideBarOpen: false,
+        token: getToken(),
+        name: '',
+    },
+    getters: {
+        sideBarOpen: state => {
+            return state.sideBarOpen
+        },
+        token: state => state.user.token,
+        name: state => state.user.name
+    },
+    mutations: {
+        toggleSidebar (state) {
+            state.sideBarOpen = !state.sideBarOpen
+        },
+        SET_TOKEN: (state, token) => {
+            state.token = token
+          },
+          SET_NAME: (state, name) => {
+            state.name = name
+          },
+    },
+    actions: {
+        toggleSidebar(context) {
+            context.commit('toggleSidebar')
+        },
+        login({ commit }, userInfo) {
+            const { account, password} = userInfo
+            return new Promise((resolve, reject) => {
+              API.account.login({
+                account: account.trim(),
+                password,
+              }).then(response => {
+                const { data } = response
+                commit('SET_TOKEN', data.token)
+                setToken(data.token)
+                resolve()
+              }).catch(error => {
+                reject(error)
+              })
+            })
+          },
+          logout({ commit, state, dispatch }) {
+            return new Promise((resolve, reject) => {
+              // API.account.logout(state.token).then(() => {
+                commit('SET_TOKEN', '')
+                commit('SET_NAME', '')
+                removeToken()
+                resetRouter()
+        
+                // reset visited views and cached views
+                // to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2485
+                resolve()
+              // }).catch(error => {
+              //   reject(error)
+              // })
+            })
+          },
+    }
+})
+export default store

+ 62 - 0
src/utils/WebSocketClient.js

@@ -0,0 +1,62 @@
+import axios from 'axios'
+ 
+export default class WebSocketClient {
+  constructor(url, apiEndpoint) {
+    this.url = url
+    this.apiEndpoint = apiEndpoint
+    this.socket = null
+    this.connected = false
+    this.messages = []
+    this.error = null
+  }
+ 
+  connect() {
+    if (this.socket && this.socket.readyState === WebSocket.OPEN) {
+      return
+    }
+    this.socket = new WebSocket(this.url)
+ 
+    this.socket.addEventListener('open', () => {
+      console.log('WebSocket connected')
+      this.connected = true
+      // 连接成功后发送 API 请求
+      //this.sendApiRequest()
+    })
+    this.socket.addEventListener('message', (event) => {
+      const message = JSON.parse(event.data)
+      //const message = event.data;
+      this.messages.push(message)
+    })
+    this.socket.addEventListener('error', (event) => {
+      console.error('WebSocket error:', event)
+      this.error = event
+    })
+    this.socket.addEventListener('close', () => {
+      console.log('WebSocket closed')
+      this.connected = false
+    })
+  }
+ 
+  disconnect() {
+    if (this.socket) {
+      this.socket.close()
+    }
+  }
+ 
+  sendMessage(message) {
+    if (this.socket.readyState !== WebSocket.OPEN) {
+      return
+    }
+    this.socket.send(JSON.stringify(message))
+  }
+ 
+  sendApiRequest() {
+    axios.get(this.apiEndpoint).then(response => {
+      // 处理 API 返回数据并发送给服务端
+      const data = response.data
+      this.sendMessage(data)
+    }).catch(error => {
+      console.error('API request error:', error)
+    })
+  }
+}

+ 21 - 0
src/utils/auth.js

@@ -0,0 +1,21 @@
+import VueCookies from 'vue-cookies'
+const TokenKey = 'token'
+
+export function getToken() {
+  // return Cookies.get(TokenKey)
+  return VueCookies.get(TokenKey)
+}
+
+export function setToken(token) {
+  // var time = new Date(new Date().getTime() + 2 * 60 * 60 * 1000);
+  // return Cookies.set(TokenKey, token, {
+  //   expires: time
+  // })
+  // return Cookies.set(TokenKey, token)
+  return VueCookies.set(TokenKey, token, 2*60*60)
+}
+
+export function removeToken() {
+  // return Cookies.remove(TokenKey)
+  return VueCookies.remove(TokenKey)
+}

+ 513 - 0
src/utils/index.js

@@ -0,0 +1,513 @@
+export function parseTime(time, cFormat) {
+    if (arguments.length === 0) {
+      return null
+    }
+    const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
+    let date
+    if (typeof time === 'object') {
+      date = time
+    } else {
+      if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
+        time = parseInt(time)
+      }
+      if ((typeof time === 'number') && (time.toString().length === 10)) {
+        time = time * 1000
+      }
+      date = new Date(time)
+    }
+    const formatObj = {
+      y: date.getFullYear(),
+      m: date.getMonth() + 1,
+      d: date.getDate(),
+      h: date.getHours(),
+      i: date.getMinutes(),
+      s: date.getSeconds(),
+      a: date.getDay()
+    }
+    const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
+      const value = formatObj[key]
+      // Note: getDay() returns 0 on Sunday
+      if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
+      return value.toString().padStart(2, '0')
+    })
+    return time_str
+  }
+  
+  /**
+   * @param {number} time
+   * @param {string} option
+   * @returns {string}
+   */
+  export function formatTime(time, option) {
+    if (('' + time).length === 10) {
+      time = parseInt(time) * 1000
+    } else {
+      time = +time
+    }
+    const d = new Date(time)
+    const now = Date.now()
+  
+    const diff = (now - d) / 1000
+  
+    if (diff < 30) {
+      return '刚刚'
+    } else if (diff < 3600) {
+      // less 1 hour
+      return Math.ceil(diff / 60) + '分钟前'
+    } else if (diff < 3600 * 24) {
+      return Math.ceil(diff / 3600) + '小时前'
+    } else if (diff < 3600 * 24 * 2) {
+      return '1天前'
+    }
+    if (option) {
+      return parseTime(time, option)
+    } else {
+      return (
+        d.getMonth() +
+        1 +
+        '月' +
+        d.getDate() +
+        '日' +
+        d.getHours() +
+        '时' +
+        d.getMinutes() +
+        '分'
+      )
+    }
+  }
+  
+  /**
+   * @param {string} url
+   * @returns {Object}
+   */
+  export function getQueryObject(url) {
+    url = url == null ? window.location.href : url
+    const search = url.substring(url.lastIndexOf('?') + 1)
+    const obj = {}
+    const reg = /([^?&=]+)=([^?&=]*)/g
+    search.replace(reg, (rs, $1, $2) => {
+      const name = decodeURIComponent($1)
+      let val = decodeURIComponent($2)
+      val = String(val)
+      obj[name] = val
+      return rs
+    })
+    return obj
+  }
+  
+  /**
+   * @param {string} input value
+   * @returns {number} output value
+   */
+  export function byteLength(str) {
+    // returns the byte length of an utf8 string
+    let s = str.length
+    for (var i = str.length - 1; i >= 0; i--) {
+      const code = str.charCodeAt(i)
+      if (code > 0x7f && code <= 0x7ff) s++
+      else if (code > 0x7ff && code <= 0xffff) s += 2
+      if (code >= 0xDC00 && code <= 0xDFFF) i--
+    }
+    return s
+  }
+  
+  /**
+   * @param {Array} actual
+   * @returns {Array}
+   */
+  export function cleanArray(actual) {
+    const newArray = []
+    for (let i = 0; i < actual.length; i++) {
+      if (actual[i]) {
+        newArray.push(actual[i])
+      }
+    }
+    return newArray
+  }
+  
+  /**
+   * @param {Object} json
+   * @returns {Array}
+   */
+  export function param(json) {
+    if (!json) return ''
+    return cleanArray(
+      Object.keys(json).map(key => {
+        if (json[key] === undefined) return ''
+        return encodeURIComponent(key) + '=' + encodeURIComponent(json[key])
+      })
+    ).join('&')
+  }
+  
+  /**
+   * @param {string} url
+   * @returns {Object}
+   */
+  export function param2Obj(url) {
+    const search = url.split('?')[1]
+    if (!search) {
+      return {}
+    }
+    return JSON.parse(
+      '{"' +
+        decodeURIComponent(search)
+          .replace(/"/g, '\\"')
+          .replace(/&/g, '","')
+          .replace(/=/g, '":"')
+          .replace(/\+/g, ' ') +
+        '"}'
+    )
+  }
+  
+  /**
+   * @param {string} val
+   * @returns {string}
+   */
+  export function html2Text(val) {
+    const div = document.createElement('div')
+    div.innerHTML = val
+    return div.textContent || div.innerText
+  }
+  
+  /**
+   * Merges two objects, giving the last one precedence
+   * @param {Object} target
+   * @param {(Object|Array)} source
+   * @returns {Object}
+   */
+  export function objectMerge(target, source) {
+    if (typeof target !== 'object') {
+      target = {}
+    }
+    if (Array.isArray(source)) {
+      return source.slice()
+    }
+    Object.keys(source).forEach(property => {
+      const sourceProperty = source[property]
+      if (typeof sourceProperty === 'object') {
+        target[property] = objectMerge(target[property], sourceProperty)
+      } else {
+        target[property] = sourceProperty
+      }
+    })
+    return target
+  }
+  
+  /**
+   * @param {HTMLElement} element
+   * @param {string} className
+   */
+  export function toggleClass(element, className) {
+    if (!element || !className) {
+      return
+    }
+    let classString = element.className
+    const nameIndex = classString.indexOf(className)
+    if (nameIndex === -1) {
+      classString += '' + className
+    } else {
+      classString =
+        classString.substr(0, nameIndex) +
+        classString.substr(nameIndex + className.length)
+    }
+    element.className = classString
+  }
+  
+  /**
+   * @param {string} type
+   * @returns {Date}
+   */
+  export function getTime(type) {
+    if (type === 'start') {
+      return new Date().getTime() - 3600 * 1000 * 24 * 90
+    } else {
+      return new Date(new Date().toDateString())
+    }
+  }
+  
+  /**
+   * @param {Function} func
+   * @param {number} wait
+   * @param {boolean} immediate
+   * @return {*}
+   */
+  export function debounce(func, wait, immediate) {
+    let timeout, args, context, timestamp, result
+  
+    const later = function() {
+      // 据上一次触发时间间隔
+      const last = +new Date() - timestamp
+  
+      // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
+      if (last < wait && last > 0) {
+        timeout = setTimeout(later, wait - last)
+      } else {
+        timeout = null
+        // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
+        if (!immediate) {
+          result = func.apply(context, args)
+          if (!timeout) context = args = null
+        }
+      }
+    }
+  
+    return function(...args) {
+      context = this
+      timestamp = +new Date()
+      const callNow = immediate && !timeout
+      // 如果延时不存在,重新设定延时
+      if (!timeout) timeout = setTimeout(later, wait)
+      if (callNow) {
+        result = func.apply(context, args)
+        context = args = null
+      }
+  
+      return result
+    }
+  }
+  
+  /**
+   * This is just a simple version of deep copy
+   * Has a lot of edge cases bug
+   * If you want to use a perfect deep copy, use lodash's _.cloneDeep
+   * @param {Object} source
+   * @returns {Object}
+   */
+  export function deepClone(source) {
+    if (!source && typeof source !== 'object') {
+      throw new Error('error arguments', 'deepClone')
+    }
+    const targetObj = source.constructor === Array ? [] : {}
+    Object.keys(source).forEach(keys => {
+      if (source[keys] && typeof source[keys] === 'object') {
+        targetObj[keys] = deepClone(source[keys])
+      } else {
+        targetObj[keys] = source[keys]
+      }
+    })
+    return targetObj
+  }
+  
+  /**
+   * @param {Array} arr
+   * @returns {Array}
+   */
+  export function uniqueArr(arr) {
+    return Array.from(new Set(arr))
+  }
+  
+  /**
+   * @returns {string}
+   */
+  export function createUniqueString() {
+    const timestamp = +new Date() + ''
+    const randomNum = parseInt((1 + Math.random()) * 65536) + ''
+    return (+(randomNum + timestamp)).toString(32)
+  }
+  
+  /**
+   * Check if an element has a class
+   * @param {HTMLElement} elm
+   * @param {string} cls
+   * @returns {boolean}
+   */
+  export function hasClass(ele, cls) {
+    return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'))
+  }
+  
+  /**
+   * Add class to element
+   * @param {HTMLElement} elm
+   * @param {string} cls
+   */
+  export function addClass(ele, cls) {
+    if (!hasClass(ele, cls)) ele.className += ' ' + cls
+  }
+  
+  /**
+   * Remove class from element
+   * @param {HTMLElement} elm
+   * @param {string} cls
+   */
+  export function removeClass(ele, cls) {
+    if (hasClass(ele, cls)) {
+      const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)')
+      ele.className = ele.className.replace(reg, ' ')
+    }
+  }
+  
+  /*
+  * 解析地址栏参数
+  *
+  * @method getQueryObj
+  *
+  * @param
+  *
+  * @return {Object} 返回query参数对象
+  *
+  * */
+  export function getQueryObj () {
+    let href = window.location.href
+    let query = href.substr(href.indexOf('?') + 1)
+    let arr = query.split('&')
+    let queryObj = {}
+  
+    for (let i = 0; i < arr.length; i++) {
+      let keyVal = arr[i].split('=')
+  
+      queryObj[keyVal[0]] = keyVal[1]
+    }
+    return queryObj
+  }
+  
+  /*
+  * String过长,中间显示点
+  *
+  * @method interceptString
+  *
+  * @param {String} 目标字符串
+  * @param {Number} 目标长度后开始截取,否则返回原String
+  * @param {Number} String开始保留长度 1开始
+  * @param {Number} String结尾保留长度
+  *
+  * @return {String} xxx...xxx 或 xxx
+  * */
+  export function interceptString (str, len, m, n) {
+    if ((typeof str === 'string') && (typeof len === 'number') &&
+      (typeof m === 'number') && (typeof n === 'number')) {
+      if (len <= m + n) {
+        throw new Error('长度参数有误,开始截取长度必须大于前后保留长度的和')
+      }
+      if (str.length > len) {
+        return str.substring(0, m) + '...' + str.substring(str.length - n)
+      } else {
+        return str
+      }
+    } else {
+      throw new Error('interceptString方法,参数类型错误')
+    }
+  }
+  
+  /*
+  * 动态检测浏览器大小
+  * @method windowWidth
+  *
+  * @return {Object} 浏览器宽度
+  * */
+  export function windowWH () {
+    return {
+      screenWidth: window.screen.width, // 屏幕分辨率,宽
+      screenHeight: window.screen.height, // 屏幕分辨率,高
+      screenAvailWidth: window.screen.availWidth, // 屏幕可用大小
+      screenAvailHeight: window.screen.availHeight, // 屏幕可用大小
+      clientWidth: document.body.clientWidth, // 网页可见区域宽
+      clientHeight: document.body.clientHeight, // 网页可见区域高
+      offsetWidth: document.body.offsetWidth, // 网页可见区域宽(包括边线的宽)
+      offsetHeight: document.body.offsetHeight, // 网页可见区域高(包括边线的宽)
+      scrollWidth: document.body.scrollWidth, // 网页正文全文宽
+      scrollHeight: document.body.scrollHeight, // 网页正文全文高
+      scrollTop: document.body.scrollTop, // 网页被卷去的高
+      scrollLeft: document.body.scrollLeft, // 网页被卷去的左
+      WinScreenTop: window.screenTop, // 网页正文部分左
+      WinScrollLeft: window.screenLeft,
+    }
+  }
+  
+  /*
+  * 对象深拷贝
+  * @method deepCopy
+  * @param p {Object} 拷贝对象
+  * @param c {Object} 可选,拷贝到c对象
+  * @return {Object} 拷贝对象
+  * */
+  export function deepCopy (source) {
+    let result = {}
+  
+    for (let key in source) {
+      result[key] = typeof source[key] === 'object'
+        ? deepCopy(source[key])
+        : source[key]
+    }
+    return result
+  }
+  
+  export function deepCopyJson (p) {
+    return JSON.parse((JSON.stringify(p)))
+  }
+  
+  /*
+  * 判断为数组
+  * @method isArray
+  * @param o {all} 判断的对象
+  * @return {Boolean} 是否为数组
+  * */
+  export function isArray (o) {
+    return Object.prototype.toString.call(o) === '[object Array]'
+  }
+  
+  export function isArray2 (arr) {
+    return arr instanceof Array
+  }
+  
+  /*
+  * 判断对象
+  * @method isJsonObj
+  * @param o {all} 判断的对象
+  * @return {Boolean} 是否为对象
+  * */
+  export function isJsonObj (o) {
+    if (o instanceof Object) {
+      return o instanceof Array ? false : true // eslint-disable-line
+    } else {
+      return false
+    }
+  }
+  
+  export function arrToStr (arr, attr) { // 对象数组id属性转为字符串[{id: 1},{id, 2}] to '1,2'
+    let tempArr = []
+    arr.forEach(item => {
+      tempArr.push(item[attr])
+    })
+    return tempArr.join(',')
+  }
+  
+  /*
+  * aBC 转 a_b_c
+  * */
+  export function underscoreName (s) {
+    if (!s) {
+      return null
+    }
+    return s.replace(/([A-Z])/g, '_$1').toLowerCase()
+  }
+  
+  /*
+  * a_b_c 转 aBC
+  * */
+  export function camelName () {
+  }
+  
+  /*
+  * 上个月1号日期
+  *
+  * */
+  export function lastMonthDate () { // 上个月1号
+    var Nowdate = new Date()
+    var vYear = Nowdate.getFullYear()
+    var vMon = Nowdate.getMonth() + 1
+    // var vDay = Nowdate.getDate()
+    // 每个月的最后一天日期(为了使用月份便于查找,数组第一位设为0)
+    // var daysInMonth = new Array(0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
+    if (vMon === 1) {
+      vYear = Nowdate.getFullYear() - 1
+      vMon = 12
+    } else {
+      vMon = vMon - 1
+    }
+    if (vMon < 10) {
+      vMon = '0' + vMon
+    }
+    var date = vYear + '-' + vMon + '-01'
+    // console.log(date)
+    return new Date(date)
+  }

+ 109 - 0
src/utils/request.js

@@ -0,0 +1,109 @@
+import axios from 'axios'
+import { Message } from 'element-ui'
+import { getToken } from '@/utils/auth'
+import $router from '@/router'
+import { Loading } from 'element-ui'
+
+
+const service = axios.create({
+  baseURL: process.env.VUE_APP_BASE_API2, // url = base url + request url
+  timeout: 1000 * 60 * 60 * 5 // request timeout
+})
+
+// request interceptor
+service.interceptors.request.use(
+  config => {
+    // do something before request is sent
+    if (getToken()) {
+      // let each request carry token
+      // ['X-Token'] is a custom headers key
+      // please modify it according to the actual situation
+      config.headers['token'] = getToken()
+    }
+    // 时间
+    if (config.params) {
+      config.params.nowDate = new Date()
+    }
+    return config
+  },
+  error => {
+    // do something with request error
+    console.log(error) // for debug
+    return Promise.reject(error)
+  }
+)
+
+// response interceptor
+service.interceptors.response.use(
+  /**
+   * If you want to get http information such as headers or status
+   * Please return  response => response
+   */
+
+  /**
+   * Determine the request status by custom code
+   * Here is just an example
+   * You can also judge the status by HTTP Status Code
+   */
+  response => {
+    const res = response.data
+    // console.log(res)
+
+    // if the custom code is not 20000, it is judged as an error.
+     if (res.code && res.code !== 200) {
+      if (document.getElementsByClassName('el-message').length > 0) {
+        // 已经存在不提示
+      } else {
+        Message({
+          message: res.msg || 'Error',
+          type: 'error',
+          duration: 5 * 1000
+        })
+      }
+
+      // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
+      if (res.code === 401 && res.msg === '登录过期') {
+        store.dispatch('user/resetToken').then(() => {
+          // location.reload()
+          if (location.href.indexOf('#') > -1) {
+            const href = location.href.split('#')
+            $router.push(`/login?redirect=${href[1]}`)
+          } else {
+            $router.push(`/login`)
+          }
+        })
+      }
+      if (res.code === 10005 || res.code === 10004) { // 10005非法token, 10004用户未登录
+        store.dispatch('user/resetToken').then(() => {
+          // location.reload()
+          if (location.h.indexOf('#') > -1) {
+            const href = location.href.split('#')
+            $router.push(`/login?redirect=${href[1]}`)
+          } else {
+            $router.push(`/login`)
+          }
+        })
+      }
+      return Promise.reject(new Error(res.msg || 'Error'))
+    } else if (response.config.originResponse) {
+      return response
+    } else {
+      return res
+    }
+  },
+  error => {
+    if (document.getElementsByClassName('el-message').length > 0) {
+      // 已经存在不提示
+    } else {
+      Message({
+        // message: error.message,
+        message: '网络错误,请稍后再试!',
+        type: 'error',
+        duration: 5 * 1000
+      })
+    }
+    return Promise.reject(error)
+  }
+)
+
+export default service

+ 58 - 0
src/utils/scroll-to.js

@@ -0,0 +1,58 @@
+Math.easeInOutQuad = function(t, b, c, d) {
+    t /= d / 2
+    if (t < 1) {
+      return c / 2 * t * t + b
+    }
+    t--
+    return -c / 2 * (t * (t - 2) - 1) + b
+  }
+  
+  // requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
+  var requestAnimFrame = (function() {
+    return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) }
+  })()
+  
+  /**
+   * Because it's so fucking difficult to detect the scrolling element, just move them all
+   * @param {number} amount
+   */
+  function move(amount) {
+    document.documentElement.scrollTop = amount
+    document.body.parentNode.scrollTop = amount
+    document.body.scrollTop = amount
+  }
+  
+  function position() {
+    return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop
+  }
+  
+  /**
+   * @param {number} to
+   * @param {number} duration
+   * @param {Function} callback
+   */
+  export function scrollTo(to, duration, callback) {
+    const start = position()
+    const change = to - start
+    const increment = 20
+    let currentTime = 0
+    duration = (typeof (duration) === 'undefined') ? 500 : duration
+    var animateScroll = function() {
+      // increment the time
+      currentTime += increment
+      // find the value with the quadratic in-out easing function
+      var val = Math.easeInOutQuad(currentTime, start, change, duration)
+      // move the document.body
+      move(val)
+      // do the animation unless its over
+      if (currentTime < duration) {
+        requestAnimFrame(animateScroll)
+      } else {
+        if (callback && typeof (callback) === 'function') {
+          // the animation is done so lets callback
+          callback()
+        }
+      }
+    }
+    animateScroll()
+  }

+ 12 - 0
src/vueConfig/globalComponents/index.js

@@ -0,0 +1,12 @@
+import Vue from 'vue'
+import parentTable from '../../components/custom/BaseTable/parentTable'
+import YPageListLayout from '../../components/custom/YPageListLayout'
+//import YDetailPageLayout from '../../components/YDetailPageLayout'
+
+function plugins() {
+  Vue.component('parentTable', parentTable)
+  Vue.component('YPageListLayout', YPageListLayout)
+  //Vue.component('YDetailPageLayout', YDetailPageLayout)
+}
+
+plugins()

+ 10 - 0
src/vueConfig/index.js

@@ -0,0 +1,10 @@
+// https://webpack.js.org/guides/dependency-management/#requirecontext
+require.context('./prototype', true, /\.js$/)
+// 添加prototype
+import './prototype/api'
+import './prototype/vueCookies'
+//import './prototype/webStorage'
+// import './prototype/utils'
+
+// globalComponents
+import './globalComponents/index'

+ 6 - 0
src/vueConfig/prototype/api.js

@@ -0,0 +1,6 @@
+import Vue from 'vue'
+
+import api from '@/api'
+Object.defineProperty(Vue.prototype, '$api', {
+  value: api
+})

+ 6 - 0
src/vueConfig/prototype/utils.js

@@ -0,0 +1,6 @@
+// import Vue from 'vue'
+// import utils from '@/utils/utils'
+
+// Object.defineProperty(Vue.prototype, '$utils', {
+//   value: utils
+// })

+ 4 - 0
src/vueConfig/prototype/vueCookies.js

@@ -0,0 +1,4 @@
+import Vue from 'vue'
+import vueCookies from 'vue-cookies'
+// Vue.prototype.$vueCookies = vueCookies
+Object.defineProperty(Vue.prototype, '$vueCookies', { value: vueCookies })

+ 4 - 0
src/vueConfig/prototype/webStorage.js

@@ -0,0 +1,4 @@
+// import Vue from 'vue'
+// import webStorage from 'webStorage'
+// // Vue.prototype.$webStorage = webStorage
+// Object.defineProperty(Vue.prototype, '$webStorage', { value: webStorage })

+ 7 - 0
tailwind.js

@@ -0,0 +1,7 @@
+module.exports = {
+  theme: {
+    extend: {}
+  },
+  variants: {},
+  plugins: []
+}

+ 60 - 0
vue.config.js

@@ -0,0 +1,60 @@
+'use strict';
+const path = require('path');
+
+
+function resolve(dir) {
+  return path.join(__dirname, dir)
+}
+
+
+
+
+// If your port is set to 80,
+// use administrator privileges to execute the command line.
+// For example, Mac: sudo npm run
+// You can change the port by the following method:
+// port = 9518 npm run dev OR npm run dev --port = 9518
+const port = process.env.port || process.env.npm_config_port || 9518; // dev port
+
+// All configuration item explanations can be find in https://cli.vuejs.org/config/
+module.exports = {
+  /**
+   * You will need to set publicPath if you plan to deploy your site under a sub path,
+   * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
+   * then publicPath should be set to "/bar/".
+   * In most cases please use '/' !!!
+   * Detail: https://cli.vuejs.org/config/#publicpath
+   */
+  // publicPath: '/',
+  publicPath: './',
+  outputDir: 'dist',
+  assetsDir: './static',
+  lintOnSave: false,
+  indexPath: './index.html',
+  productionSourceMap: false,
+  devServer: {
+    port: port,
+    open: true,
+    overlay: {
+      warnings: false,
+      errors: true
+    },
+    proxy: {
+      '/apo': {
+        target: 'http://localhost:8090',
+        changeOrigin: true,
+        pathRewrite: {}
+      },
+      '/ws': {
+        target: 'http://localhost:8090',
+        ws: true,
+        changeOrigin: true,
+        pathRewrite: {
+          '^/ws' : ''
+        }
+      },
+    },
+    // before: require('./mock/mock-server.js')
+  },
+  transpileDependencies: [/node_modules[/\\\\](element-ui|vuex|vue-baidu-map|)[/\\\\]/], // 默认情况下 babel-loader 会忽略所有 node_modules 中的文件。如果你想要通过 Babel 显式转译一个依赖,可以在 transpileDependencies 选项中列出来。
+};