lrf 1 year ago
commit
5439a7139f
66 changed files with 5076 additions and 0 deletions
  1. 3 0
      .env
  2. 33 0
      .eslintrc.js
  3. 24 0
      .gitignore
  4. 0 0
      README.md
  5. 3 0
      babel.config.js
  6. 68 0
      package.json
  7. BIN
      public/favicon.ico
  8. 25 0
      public/index.html
  9. BIN
      public/保安员入职信息导入文件模板.xlsx
  10. 41 0
      src/App.vue
  11. 28 0
      src/assets/css/color-dark.css
  12. 4 0
      src/assets/css/icon.css
  13. 176 0
      src/assets/css/main.css
  14. 37 0
      src/assets/css/theme-green/color-green.css
  15. BIN
      src/assets/css/theme-green/fonts/element-icons.ttf
  16. BIN
      src/assets/css/theme-green/fonts/element-icons.woff
  17. 1 0
      src/assets/css/theme-green/index.css
  18. BIN
      src/assets/img/img.jpg
  19. BIN
      src/assets/img/login-bg.jpg
  20. BIN
      src/assets/logo.png
  21. BIN
      src/assets/zw.jpg
  22. 88 0
      src/components/HelloWorld.vue
  23. 38 0
      src/main.js
  24. 19 0
      src/plugins/axios.js
  25. 39 0
      src/plugins/check-res.js
  26. 16 0
      src/plugins/components.js
  27. 5 0
      src/plugins/element.js
  28. 6 0
      src/plugins/filters.js
  29. 27 0
      src/plugins/loading.js
  30. 4 0
      src/plugins/meta.js
  31. 33 0
      src/plugins/methods.js
  32. 21 0
      src/plugins/setting.js
  33. 65 0
      src/plugins/stomp.js
  34. 25 0
      src/plugins/var.js
  35. 134 0
      src/router/index.js
  36. 202 0
      src/store/index.js
  37. 9 0
      src/util/answer-type.js
  38. 140 0
      src/util/axios-wrapper.js
  39. 10 0
      src/util/filters.js
  40. 50 0
      src/util/methods-util.js
  41. 135 0
      src/util/print.js
  42. 69 0
      src/util/user-util.js
  43. 148 0
      src/views/baoanexam/exam/answer/index.vue
  44. 56 0
      src/views/baoanexam/exam/answer/view.vue
  45. 101 0
      src/views/baoanexam/exam/config/index.vue
  46. 149 0
      src/views/baoanexam/exam/config/proportion.vue
  47. 101 0
      src/views/baoanexam/exam/exam/index.vue
  48. 305 0
      src/views/baoanexam/exam/question/index.vue
  49. 176 0
      src/views/baoanexam/exam/questionType/index.vue
  50. 247 0
      src/views/baoanexam/security/kcbp/add.vue
  51. 389 0
      src/views/baoanexam/security/kcbp/index.vue
  52. 153 0
      src/views/baoanexam/security/kcbp/parts/info-1.vue
  53. 84 0
      src/views/baoanexam/security/kcbp/parts/layout-1.vue
  54. 233 0
      src/views/baoanexam/security/kscjcx/index.vue
  55. 226 0
      src/views/baoanexam/security/ksqkdj/index.vue
  56. 64 0
      src/views/baoanexam/security/ksqkdj/parts/achieve-1.vue
  57. 183 0
      src/views/baoanexam/security/ksqkdj/parts/info-1.vue
  58. 175 0
      src/views/baoanexam/security/ksxxdc/index.vue
  59. 139 0
      src/views/baoanexam/security/zkzcx/index.vue
  60. 142 0
      src/views/baoanexam/security/zkzdy/index.vue
  61. 150 0
      src/views/baoanexam/security/zkzdyrz/index.vue
  62. 36 0
      src/views/baoanexam/security/zkzfmdy/index.vue
  63. 36 0
      src/views/homeIndex/index.vue
  64. 37 0
      src/views/index.vue
  65. 126 0
      src/views/login.vue
  66. 42 0
      vue.config.js

+ 3 - 0
.env

@@ -0,0 +1,3 @@
+VUE_APP_AXIOS_BASE_URL = ''
+VUE_APP_ROUTER="baoanexam"
+VUE_APP_HOST="http://baoan.fwedzgc.com:8090"

+ 33 - 0
.eslintrc.js

@@ -0,0 +1,33 @@
+// https://eslint.org/docs/user-guide/configuring
+
+module.exports = {
+  root: true,
+  env: {
+    node: true,
+  },
+  extends: ["plugin:vue/essential", "@vue/prettier"],
+  plugins: ["vue"],
+  rules: {
+    "max-len": [
+      "warn",
+      {
+        code: 10000,
+      },
+    ],
+    "no-unused-vars": "off",
+    "no-console": "off",
+    "prettier/prettier": [
+      "warn",
+      {
+        singleQuote: true,
+        trailingComma: "es5",
+        bracketSpacing: true,
+        jsxBracketSameLine: true,
+        printWidth: 160,
+      },
+    ],
+  },
+  parserOptions: {
+    parser: "babel-eslint",
+  },
+};

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+.DS_Store
+node_modules
+package-lock.json
+/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?

+ 0 - 0
README.md


+ 3 - 0
babel.config.js

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

+ 68 - 0
package.json

@@ -0,0 +1,68 @@
+{
+  "name": "baoan-exam",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "@stomp/stompjs": "^6.1.0",
+    "axios": "^0.21.1",
+    "babel-polyfill": "^6.26.0",
+    "core-js": "^3.6.5",
+    "echarts": "^5.1.0",
+    "element-ui": "^2.15.1",
+    "html2canvas": "^1.0.0-rc.7",
+    "jsonwebtoken": "^8.5.1",
+    "jspdf": "^2.3.1",
+    "lodash": "^4.17.21",
+    "moment": "^2.29.1",
+    "naf-core": "^0.1.2",
+    "vue": "^2.6.11",
+    "vue-amap": "^0.5.10",
+    "vue-i18n": "^8.24.4",
+    "vue-json-excel": "^0.3.0",
+    "vue-meta": "^2.4.0",
+    "vue-router": "^3.2.0",
+    "vuex": "^3.4.0",
+    "wangeditor": "3.1.1"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "~4.5.0",
+    "@vue/cli-plugin-eslint": "~4.5.0",
+    "@vue/cli-plugin-router": "~4.5.0",
+    "@vue/cli-plugin-vuex": "~4.5.0",
+    "@vue/cli-service": "~4.5.0",
+    "@vue/eslint-config-prettier": "^6.0.0",
+    "babel-eslint": "^10.1.0",
+    "eslint": "^6.7.2",
+    "eslint-plugin-prettier": "^3.3.1",
+    "eslint-plugin-vue": "^6.2.2",
+    "less": "^3.0.4",
+    "less-loader": "^5.0.0",
+    "prettier": "^2.2.1",
+    "vue-template-compiler": "^2.6.11"
+  },
+  "eslintConfig": {
+    "root": true,
+    "env": {
+      "node": true
+    },
+    "extends": [
+      "plugin:vue/essential",
+      "eslint:recommended",
+      "@vue/prettier"
+    ],
+    "parserOptions": {
+      "parser": "babel-eslint"
+    },
+    "rules": {}
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not dead"
+  ]
+}

BIN
public/favicon.ico


+ 25 - 0
public/index.html

@@ -0,0 +1,25 @@
+<!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>加载中...</title>
+    <style>
+      body{
+          overflow: hidden !important;
+      }
+    </style>
+    <script
+      src="https://webapi.amap.com/maps?v=2.0&key=47947a002b2cddf99df02a1a5b1bc198&plugin=AMap.Scale,AMap.ToolBar"></script>
+    <script type="text/javascript" src="https://webapi.amap.com/loca?v=2.0.0&key=47947a002b2cddf99df02a1a5b1bc198"></script>
+  </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>

BIN
public/保安员入职信息导入文件模板.xlsx


+ 41 - 0
src/App.vue

@@ -0,0 +1,41 @@
+<template>
+  <div id="App">
+    <router-view></router-view>
+  </div>
+</template>
+
+<script>
+export default {
+  metaInfo: { title: 'App' },
+  name: 'App',
+  props: {},
+  components: {},
+  data: function () {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {},
+};
+</script>
+
+<style>
+@import './assets/css/main.css';
+@import './assets/css/color-dark.css';
+body {
+  margin: 0;
+}
+.w_1200 {
+  width: 1200px;
+  margin: 0 auto;
+}
+p {
+  margin: 0;
+  padding: 0;
+}
+.textOver {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+</style>

+ 28 - 0
src/assets/css/color-dark.css

@@ -0,0 +1,28 @@
+.header{
+    background-color: #242f42;
+}
+.login-wrap{
+    background: #324157;
+}
+.plugins-tips{
+    background: #eef1f6;
+}
+.plugins-tips a{
+    color: #20a0ff;
+}
+.el-upload--text em {
+    color: #20a0ff;
+}
+.pure-button{
+    background: #20a0ff;
+}
+.tags-li.active {
+    border: 1px solid #409EFF;
+    background-color: #409EFF;
+}
+.message-title{
+    color: #20a0ff;
+}
+.collapse-btn:hover{
+    background: rgb(40,52,70);
+}

+ 4 - 0
src/assets/css/icon.css

@@ -0,0 +1,4 @@
+
+    [class*=" el-icon-lx"], [class^=el-icon-lx] {
+        font-family: lx-iconfont!important;
+    }

+ 176 - 0
src/assets/css/main.css

@@ -0,0 +1,176 @@
+* {
+    margin: 0;
+    padding: 0;
+}
+
+html,
+body,
+#app,
+.wrapper {
+    width: 100%;
+    height: 100%;
+}
+
+body {
+    font-family: 'PingFang SC', "Helvetica Neue", Helvetica, "microsoft yahei", arial, STHeiTi, sans-serif;
+}
+
+a {
+    text-decoration: none
+}
+
+
+.content-box {
+    position: absolute;
+    left: 200px;
+    right: 0;
+    top: 60px;
+    bottom: 0;
+    padding-bottom: 30px;
+    -webkit-transition: left .3s ease-in-out;
+    transition: left .3s ease-in-out;
+    background: #f0f0f0;
+}
+
+.content {
+    width: 100%;
+    height: 100%;
+    padding: 10px;
+    box-sizing: border-box;
+}
+
+.content-collapse {
+    left: 65px;
+}
+
+.container {
+    width: 100%;
+    padding: 30px;
+    background: #fff;
+    border: 1px solid #ddd;
+    border-radius: 5px;
+}
+
+.crumbs {
+    margin: 10px 0;
+}
+
+.el-table th {
+    background-color: #f5f7fa !important;
+}
+
+.pagination {
+    margin: 20px 0;
+    text-align: right;
+}
+
+.plugins-tips {
+    padding: 20px 10px;
+    margin-bottom: 20px;
+}
+
+.el-button+.el-tooltip {
+    margin-left: 10px;
+}
+
+.el-table tr:hover {
+    background: #f6faff;
+}
+
+.mgb20 {
+    margin-bottom: 20px;
+}
+
+.move-enter-active,
+.move-leave-active {
+    transition: opacity .5s;
+}
+
+.move-enter,
+.move-leave {
+    opacity: 0;
+}
+
+/*BaseForm*/
+
+.form-box {
+    width: 600px;
+}
+
+.form-box .line {
+    text-align: center;
+}
+
+.el-time-panel__content::after,
+.el-time-panel__content::before {
+    margin-top: -7px;
+}
+
+.el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default) {
+    padding-bottom: 0;
+}
+
+/*Upload*/
+
+.pure-button {
+    width: 150px;
+    height: 40px;
+    line-height: 40px;
+    text-align: center;
+    color: #fff;
+    border-radius: 3px;
+}
+
+.g-core-image-corp-container .info-aside {
+    height: 45px;
+}
+
+.el-upload--text {
+    background-color: #fff;
+    border: 1px dashed #d9d9d9;
+    border-radius: 6px;
+    box-sizing: border-box;
+    width: 360px;
+    height: 180px;
+    text-align: center;
+    cursor: pointer;
+    position: relative;
+    overflow: hidden;
+}
+
+.el-upload--text .el-icon-upload {
+    font-size: 67px;
+    color: #97a8be;
+    margin: 40px 0 16px;
+    line-height: 50px;
+}
+
+.el-upload--text {
+    color: #97a8be;
+    font-size: 14px;
+    text-align: center;
+}
+
+.el-upload--text em {
+    font-style: normal;
+}
+
+/*VueEditor*/
+
+.ql-container {
+    min-height: 400px;
+}
+
+.ql-snow .ql-tooltip {
+    transform: translateX(117.5px) translateY(10px) !important;
+}
+
+.editor-btn {
+    margin-top: 20px;
+}
+
+/*markdown*/
+
+.v-note-wrapper .v-note-panel {
+    min-height: 500px;
+}

+ 37 - 0
src/assets/css/theme-green/color-green.css

@@ -0,0 +1,37 @@
+.header{
+    background-color: #07c4a8;
+}
+.login-wrap{
+    background: rgba(56, 157, 170, 0.82);;
+}
+.plugins-tips{
+    background: #f2f2f2;
+}
+.plugins-tips a{
+    color: #00d1b2;
+}
+.el-upload--text em {
+    color: #00d1b2;
+}
+.pure-button{
+    background: #00d1b2;
+}
+.pagination > .active > a, .pagination > .active > a:hover, .pagination > .active > a:focus, .pagination > .active > span, .pagination > .active > span:hover, .pagination > .active > span:focus {
+    background-color: #00d1b2 !important;
+    border-color: #00d1b2 !important;
+}
+.tags-li.active {
+    border: 1px solid #00d1b2 !important;
+    background-color: #00d1b2 !important;
+}
+.collapse-btn:hover{
+    background: #00d1b2;
+}
+.el-submenu__title i{
+  color: #000000 !important;
+  font-size: 18px !important;
+  font-weight: bold !important;
+}
+.sidebar{
+   background-color: #00d1b2 !important;
+}

BIN
src/assets/css/theme-green/fonts/element-icons.ttf


BIN
src/assets/css/theme-green/fonts/element-icons.woff


File diff suppressed because it is too large
+ 1 - 0
src/assets/css/theme-green/index.css


BIN
src/assets/img/img.jpg


BIN
src/assets/img/login-bg.jpg


BIN
src/assets/logo.png


BIN
src/assets/zw.jpg


+ 88 - 0
src/components/HelloWorld.vue

@@ -0,0 +1,88 @@
+<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-router" target="_blank" rel="noopener">router</a>
+      </li>
+      <li>
+        <a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex" target="_blank" rel="noopener">vuex</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 lang="less">
+h3 {
+  margin: 40px 0 0;
+}
+ul {
+  list-style-type: none;
+  padding: 0;
+}
+li {
+  display: inline-block;
+  margin: 0 10px;
+}
+a {
+  color: #42b983;
+}
+</style>

+ 38 - 0
src/main.js

@@ -0,0 +1,38 @@
+import Vue from 'vue';
+import App from './App.vue';
+import router from './router';
+import store from './store';
+import '@/plugins/element.js';
+import '@/plugins/axios';
+import '@/plugins/check-res';
+import '@/plugins/meta';
+import '@/plugins/filters';
+import '@/plugins/loading';
+import '@/plugins/var';
+import '@/plugins/methods';
+import '@/plugins/setting';
+import '@/plugins/components';
+import '@common/src/assets/icon/iconfont.css';
+// 打印
+import Print from '@/util/print';
+Vue.use(Print);
+// 导数据
+import JsonExcel from 'vue-json-excel';
+Vue.component('downloadExcel', JsonExcel);
+// 管理中心页面效果布局所需插件
+import ElementUI from 'element-ui';
+import VueI18n from 'vue-i18n';
+import 'babel-polyfill';
+import { messages } from '@common/src/components/admin-frame/i18n';
+import '@common/src/components/admin-frame/directives';
+// import 'element-ui/lib/theme-chalk/index.css';
+import '@common/src/assets/css/theme-green/index.css'; // 浅绿色主题
+import '@common/src/assets/css/icon.css';
+import '@common/src/assets/css/theme-green/color-green.css'; // 浅绿色主题
+
+Vue.config.productionTip = false;
+Vue.use(VueI18n);
+Vue.use(ElementUI, { size: 'small' });
+const i18n = new VueI18n({ locale: 'zh', messages });
+new Vue({ router, store, i18n, render: (h) => h(App) }).$mount('#app');
+window.vm = new Vue({ router });

+ 19 - 0
src/plugins/axios.js

@@ -0,0 +1,19 @@
+import Vue from 'vue';
+import AxiosWrapper from '@/util/axios-wrapper';
+
+const Plugin = {
+  install(vue, options) {
+    // 3. 注入组件
+    vue.mixin({
+      created() {
+        if (this.$store && !this.$store.$axios) {
+          this.$store.$axios = this.$axios;
+        }
+      },
+    });
+    // 4. 添加实例方法
+    vue.prototype.$axios = new AxiosWrapper(options);
+  },
+};
+
+Vue.use(Plugin, { baseUrl: process.env.VUE_APP_AXIOS_BASE_URL });

+ 39 - 0
src/plugins/check-res.js

@@ -0,0 +1,39 @@
+/* eslint-disable no-underscore-dangle */
+/* eslint-disable no-param-reassign */
+/* eslint-disable no-unused-vars */
+/* eslint-disable no-shadow */
+import Vue from 'vue';
+import _ from 'lodash';
+import { Message } from 'element-ui';
+
+const vm = new Vue({});
+const Plugin = {
+  install(Vue, options) {
+    // 4. 添加实例方法
+    Vue.prototype.$checkRes = (res, okText, errText) => {
+      let _okText = okText;
+      let _errText = errText;
+      if (!_.isFunction(okText) && _.isObject(okText) && okText != null) {
+        ({ okText: _okText, errText: _errText } = okText);
+      }
+      const { errcode = 0, errmsg } = res || {};
+      if (errcode === 0) {
+        if (_.isFunction(_okText)) {
+          return _okText();
+        }
+        if (_okText) {
+          Message.success(_okText);
+        }
+        return true;
+      }
+      if (_.isFunction(_errText)) {
+        return _errText();
+      }
+      Message.error(_errText || errmsg);
+      // Message({ message: _errText || errmsg, duration: 60000 });
+      return false;
+    };
+  },
+};
+
+Vue.use(Plugin);

+ 16 - 0
src/plugins/components.js

@@ -0,0 +1,16 @@
+import Vue from 'vue';
+import dataTable from '@common/src/components/frame/filter-page-table.vue';
+import dataForm from '@common/src/components/frame/form.vue';
+import eUpload from '@common/src/components/frame/e-upload.vue';
+import sUpload from '@common/src/components/frame/s-upload.vue';
+import eDateRange from '@common/src/components/frame/e-dateRange.vue';
+const Plugin = (vue) => {
+  vue.prototype.$dev_mode = process.env.NODE_ENV === 'development';
+  vue.component('data-table', dataTable);
+  vue.component('data-form', dataForm);
+  vue.component('eUpload', eUpload);
+  vue.component('sUpload', sUpload);
+  vue.component('eDateRange', eDateRange);
+};
+
+Vue.use(Plugin);

+ 5 - 0
src/plugins/element.js

@@ -0,0 +1,5 @@
+import Vue from 'vue';
+import Element from 'element-ui';
+import 'element-ui/lib/theme-chalk/index.css';
+
+Vue.use(Element);

+ 6 - 0
src/plugins/filters.js

@@ -0,0 +1,6 @@
+import Vue from 'vue';
+import filters from '@/util/filters';
+
+for (const method in filters) {
+  Vue.filter(method, filters[method]);
+}

+ 27 - 0
src/plugins/loading.js

@@ -0,0 +1,27 @@
+/* eslint-disable no-console */
+/* eslint-disable no-param-reassign */
+
+import Vue from 'vue';
+
+const Plugin = {
+  // eslint-disable-next-line no-unused-vars
+  install(vue, options) {
+    // 3. 注入组件
+    vue.mixin({
+      created() {
+        // eslint-disable-next-line no-underscore-dangle
+        const isRoot = this.constructor === Vue;
+        // console.log(`rootId:${rootVue_uid}; thisId:${this._uid}`);
+        // if (rootVue_uid !== 3) {
+        //   console.log(this);
+        // }
+        if (isRoot) {
+          const el = document.getElementById('loading');
+          if (el) el.style.display = 'none';
+        }
+      },
+    });
+  },
+};
+
+Vue.use(Plugin, { baseUrl: process.env.VUE_APP_AXIOS_BASE_URL });

+ 4 - 0
src/plugins/meta.js

@@ -0,0 +1,4 @@
+import Vue from 'vue';
+import Meta from 'vue-meta';
+
+Vue.use(Meta);

+ 33 - 0
src/plugins/methods.js

@@ -0,0 +1,33 @@
+import Vue from 'vue';
+import _ from 'lodash';
+const Plugin = {
+  install(Vue, options) {
+    // 3. 注入组件
+    Vue.mixin({
+      created() {
+        if (this.$store && !this.$store.$toUndefined) {
+          this.$store.$toUndefined = this.$toUndefined;
+        }
+      },
+    });
+    // 4. 添加实例方法
+    Vue.prototype.$toUndefined = (object) => {
+      let keys = Object.keys(object);
+      keys.map((item) => {
+        object[item] = object[item] === '' ? (object[item] = undefined) : object[item];
+      });
+      return object;
+    };
+    Vue.prototype.$turnTo = (item) => {
+      if (item.info_type == 1) {
+        window.open(item.url);
+      } else {
+        let router = window.vm.$router;
+        let route = window.vm.$route.path;
+        router.push({ path: `/info/detail?id=${item.id}` });
+      }
+    };
+  },
+};
+
+Vue.use(Plugin);

+ 21 - 0
src/plugins/setting.js

@@ -0,0 +1,21 @@
+import Vue from 'vue';
+
+Vue.config.weixin = {
+  // baseUrl: process.env.BASE_URL + 'weixin',
+  baseUrl: `http://${location.host}`,
+};
+
+Vue.config.stomp = {
+  // brokerURL: 'ws://192.168.1.118/ws',
+  brokerURL: '/ws', // ws://${location.host}/ws
+  // brokerURL: 'ws://127.0.0.1:8000/ws',
+  connectHeaders: {
+    host: 'platform',
+    login: 'visit', //visit
+    passcode: 'visit', //visit123
+  },
+  // debug: true,
+  reconnectDelay: 5000,
+  heartbeatIncoming: 4000,
+  heartbeatOutgoing: 4000,
+};

+ 65 - 0
src/plugins/stomp.js

@@ -0,0 +1,65 @@
+/**
+ * 基于WebStomp的消息处理插件
+ */
+
+import Vue from 'vue';
+import _ from 'lodash';
+import assert from 'assert';
+import { Client } from '@stomp/stompjs/esm5/client';
+
+const Plugin = {
+  install(Vue, options) {
+    assert(_.isObject(options));
+    if (options.debug && !_.isFunction(options.debug)) {
+      options.debug = (str) => {
+        console.log(str);
+      };
+    }
+    assert(_.isString(options.brokerURL));
+    if (!options.brokerURL.startsWith('ws://')) {
+      options.brokerURL = `ws://${location.host}${options.brokerURL}`;
+    }
+
+    // 3. 注入组件
+    Vue.mixin({
+      beforeDestroy: function () {
+        if (this.$stompClient) {
+          this.$stompClient.deactivate();
+          delete this.$stompClient;
+        }
+      },
+    });
+
+    // 4. 添加实例方法
+    Vue.prototype.$stomp = function (subscribes = {}) {
+      // connect to mq
+      const client = new Client(options);
+      client.onConnect = (frame) => {
+        // Do something, all subscribes must be done is this callback
+        // This is needed because this will be executed after a (re)connect
+        console.log('[stomp] connected');
+        Object.keys(subscribes)
+          .filter((p) => _.isFunction(subscribes[p]))
+          .forEach((key) => {
+            client.subscribe(key, subscribes[key]);
+          });
+      };
+
+      client.onStompError = (frame) => {
+        // Will be invoked in case of error encountered at Broker
+        // Bad login/passcode typically will cause an error
+        // Complaint brokers will set `message` header with a brief message. Body may contain details.
+        // Compliant brokers will terminate the connection after any error
+        console.log('Broker reported error: ' + frame.headers['message']);
+        console.log('Additional details: ' + frame.body);
+      };
+
+      client.activate();
+
+      this.$stompClient = client;
+    };
+  },
+};
+export default () => {
+  Vue.use(Plugin, Vue.config.stomp);
+};

+ 25 - 0
src/plugins/var.js

@@ -0,0 +1,25 @@
+import Vue from 'vue';
+import _ from 'lodash';
+
+const getSiteId = () => {
+  let host = `${window.location.hostname}`; //`999991.smart.jilinjobswx.cn ${window.location.hostname}`
+  let schId;
+  host = host.replace('http://', '');
+  let arr = host.split('.');
+  if (arr.length > 0) {
+    schId = arr[0];
+    if (schId === 'smart') schId = 'master';
+    else `${schId}`.includes('localhost') || `${schId}`.includes('127.0.0.1') ? (schId = '99991') : '';
+    sessionStorage.setItem('schId', `${schId}`.includes('localhost') || `${schId}`.includes('127.0.0.1') ? '99991' : schId);
+  }
+  return schId;
+};
+const Plugin = {
+  install(vue, options) {
+    // 4. 添加实例方法
+    vue.prototype.$limit = 10;
+    vue.prototype.$site = getSiteId();
+  },
+};
+
+Vue.use(Plugin);

+ 134 - 0
src/router/index.js

@@ -0,0 +1,134 @@
+import Vue from 'vue';
+import VueRouter from 'vue-router';
+import store from '@/store/index';
+const _ = require('lodash');
+const originalPush = VueRouter.prototype.push;
+VueRouter.prototype.push = function push(location) {
+  return originalPush.call(this, location).catch((err) => err);
+};
+Vue.use(VueRouter);
+// 考试端-保安员管理
+const security = [
+  {
+    path: '/baoanexam/security/kcbp',
+    meta: { title: '考场编排' },
+    component: () => import('../views/baoanexam/security/kcbp/index.vue'),
+  },
+  {
+    path: '/baoanexam/security/kcbp/add',
+    meta: { title: '考场编排-添加' },
+    component: () => import('../views/baoanexam/security/kcbp/add.vue'),
+  },
+  {
+    path: '/baoanexam/security/zkzdy',
+    meta: { title: '准考证打印' },
+    component: () => import('../views/baoanexam/security/zkzdy/index.vue'),
+  },
+  {
+    path: '/baoanexam/security/zkzfmdy',
+    meta: { title: '准考证封面打印' },
+    component: () => import('../views/baoanexam/security/zkzfmdy/index.vue'),
+  },
+  {
+    path: '/baoanexam/security/zkzcx',
+    meta: { title: '准考证查询' },
+    component: () => import('../views/baoanexam/security/zkzcx/index.vue'),
+  },
+  {
+    path: '/baoanexam/security/ksxxdc',
+    meta: { title: '考生信息导出' },
+    component: () => import('../views/baoanexam/security/ksxxdc/index.vue'),
+  },
+  {
+    path: '/baoanexam/security/ksqkdj',
+    meta: { title: '考试情况登记' },
+    component: () => import('../views/baoanexam/security/ksqkdj/index.vue'),
+  },
+  {
+    path: '/baoanexam/security/kscjcx',
+    meta: { title: '考试成绩查询' },
+    component: () => import('../views/baoanexam/security/kscjcx/index.vue'),
+  },
+  {
+    path: '/baoanexam/security/zkzdyrz',
+    meta: { title: '准考证打印日志' },
+    component: () => import('../views/baoanexam/security/zkzdyrz/index.vue'),
+  },
+];
+const baoanexam = [
+  {
+    path: '/exam/questionType',
+    meta: { title: '考试题型设置' },
+    component: () => import('../views/baoanexam/exam/questionType/index.vue'),
+  },
+  {
+    path: '/exam/config',
+    meta: { title: '考试设置' },
+    component: () => import('../views/baoanexam/exam/config/index.vue'),
+  },
+  {
+    path: '/exam/question',
+    meta: { title: '考试题库' },
+    component: () => import('../views/baoanexam/exam/question/index.vue'),
+  },
+  {
+    path: '/exam/exam',
+    meta: { title: '试卷管理' },
+    component: () => import('../views/baoanexam/exam/exam/index.vue'),
+  },
+  {
+    path: '/exam/answer',
+    meta: { title: '考试结果' },
+    component: () => import('../views/baoanexam/exam/answer/index.vue'),
+  },
+];
+const web = [
+  {
+    path: '/',
+    redirect: '/adminCenter/homeIndex',
+  },
+  // 管理登录
+  {
+    path: '/login',
+    name: 'login',
+    meta: { title: '管理登录', noCheck: true },
+    component: () => import('../views/login.vue'),
+  },
+  // 管理中心
+  {
+    path: '/',
+    name: 'adminCenter',
+    component: () => import('@common/src/components/admin-frame/Home.vue'),
+    children: [
+      {
+        path: '/adminCenter/homeIndex',
+        name: 'admin_homeIndex',
+        meta: { title: '首页' },
+        component: () => import('../views/homeIndex/index.vue'),
+      },
+      ...baoanexam,
+      ...security,
+    ],
+  },
+];
+const routes = [...web];
+const router = new VueRouter({
+  mode: 'history',
+  base: process.env.VUE_APP_ROUTER,
+  routes,
+});
+router.beforeEach((to, from, next) => {
+  document.title = `${to.meta.title} `;
+  const noCheck = _.get(to, 'meta.noCheck', false);
+  if (noCheck) next();
+  else {
+    const token = localStorage.getItem('token');
+    if (!token) next('/login');
+    else {
+      store.commit('setToken', token, { root: true });
+      next();
+    }
+  }
+});
+
+export default router;

+ 202 - 0
src/store/index.js

@@ -0,0 +1,202 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import jwt from 'jsonwebtoken';
+import * as ustate from '@common/src/store/user/state';
+import * as umutations from '@common/src/store/user/mutations';
+// 保安员报名-基本信息表
+import security_guard_base from '@common/src/store/security_guard_base';
+// 保安员报名-其他信息表
+import security_guard_others from '@common/src/store/security_guard_others';
+// 保安员报名-人像采集
+import security_guard_collect from '@common/src/store/security_guard_collect';
+// 保安员统计
+import baoan_statis from '@common/src/store/baoan_statis';
+//电子资料表
+import digital_files from '@common/src/store/digital_files';
+// 政审,报名审核,审批
+import security_guard_qualifications from '@common/src/store/security_guard_qualifications';
+// 考场信息编排
+import examination_room from '@common/src/store/examination_room';
+// 报名缴费
+import examination_examinee from '@common/src/store/examination_examinee';
+// 保安员证件表
+import certificates_base from '@common/src/store/certificates_base';
+// 保安员证件发放表
+import certificates_grant from '@common/src/store/certificates_grant';
+// 保安员证件打印日志
+import certificates_print_log from '@common/src/store/certificates_print_log';
+// 保安员补打申请表
+import certificates_print_again from '@common/src/store/certificates_print_again';
+// 日常检查表
+import daily_inspection from '@common/src/store/daily_inspection';
+// 企业奖励表
+import enterprise_reward from '@common/src/store/enterprise_reward';
+// 投诉信息表
+import complaints_info from '@common/src/store/complaints_info';
+// 协查通报表
+import assistance_investigation from '@common/src/store/assistance_investigation';
+// 协查通报回复表
+import assistance_investigation_reply from '@common/src/store/assistance_investigation_reply';
+// 综合信息查询
+// 武器装备
+import company_equipment from '@common/src/store/company_equipment';
+// 车辆信息
+import company_car from '@common/src/store/company_car';
+// 枪支信息
+import company_gun from '@common/src/store/company_gun';
+// 采集信息预约记录信息
+import reserve_collect from '@common/src/store/reserve_collect';
+// 已预约采集信息记录
+import reserve_record from '@common/src/store/reserve_record';
+
+// 部门表
+import police_department from '@common/src/store/police_department';
+import login from '@common/src/store/login';
+import system_account from '@common/src/store/system_account';
+import company_base from '@common/src/store/company_base';
+import company_applicant from '@common/src/store/company_applicant';
+import company_shareholders from '@common/src/store/company_shareholders';
+import company_managers from '@common/src/store/company_managers';
+import company_information from '@common/src/store/company_information';
+import company_teachers from '@common/src/store/company_teachers';
+import company_person_charge from '@common/src/store/company_person_charge';
+import company_service_area from '@common/src/store/company_service_area';
+import company_legal_person from '@common/src/store/company_legal_person';
+// 服务对象
+import company_service_object from '@common/src/store/company_service_object';
+// 服务许可证表
+import company_licence from '@common/src/store/company_licence';
+// 服务许可证发放表
+import company_licence_grant from '@common/src/store/company_licence_grant';
+
+import company_keep from '@common/src/store/company_keep';
+
+import company_keep_grant from '@common/src/store/company_keep_grant';
+
+// 企业入职表
+import company_baoan_work from '@common/src/store/company_baoan_work';
+
+// 企业审核
+import company_examine from '@common/src/store/company_examine';
+// 企业受理
+import company_accept from '@common/src/store/company_accept';
+// 法人变更申请
+import company_legal_change from '@common/src/store/company_legal_change';
+// 保安资讯信息
+import baoan_news from '@common/src/store/baoan_news';
+// 异常警告表
+import baoan_work_warning from '@common/src/store/baoan_work_warning';
+
+// 位置监控
+import position from '@common/src/store/position';
+
+// 首页企业统计
+import home_company from '@common/src/store/home_company';
+// 首页保安员统计
+import home_security from '@common/src/store/home_security';
+// 模板消息
+import sendTemplate from '@common/src/store/sendTemplate';
+
+// 角色
+import systen_role from '@common/src/store/system_role';
+
+// 协会端账号
+import society_account from '@common/src/store/society_account';
+
+// 考试端账号
+import exam_account from '@common/src/store/exam_account';
+
+// 文件导入-保安员报名信息导入,考试成绩导入
+import import_file from '@common/src/store/import_file';
+// 服务端对服务端
+import upload from '@common/src/store/upload';
+
+// 考试部分
+// 考试设置
+import exam_config from '@common/src/store/exam/config';
+// 考试题型
+import exam_question_type from '@common/src/store/exam/question_type';
+// 考试题库
+import exam_question from '@common/src/store/exam/question';
+import exam_answer from '@common/src/store/exam/answer';
+import exam from '@common/src/store/exam/exam';
+
+Vue.use(Vuex);
+
+export default new Vuex.Store({
+  state: { ...ustate },
+  mutations: { ...umutations },
+  actions: {},
+  modules: {
+    login,
+    security_guard_base,
+    security_guard_others,
+    security_guard_collect,
+    baoan_statis,
+    digital_files,
+    police_department,
+    security_guard_qualifications,
+    examination_room,
+    examination_examinee,
+    certificates_base,
+    certificates_grant,
+    certificates_print_log,
+    certificates_print_again,
+    daily_inspection,
+    enterprise_reward,
+    complaints_info,
+    assistance_investigation,
+    assistance_investigation_reply,
+    system_account,
+    company_base,
+    company_applicant,
+    company_shareholders,
+    company_managers,
+    company_information,
+    company_teachers,
+    company_person_charge,
+    company_service_area,
+    company_legal_person,
+    company_service_object,
+    company_examine,
+    company_accept,
+    company_equipment,
+    company_car,
+    company_gun,
+    company_licence,
+    company_licence_grant,
+    company_baoan_work,
+    company_keep,
+    company_keep_grant,
+    company_legal_change,
+    baoan_news,
+    baoan_work_warning,
+    position,
+    home_company,
+    home_security,
+    sendTemplate,
+    systen_role,
+    society_account,
+    exam_account,
+    import_file,
+    reserve_collect,
+    reserve_record,
+    upload,
+    exam_config,
+    exam_question_type,
+    exam_question,
+    exam_answer,
+    exam,
+  },
+  getters: {
+    tokenInfo(state) {
+      const token = state.token;
+      let result;
+      if (token) {
+        const data = jwt.decode(token);
+        result = data;
+      }
+      return result;
+    },
+  },
+});

+ 9 - 0
src/util/answer-type.js

@@ -0,0 +1,9 @@
+export default [
+  { label: '单选', value: 'radio' },
+  { label: '多选', value: 'checkbox' },
+  {
+    label: '判断',
+    value: 'judge',
+    selects: [{ title: '正确' }, { title: '错误' }],
+  },
+];

+ 140 - 0
src/util/axios-wrapper.js

@@ -0,0 +1,140 @@
+/* eslint-disable no-console */
+/* eslint-disable no-param-reassign */
+
+import _ from 'lodash';
+import Axios from 'axios';
+import { Util, Error } from 'naf-core';
+// import { Indicator } from 'mint-ui';
+import util from './user-util';
+
+const { trimData, isNullOrUndefined } = Util;
+const { ErrorCode } = Error;
+
+let currentRequests = 0;
+
+export default class AxiosWrapper {
+  constructor({ baseUrl = '', unwrap = true } = {}) {
+    this.baseUrl = baseUrl;
+    this.unwrap = unwrap;
+  }
+
+  // 替换uri中的参数变量
+  static merge(uri, query = {}) {
+    if (!uri.includes(':')) {
+      return uri;
+    }
+    const keys = [];
+    const regexp = /\/:([a-z0-9_]+)/gi;
+    let res;
+    // eslint-disable-next-line no-cond-assign
+    while ((res = regexp.exec(uri)) != null) {
+      keys.push(res[1]);
+    }
+    keys.forEach((key) => {
+      console.log(query[key]);
+      if (!isNullOrUndefined(query[key])) {
+        uri = uri.replace(`:${key}`, query[key]);
+      }
+    });
+    return uri;
+  }
+
+  //检查query范围参数
+  static checkRangeQuery(query) {
+    for (const key in query) {
+      if (query[key] === '') query[key] = null;
+    }
+    for (const key in query) {
+      if (_.isObject(query[key])) {
+        // 处理,将{start,end}转换成key@start/end
+        if (_.get(query[key], 'start') && _.get(query[key], 'end')) {
+          query[`${key}@start`] = _.get(query[key], 'start');
+          query[`${key}@end`] = _.get(query[key], 'end');
+          delete query[key];
+        }
+      }
+    }
+
+    return query;
+  }
+
+  $get(uri, query, options) {
+    return this.$request(uri, null, query, options);
+  }
+
+  $post(uri, data = {}, query, options) {
+    return this.$request(uri, data, query, options);
+  }
+  $delete(uri, data = {}, router, query, options = {}) {
+    options = { ...options, method: 'delete' };
+    return this.$request(uri, data, query, options, router);
+  }
+  async $request(uri, data, query, options) {
+    query = AxiosWrapper.checkRangeQuery(query);
+    // TODO: 合并query和options
+    if (_.isObject(query) && _.isObject(options)) {
+      options = { ...options, params: query, method: 'get' };
+    } else if (_.isObject(query) && !query.params) {
+      options = { params: query };
+    } else if (_.isObject(query) && query.params) {
+      options = query;
+    }
+    // 处理范围参数
+    if (!options) options = {};
+    if (options.params) options.params = trimData(options.params);
+    const url = AxiosWrapper.merge(uri, options.params);
+    currentRequests += 1;
+    // Indicator.open({
+    //   spinnerType: 'fading-circle',
+    // });
+
+    try {
+      const axios = Axios.create({
+        baseURL: this.baseUrl,
+      });
+      const token = localStorage.getItem('token');
+      if (token) axios.defaults.headers.common.Authorization = token;
+      let res = await axios.request({
+        method: isNullOrUndefined(data) ? 'get' : 'post',
+        url,
+        data,
+        responseType: 'json',
+        ...options,
+      });
+      res = res.data;
+      const { errcode, errmsg, details } = res;
+      if (errcode) {
+        console.warn(`[${uri}] fail: ${errcode}-${errmsg} ${details}`);
+        return res;
+      }
+      // unwrap data
+      if (this.unwrap) {
+        res = _.omit(res, ['errmsg', 'details']);
+        const keys = Object.keys(res);
+        if (keys.length === 1 && keys.includes('data')) {
+          res = res.data;
+        }
+      }
+      return res;
+    } catch (err) {
+      let errmsg = '接口请求失败,请稍后重试';
+      if (err.response) {
+        const { status } = err.response;
+        if (status === 401) errmsg = '用户认证失败,请重新登录';
+        if (status === 403) errmsg = '当前用户不允许执行该操作';
+      }
+      console.error(
+        `[AxiosWrapper] 接口请求失败: ${err.config && err.config.url} - 
+        ${err.message}`
+      );
+      return { errcode: ErrorCode.SERVICE_FAULT, errmsg, details: err.message };
+    } finally {
+      /* eslint-disable */
+      currentRequests -= 1;
+      if (currentRequests <= 0) {
+        currentRequests = 0;
+        // Indicator.close();
+      }
+    }
+  }
+}

+ 10 - 0
src/util/filters.js

@@ -0,0 +1,10 @@
+import _ from 'lodash';
+
+const filters = {
+  getName(object) {
+    const { data, searchItem } = object;
+    return _.get(data, searchItem) === undefined ? '' : _.get(data, searchItem);
+  },
+};
+
+export default filters;

+ 50 - 0
src/util/methods-util.js

@@ -0,0 +1,50 @@
+import { Util } from 'naf-core';
+
+const { isNullOrUndefined } = Util;
+
+export default {
+  //判断信息是否过期
+  isDateOff(dataDate) {
+    const now = new Date(new Date().getTime() - 24 * 60 * 60 * 1000);
+    dataDate = new Date(dataDate);
+    return now.getTime() <= dataDate.getTime();
+  },
+  //判断企业是否可以执行此动作/显示
+  checkCorp(data) {
+    const { role, unit, selfUnit, status, displayType, userid } = data;
+    if (!isNullOrUndefined(selfUnit) && !isNullOrUndefined(status)) {
+      return role === 'corp' && selfUnit === unit && status === '0';
+    } else if (!isNullOrUndefined(displayType)) {
+      if (role === 'corp') {
+        return role === displayType;
+      } else {
+        return role === displayType && !isNullOrUndefined(userid);
+      }
+    }
+  },
+  //获取url的参数params
+  getParams() {
+    let str = location.href;
+    let num = str.indexOf('?');
+    const param = {};
+    str = str.substr(num + 1);
+    let num2 = str.indexOf('#');
+    let str2 = '';
+    if (num2 > 0) {
+      str2 = str.substr(0, num2);
+    } else {
+      num2 = str.indexOf('/');
+      str2 = str.substr(0, num2);
+    }
+    const arr = str2.split('&');
+    for (let i = 0; i < arr.length; i++) {
+      num = arr[i].indexOf('=');
+      if (num > 0) {
+        const name = arr[i].substring(0, num);
+        const value = arr[i].substr(num + 1);
+        param[name] = decodeURI(value);
+      }
+    }
+    return param;
+  },
+};

+ 135 - 0
src/util/print.js

@@ -0,0 +1,135 @@
+// 打印类属性、方法定义
+/* eslint-disable */
+const Print = function (dom, options) {
+  if (!(this instanceof Print)) return new Print(dom, options);
+
+  this.options = this.extend({
+    'noPrint': '.no-print'
+  }, options);
+
+  if ((typeof dom) === "string") {
+    this.dom = document.querySelector(dom);
+  } else {
+    this.isDOM(dom)
+    this.dom = this.isDOM(dom) ? dom : dom.$el;
+  }
+
+  this.init();
+};
+Print.prototype = {
+  init: function () {
+    var content = this.getStyle() + this.getHtml();
+    this.writeIframe(content);
+  },
+  extend: function (obj, obj2) {
+    for (var k in obj2) {
+      obj[k] = obj2[k];
+    }
+    return obj;
+  },
+
+  getStyle: function () {
+    var str = "",
+      styles = document.querySelectorAll('style,link');
+    for (var i = 0; i < styles.length; i++) {
+      str += styles[i].outerHTML;
+    }
+    str += "<style>" + (this.options.noPrint ? this.options.noPrint : '.no-print') + "{display:none;}</style>";
+
+    return str;
+  },
+
+  getHtml: function () {
+    var inputs = document.querySelectorAll('input');
+    var textareas = document.querySelectorAll('textarea');
+    var selects = document.querySelectorAll('select');
+
+    for (var k = 0; k < inputs.length; k++) {
+      if (inputs[k].type == "checkbox" || inputs[k].type == "radio") {
+        if (inputs[k].checked == true) {
+          inputs[k].setAttribute('checked', "checked")
+        } else {
+          inputs[k].removeAttribute('checked')
+        }
+      } else if (inputs[k].type == "text") {
+        inputs[k].setAttribute('value', inputs[k].value)
+      } else {
+        inputs[k].setAttribute('value', inputs[k].value)
+      }
+    }
+
+    for (var k2 = 0; k2 < textareas.length; k2++) {
+      if (textareas[k2].type == 'textarea') {
+        textareas[k2].innerHTML = textareas[k2].value
+      }
+    }
+
+    for (var k3 = 0; k3 < selects.length; k3++) {
+      if (selects[k3].type == 'select-one') {
+        var child = selects[k3].children;
+        for (var i in child) {
+          if (child[i].tagName == 'OPTION') {
+            if (child[i].selected == true) {
+              child[i].setAttribute('selected', "selected")
+            } else {
+              child[i].removeAttribute('selected')
+            }
+          }
+        }
+      }
+    }
+
+    return this.dom.outerHTML;
+  },
+
+  writeIframe: function (content) {
+    var w, doc, iframe = document.createElement('iframe'),
+      f = document.body.appendChild(iframe);
+    iframe.id = "myIframe";
+    //iframe.style = "position:absolute;width:0;height:0;top:-10px;left:-10px;";
+    iframe.setAttribute('style', 'position:absolute;width:0;height:0;top:-10px;left:-10px;');
+    w = f.contentWindow || f.contentDocument;
+    doc = f.contentDocument || f.contentWindow.document;
+    doc.open();
+    doc.write(content);
+    doc.close();
+    var _this = this
+    iframe.onload = function(){
+      _this.toPrint(w);
+      setTimeout(function () {
+        document.body.removeChild(iframe)
+      }, 100)
+    }
+  },
+
+  toPrint: function (frameWindow) {
+    try {
+      setTimeout(function () {
+        frameWindow.focus();
+        try {
+          if (!frameWindow.document.execCommand('print', false, null)) {
+            frameWindow.print();
+          }
+        } catch (e) {
+          frameWindow.print();
+        }
+        frameWindow.close();
+      }, 10);
+    } catch (err) {
+      console.log('err', err);
+    }
+  },
+  isDOM: (typeof HTMLElement === 'object') ?
+    function (obj) {
+      return obj instanceof HTMLElement;
+    } :
+    function (obj) {
+      return obj && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string';
+    }
+};
+const MyPlugin = {}
+MyPlugin.install = function (Vue, options) {
+  // 4. 添加实例方法
+  Vue.prototype.$print = Print
+}
+export default MyPlugin

+ 69 - 0
src/util/user-util.js

@@ -0,0 +1,69 @@
+/* eslint-disable no-console */
+export default {
+  get user() {
+    const val = sessionStorage.getItem('user');
+    try {
+      if (val) return JSON.parse(val);
+    } catch (err) {
+      console.error(err);
+    }
+    return null;
+  },
+  set user(userinfo) {
+    sessionStorage.setItem('user', JSON.stringify(userinfo));
+  },
+  get token() {
+    return sessionStorage.getItem('token');
+  },
+  set token(token) {
+    sessionStorage.setItem('token', token);
+  },
+  get openid() {
+    return sessionStorage.getItem('openid');
+  },
+  set openid(openid) {
+    sessionStorage.setItem('openid', openid);
+  },
+  get isGuest() {
+    return !this.user || this.user.role === 'guest';
+  },
+  save({ userinfo, token }) {
+    sessionStorage.setItem('user', JSON.stringify(userinfo));
+    sessionStorage.setItem('token', token);
+  },
+
+  get corpInfo() {
+    const val = sessionStorage.getItem('corpInfo');
+    if (val) return JSON.parse(val);
+    return null;
+  },
+  set corpInfo(corpInfo) {
+    sessionStorage.setItem('corpInfo', JSON.stringify(corpInfo));
+  },
+  saveCorpInfo(corpInfo) {
+    sessionStorage.setItem('corpInfo', JSON.stringify(corpInfo));
+  },
+
+  get unit() {
+    const val = sessionStorage.getItem('unit');
+    if (val) return JSON.parse(val);
+    return null;
+  },
+  set unit(unitList) {
+    sessionStorage.setItem('unit', JSON.stringify(unitList));
+  },
+  saveUnit(unitList) {
+    sessionStorage.setItem('unit', JSON.stringify(unitList));
+  },
+  get userInfo() {
+    const val = sessionStorage.getItem('userInfo');
+    if (val) return JSON.parse(val);
+    return null;
+  },
+  set userInfo(userInfo) {
+    sessionStorage.setItem('userInfo', JSON.stringify(userInfo));
+  },
+  saveUserInfo(userInfo) {
+    sessionStorage.setItem('userInfo', JSON.stringify(userInfo));
+  },
+};

+ 148 - 0
src/views/baoanexam/exam/answer/index.vue

@@ -0,0 +1,148 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="one">
+          <el-col :span="24" class="one_1"> 考试结果查询 </el-col>
+          <el-col :span="24" class="one_2">
+            <el-form ref="searchFrom" :model="searchFrom" label-width="100px">
+              <el-col :span="24" class="one_2txt">
+                <el-col :span="8">
+                  <el-form-item label="身份证号">
+                    <el-input v-model="searchFrom.user_card"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="姓名">
+                    <el-input v-model="searchFrom.user_name"></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24" class="one_2txt">
+                <el-col :span="24" class="btn">
+                  <el-button type="primary" size="mini" @click="search()">查询</el-button>
+                </el-col>
+              </el-col>
+            </el-form>
+          </el-col>
+        </el-col>
+        <el-col :span="24" class="two">
+          <data-table @query="search" :usePage="false" :fields="fields" :opera="opera" :data="list" @view="toView"> </data-table>
+        </el-col>
+      </el-col>
+    </el-row>
+    <el-dialog class="dialog" title="查看答案" center :visible.sync="show" width="50%" :before-close="close" :close-on-click-modal="false">
+      <view-info :data="info"></view-info>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import viewInfo from './view.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: exam_answer } = createNamespacedHelpers('exam_answer');
+const { mapActions: security_guard_base } = createNamespacedHelpers('security_guard_base');
+const { mapActions: examination_room } = createNamespacedHelpers('examination_room');
+const _ = require('lodash');
+export default {
+  name: 'index',
+  props: {},
+  components: { viewInfo },
+  data: function () {
+    return {
+      searchFrom: {},
+      securityGuardList: [],
+      loading: false,
+      fields: [
+        { label: '保安员姓名', model: 'user_name' },
+        { label: '保安员身份证号', model: 'user_card' },
+        { label: '考场', model: 'exam_place' },
+        { label: '分数', model: 'score' },
+      ],
+      list: [],
+      opera: [{ label: '查看', method: 'view', type: 'primary' }],
+      show: false,
+      info: {},
+    };
+  },
+  created() {
+    this.search({ user_id: '61e79380b497a26c4ce1989b' });
+  },
+  methods: {
+    ...exam_answer(['query']),
+    ...security_guard_base({ getSecurityGuard: 'query' }),
+    ...examination_room({ getExamRoom: 'query' }),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const condition = _.cloneDeep(this.searchFrom);
+      let res = await this.query({ ...condition, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+      }
+    },
+
+    async toView({ data }) {
+      this.info = _.cloneDeep(data);
+      this.show = true;
+    },
+    // 远程搜索
+    async seacherSecurityGuard(card) {
+      this.loading = true;
+      if (card === '') return;
+      const res = await this.getSecurityGuard({ card });
+      if (this.$checkRes(res)) {
+        let data = _.cloneDeep(res.data);
+        data = data.map((i) => {
+          i.name = `${i.card}(${i.name})`;
+          return i;
+        });
+        this.$set(this, 'securityGuardList', data);
+      }
+      this.loading = false;
+    },
+    close() {
+      this.show = false;
+      this.info = {};
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .one {
+    .one_1 {
+      height: 40px;
+      line-height: 40px;
+      border-bottom: 1px solid #ccc;
+      margin: 0 0 10px 0;
+      font-size: 18px;
+      font-weight: bold;
+    }
+    .one_2 {
+      .one_2txt {
+        border: 1px solid #ccc;
+        /deep/.el-form-item {
+          margin: 0;
+        }
+        /deep/.el-date-editor.el-input,
+        .el-date-editor.el-input__inner {
+          width: 100%;
+        }
+        /deep/.el-select {
+          width: 100%;
+        }
+        .btn {
+          text-align: right;
+          padding: 5px 5px;
+        }
+      }
+    }
+  }
+}
+.dialog {
+  /deep/.el-dialog__body {
+    height: 450px;
+    overflow-y: auto;
+  }
+}
+</style>

+ 56 - 0
src/views/baoanexam/exam/answer/view.vue

@@ -0,0 +1,56 @@
+<template>
+  <div id="view">
+    <el-row>
+      <el-col :span="24" class="main">
+        <!-- <el-col :span="24" class="one"> 标题 </el-col> -->
+        <el-col :span="24" class="one"> 总分:{{ data.score }} </el-col>
+        <el-col :span="24" class="two">
+          <el-col :span="24" class="two_1">
+            <template v-for="(q, qi) in data.questions">
+              <el-row :key="`qt${qi}`" class="list">
+                <el-col :span="24" class="index">{{ qi + 1 }}.{{ q.title }}</el-col>
+                <template v-if="getQuestType(q) === 'radio'">
+                  <el-radio-group v-model="q.answer">
+                    <el-col :span="24" v-for="(s, si) in q.selects" :key="`selects${qi}-${si}`">
+                      <el-radio :disabled="true" :key="`selects${qi}-${si}`" :label="s.title"> {{ s.title }}{{ s.isRight ? '(正确答案)' : '' }} </el-radio>
+                    </el-col>
+                  </el-radio-group>
+                </template>
+                <template v-if="getQuestType(q) === 'checkbox'">
+                  <el-checkbox-group v-model="q.answer">
+                    <el-col :span="24" v-for="(s, si) in q.selects" :key="`checkbox${qi}-${si}`">
+                      <el-checkbox :disabled="true" :key="`checkbox${qi}-${si}`" :label="s.title">
+                        {{ s.title }}{{ s.isRight ? '(正确答案)' : '' }}
+                      </el-checkbox>
+                    </el-col>
+                  </el-checkbox-group>
+                </template>
+              </el-row>
+            </template>
+          </el-col>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'viewInfo',
+  props: {
+    data: { type: Object, required: true },
+  },
+  components: {},
+  data: function () {
+    return {};
+  },
+  created() {},
+  methods: {
+    getQuestType(quest) {
+      return _.get(quest.type, 'type');
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 101 - 0
src/views/baoanexam/exam/config/index.vue

@@ -0,0 +1,101 @@
+<template>
+  <div id="index">
+    <el-form label-position="left" label-width="120px">
+      <el-form-item label="是否开放考试">
+        <el-radio-group v-model="form.can_exam">
+          <el-radio :label="true">开放</el-radio>
+          <el-radio :label="false">关闭</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="总分数">
+        <el-input-number :min="0" v-model="form.score" :controls="false"></el-input-number>
+      </el-form-item>
+      <el-form-item label="测试题型比例">
+        <proportion v-model="form.proportion" :questionTypeList="questionTypeList"></proportion>
+      </el-form-item>
+      <el-form-item label="考试题型比例">
+        <el-row>
+          <el-col :span="24" style="margin: 10px 0px">
+            <el-alert title="考试专用的类型,题目占比会小于5%" show-icon type="warning" effect="dark" :closable="false"> </el-alert>
+          </el-col>
+        </el-row>
+        <proportion v-model="form.exam_proportion" :questionTypeList="questionTypeList" :is_exam="true"></proportion>
+      </el-form-item>
+      <el-form-item>
+        <el-row type="flex" justify="space-around">
+          <el-col :span="4">
+            <el-button type="primary" @click="toSave">保存</el-button>
+          </el-col>
+        </el-row>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import proportion from './proportion.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions } = createNamespacedHelpers('exam_config');
+const { mapActions: exam_question_type } = createNamespacedHelpers('exam_question_type');
+export default {
+  name: 'index',
+  props: {},
+  components: { proportion },
+  data: function () {
+    return {
+      form: {},
+      questionTypeList: [],
+    };
+  },
+  created() {
+    this.search();
+    this.searchQuestionType();
+  },
+  methods: {
+    ...exam_question_type(['query']),
+    ...mapActions(['fetch', 'update']),
+    async search() {
+      const res = await this.fetch();
+      if (this.$checkRes(res)) {
+        this.$set(this, `form`, res.data);
+      }
+    },
+    async searchQuestionType() {
+      const res = await this.query();
+      if (this.$checkRes(res)) {
+        const list = res.data.map((i) => ({ ...i, number: 0 }));
+        this.$set(this, `questionTypeList`, list);
+      }
+    },
+    async toSave() {
+      let dup = _.cloneDeep(this.form);
+      // 判断,将最小占比转换为分数,再看加和是不是小于等于总分数,如果大于总分数,那就GG了:按照最小比例出题也会超分,这么分没有意义
+      const pMinScore = dup.proportion.reduce((p, n) => p + _.multiply(_.get(n, 'number_min', 0), _.get(n, 'score', 0)), 0);
+      if (pMinScore > dup.score) {
+        this.$message.error('练习题型分配:按最小比例进行分配的总分数 超出规定的 总分数,请重新分配!');
+        return;
+      }
+      const epMinScore = dup.exam_proportion.reduce((p, n) => p + _.multiply(_.get(n, 'number_min', 0), _.get(n, 'score', 0)), 0);
+      if (epMinScore > dup.score) {
+        this.$message.error('考试题型分配:按最小比例进行分配的总分数 超出规定的 总分数,请重新分配!');
+        return;
+      }
+      const res = await this.update(dup);
+      if (this.$checkRes(res, '保存成功', res.errmsg || '保存失败')) {
+      }
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.rows {
+  font-size: 18px;
+  .el-col {
+    padding-bottom: 10px;
+  }
+}
+.inputRange {
+  margin-left: 10px;
+}
+</style>

+ 149 - 0
src/views/baoanexam/exam/config/proportion.vue

@@ -0,0 +1,149 @@
+<template>
+  <div id="proportion">
+    <el-row class="rows">
+      <el-col :span="24">
+        <el-button type="primary" @click="dialog = true">添加</el-button>
+      </el-col>
+      <el-col :span="24">
+        <el-table :data="proportion" border :show-summary="false" sum-text="分数总计" :summary-method="summaryMethod">
+          <template v-for="(f, index) in fields">
+            <el-table-column align="center" :key="`fields${index}`" :label="f.label" :prop="f.model">
+              <template v-slot="{ row }">
+                {{ f.format ? f.format(row[f.model]) : row[f.model] }}
+              </template>
+            </el-table-column>
+          </template>
+          <el-table-column align="center" label="题目占比">
+            <el-table-column align="center" label="最小值(%)">
+              <template v-slot="{ row }">
+                <el-input-number :disabled="isExam(row)" v-model="row.number_min" :min="0" :max="100"></el-input-number>
+              </template>
+            </el-table-column>
+            <el-table-column align="center" label="最大值(%)">
+              <template v-slot="{ row }">
+                <el-input-number :disabled="isExam(row)" v-model="row.number_max" :min="row.number_min" :max="100"></el-input-number>
+              </template>
+            </el-table-column>
+          </el-table-column>
+          <el-table-column align="center" label="考试专用类型" v-if="is_exam">
+            <template v-slot="{ row }">
+              <el-checkbox v-model="row.is_exam"></el-checkbox>
+            </template>
+          </el-table-column>
+          <el-table-column align="center" label="操作">
+            <template v-slot="{ $index }">
+              <el-link type="danger" @click="deleteType($index)">删除</el-link>
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-col>
+    </el-row>
+    <el-dialog title="添加题型" :visible.sync="dialog" width="70%">
+      <el-table :data="questionTypeList" border>
+        <template v-for="(f, index) in fields">
+          <el-table-column align="center" :key="`fields${index}`" :label="f.label" :prop="f.model">
+            <template v-slot="{ row }">
+              {{ f.format ? f.format(row[f.model]) : row[f.model] }}
+            </template>
+          </el-table-column>
+        </template>
+        <el-table-column align="center" label="操作">
+          <template v-slot="{ row }">
+            <template v-if="isSelect(row)">
+              <el-link type="info" :underline="false">已添加</el-link>
+            </template>
+            <template v-else>
+              <el-link type="primary" @click="addType(row)">添加</el-link>
+            </template>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import answerType from '@/util/answer-type';
+export default {
+  name: 'proportion',
+  props: {
+    proportion: { type: Array, default: () => [] },
+    questionTypeList: { type: Array, default: () => [] },
+    is_exam: { type: Boolean, default: false },
+  },
+  model: {
+    prop: 'proportion',
+    event: 'change',
+  },
+  components: {},
+  data: function () {
+    return {
+      dialog: false,
+      // 题型列表
+      typeList: answerType,
+      // 列表
+      fields: [
+        {
+          label: '题型别名',
+          model: '_id',
+          // 实时更新的名称,数据里的内容并不重要,重要的是拿id换来的
+          format: (i) => {
+            let r = this.questionTypeList.find((f) => f._id === i);
+            if (r) return r.title;
+            r = this.proportion.filter((f) => f._id === i);
+            if (r) return r.title;
+          },
+        },
+        {
+          label: '题型',
+          model: 'type',
+          format: (i) => {
+            const r = this.typeList.find((f) => f.value === i);
+            if (r) return r.label;
+            return i;
+          },
+        },
+        { label: '每题分数', model: 'score' },
+      ],
+    };
+  },
+  created() {},
+  methods: {
+    deleteType(index) {
+      this.proportion.splice(index, 1);
+    },
+    //判断是否已经添加到比例分配列表中
+    isSelect(row) {
+      const r = this.proportion.find((f) => f._id === row._id);
+      return r;
+    },
+    addType(row) {
+      this.proportion.push(_.cloneDeep(row));
+    },
+    // 自定义合并行
+    summaryMethod({ data }) {
+      const score = data.reduce((p, n) => p + parseInt(n.score) * parseInt(n.number), 0);
+      const number = data.reduce((p, n) => p + parseInt(n.number), 0);
+      return ['分数合计', score, '题目总数', number];
+    },
+    isExam(data) {
+      // 不是考试模式,可以编辑
+      if (!this.is_exam) return false;
+      // 如果是考试专用类型,那就不需要写最小-最大的范围值了
+      const { is_exam } = data;
+      return is_exam;
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.rows {
+  .el-col {
+    padding-bottom: 10px;
+  }
+  .el-col:first-child {
+    text-align: right;
+  }
+}
+</style>

+ 101 - 0
src/views/baoanexam/exam/exam/index.vue

@@ -0,0 +1,101 @@
+<template>
+  <div id="index">
+    <template v-for="(q, qi) in questions">
+      <el-row :key="`qt${qi}`">
+        <el-col :span="24">{{ qi + 1 }}.{{ q.title }}</el-col>
+        <template v-if="getQuestType(q) === 'radio'">
+          <el-radio-group v-model="q.answer">
+            <el-col :span="24" v-for="(s, si) in q.selects" :key="`selects${qi}-${si}`">
+              <el-radio :key="`selects${qi}-${si}`" :label="s.title" :value="s.title"> </el-radio>
+            </el-col>
+          </el-radio-group>
+        </template>
+        <template v-if="getQuestType(q) === 'checkbox'">
+          <el-checkbox-group v-model="q.answer">
+            <el-col :span="24" v-for="(s, si) in q.selects" :key="`checkbox${qi}-${si}`">
+              <el-checkbox :key="`checkbox${qi}-${si}`" :label="s.title"> {{ s.title }}</el-checkbox>
+            </el-col>
+          </el-checkbox-group>
+        </template>
+      </el-row>
+    </template>
+    <el-row>
+      <el-col :span="24">
+        <el-button @click="computedScore" type="primary">提交</el-button>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: exam_answer } = createNamespacedHelpers('exam_answer');
+const _ = require('lodash');
+export default {
+  name: 'index',
+  props: {},
+  components: {},
+  data: function () {
+    return {
+      questions: [],
+    };
+  },
+  created() {
+    this.getExamTest();
+  },
+  methods: {
+    ...exam_answer(['getExam', 'update']),
+    async getExamTest() {
+      const obj = { exam_place_id: '61e79380b497a26c4ce1989a', user_id: '61e79380b497a26c4ce1989b', exam: true };
+      let res = await this.getExam(obj);
+      if (this.$checkRes(res)) {
+        this.$set(this, `questions`, _.get(res.data, 'questions'));
+      }
+    },
+    getQuestType(quest) {
+      return _.get(quest.type, 'type');
+    },
+    async computedScore() {
+      let dup = _.cloneDeep(this.questions);
+      let score = 0;
+      let rights = [];
+      for (const q of dup) {
+        const { selects, answer } = q;
+        const questType = this.getQuestType(q);
+        const questScore = _.get(q.type, 'score');
+        const right = selects.filter((f) => f.isRight);
+        let answerRight = false;
+        if (questType === 'radio') {
+          answerRight = right.find((f) => f.title === answer);
+        } else if (questType === 'checkbox') {
+          if (answer.length <= 0) continue;
+          answerRight = answer.every((e) => right.find((f) => f.title === e));
+        }
+        if (answerRight) {
+          rights.push(q);
+          score += questScore;
+        }
+      }
+      dup.score = score;
+      // 考试的情况下,返回的数据会带有数据id,需要走保存
+      if (dup._id) {
+        const res = await this.model.update(dup);
+        if (this.$checkRes(res)) {
+          this.$confirm(`交卷成功:您本次考试的分数为:${score}分`, '交卷提示', {
+            confirmButtonText: '确定',
+            showCancelButton: false,
+            type: 'success',
+          });
+        }
+      } else
+        this.$confirm(`交卷成功:您本次练习的分数为:${score}分`, '交卷提示', {
+          confirmButtonText: '确定',
+          showCancelButton: false,
+          type: 'success',
+        });
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 305 - 0
src/views/baoanexam/exam/question/index.vue

@@ -0,0 +1,305 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="one">
+          <el-col :span="24" class="one_1"> 考试题库 </el-col>
+          <el-col :span="24" class="one_2">
+            <el-form ref="searchFrom" :model="searchFrom" label-width="100px">
+              <el-col :span="24" class="one_2txt">
+                <el-col :span="8">
+                  <el-form-item label="题目">
+                    <el-input v-model="searchFrom.title"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="题型">
+                    <el-select v-model="searchFrom.type" placeholder="">
+                      <el-option v-for="(i, index) in questionType" :key="`qt${index}`" :label="i.title" :value="i._id"></el-option>
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24" class="one_2txt">
+                <el-col :span="24" class="btn">
+                  <el-button type="primary" size="mini" @click="search()">导入</el-button>
+                  <el-button type="primary" size="mini" @click="search()">查询</el-button>
+                  <el-button type="primary" size="mini" @click="toAdd()">添加</el-button>
+                  <!-- <el-button type="primary" size="mini" @click="toExport">导入文件样本下载</el-button> -->
+                </el-col>
+              </el-col>
+            </el-form>
+          </el-col>
+        </el-col>
+        <el-col :span="24" class="two">
+          <data-table @query="search" pageMode="skip" :fields="fields" :opera="opera" :data="list" :total="total" @edit="toEdit" @delete="toDelete">
+          </data-table>
+        </el-col>
+      </el-col>
+    </el-row>
+    <el-dialog class="showinfo" title="题目信息" :visible.sync="showinfo" width="70%" :before-close="close" :close-on-click-modal="false">
+      <el-form label-position="left" label-width="120px" style="padding: 5% 20%">
+        <el-form-item label="题目" prop="title">
+          <el-input v-model="info.title" placeholder="请输入题目"></el-input>
+        </el-form-item>
+        <el-form-item label="题型" prop="type">
+          <el-select v-model="info.type" placeholder="请选择题型" @change="typeSelect">
+            <el-option v-for="(i, index) in questionType" :key="`type${index}`" :value="i._id" :label="i.title"></el-option>
+          </el-select>
+          <span v-if="getSelectTypeZh()" style="padding: 0 10px; color: red">({{ getSelectTypeZh() }})</span>
+        </el-form-item>
+        <el-form-item label="答案列表">
+          <el-row>
+            <el-col :span="24" style="text-align: right; padding: 10px 0">
+              <el-button type="primary" @click="addSelects" v-if="getSelectType() !== 'judge'">添加</el-button>
+            </el-col>
+            <el-col :span="24">
+              <el-checkbox-group v-model="info.selectsList">
+                <el-table :data="info.selects">
+                  <el-table-column align="center" label="答案内容">
+                    <template v-slot="{ row }">
+                      <el-input v-model="row.title" placeholder="请填写答案内容"></el-input>
+                    </template>
+                  </el-table-column>
+                  <el-table-column align="center" label="正确选项">
+                    <template v-slot="{ $index }">
+                      <el-checkbox :label="$index" :disabled="selectDisabled($index)">是</el-checkbox>
+                    </template>
+                  </el-table-column>
+                  <el-table-column align="center" label="操作">
+                    <template v-slot="{ $index }">
+                      <el-button v-if="getSelectType() !== 'judge'" type="danger" @click="deleteSelect($index)">删除</el-button>
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </el-checkbox-group>
+            </el-col>
+          </el-row>
+        </el-form-item>
+        <el-form-item>
+          <el-row type="flex" justify="space-around">
+            <el-col :span="6">
+              <el-button type="primary" @click="toSave">保存</el-button>
+            </el-col>
+            <el-col :span="6">
+              <el-button @click="close">返回</el-button>
+            </el-col>
+          </el-row>
+        </el-form-item>
+      </el-form>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import answerType from '@/util/answer-type';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: exam_question_type } = createNamespacedHelpers('exam_question_type');
+const { mapActions } = createNamespacedHelpers('exam_question');
+const _ = require('lodash');
+export default {
+  name: 'index',
+  props: {},
+  components: {},
+  data: function () {
+    return {
+      // 查询
+      searchFrom: {},
+      // 题型列表
+      typeList: answerType,
+      questionType: [],
+      // 列表
+      fields: [
+        { label: '题目', model: 'title' },
+        {
+          label: '题型',
+          model: 'type',
+          format: (i) => {
+            const r = this.questionType.find((f) => f._id === i);
+            if (r) return r.title;
+            return i;
+          },
+        },
+      ],
+      opera: [
+        { label: '修改', method: 'edit', type: 'primary' },
+        { label: '删除', method: 'delete', type: 'danger', confirm: true },
+      ],
+      list: [],
+      total: 0,
+      showinfo: false,
+      info: { selects: [], selectsList: [] },
+    };
+  },
+  created() {
+    this.search();
+    this.toGetQuestionType();
+  },
+  methods: {
+    ...exam_question_type({ getQuestionType: 'query' }),
+    ...mapActions(['query', 'create', 'update', 'delete']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const condition = _.cloneDeep(this.searchFrom);
+      let res = await this.query({ skip, limit, ...condition, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    async toGetQuestionType() {
+      const res = await this.getQuestionType();
+      if (this.$checkRes(res)) {
+        this.$set(this, `questionType`, res.data);
+      }
+    },
+    getSelectType(selectType = this.info.type) {
+      const type = this.questionType.find((f) => f._id === selectType);
+      if (type) return type.type;
+    },
+    getSelectTypeZh(selectType = this.info.type) {
+      const type = this.questionType.find((f) => f._id === selectType);
+      if (type) {
+        const r = this.typeList.find((f) => f.value === type.type);
+        if (r) return r.label;
+      }
+    },
+    // 添加
+    toAdd() {
+      this.showinfo = true;
+    },
+    // 修改
+    toEdit({ data }) {
+      const dup = _.cloneDeep(data);
+      const type = this.getSelectType(dup.type);
+      if (type === 'radio' || type === 'judge') {
+        dup.selectsList = [];
+        const isRight = dup.selects.findIndex((f) => f.isRight);
+        if (isRight >= 0) dup.selectsList.push(isRight);
+      } else if (type === 'checkbox') {
+        dup.selectsList = [];
+        for (let i = 0; i < dup.selects.length; i++) {
+          const e = dup.selects[i];
+          if (e.isRight) dup.selectsList.push(i);
+        }
+      }
+      this.$set(this, `info`, dup);
+      this.showinfo = true;
+    },
+    async toDelete({ data }) {
+      const res = await this.delete(data._id || data.id);
+      if (this.$checkRes(res, '操作成功', '操作失败' || res.errmsg)) {
+        this.search();
+      }
+    },
+    async toSave() {
+      let dup = _.cloneDeep(this.info);
+      let res;
+      if (this.getSelectType(dup.type) === 'radio' || this.getSelectType(dup.type) === 'judge') {
+        dup.selects = dup.selects.map((i) => _.omit(i, ['isRight']));
+        for (const i of dup.selectsList) {
+          dup.selects[i].isRight = true;
+        }
+        delete dup.selectsList;
+      } else if (this.getSelectType(dup.type) === 'checkbox') {
+        dup.selects = dup.selects.map((i) => _.omit(i, ['isRight']));
+        for (const i of dup.selectsList) {
+          dup.selects[i].isRight = true;
+        }
+        delete dup.selectsList;
+      }
+      if (dup._id) res = await this.update(dup);
+      else res = await this.create(dup);
+      if (this.$checkRes(res, '保存成功', res.errmsg || '保存失败')) {
+        this.search();
+        this.close();
+      }
+    },
+    // 选项列表添加
+    addSelects() {
+      if (!_.isArray(this.info.selects)) {
+        this.info.selects = [];
+      }
+      this.info.selects.push({});
+    },
+    // 选项列表删除
+    deleteSelect(index) {
+      if (_.isArray(this.info.selects)) {
+        this.info.selects.splice(index, 1);
+      }
+    },
+    // 选项列表是否禁用
+    selectDisabled(index) {
+      if (this.getSelectType() === 'radio' || this.getSelectType() === 'judge') {
+        if (this.info.selectsList.length > 0) {
+          if (this.info.selectsList.includes(index)) return false;
+          return true;
+        }
+        return false;
+      }
+      return false;
+    },
+    typeSelect(select) {
+      const r = this.questionType.find((f) => f._id === select);
+      if (_.get(r, 'type') === 'judge') {
+        const { selects } = this.typeList.find((f) => f.value === 'judge');
+        this.$set(this.info, 'selects', selects);
+      }
+    },
+
+    // 关闭详情
+    close() {
+      this.showinfo = false;
+      this.info = { selects: [], selectsList: [] };
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .one {
+    .one_1 {
+      height: 40px;
+      line-height: 40px;
+      border-bottom: 1px solid #ccc;
+      margin: 0 0 10px 0;
+      font-size: 18px;
+      font-weight: bold;
+    }
+    .one_2 {
+      .one_2txt {
+        border: 1px solid #ccc;
+        /deep/.el-form-item {
+          margin: 0;
+        }
+        /deep/.el-date-editor.el-input,
+        .el-date-editor.el-input__inner {
+          width: 100%;
+        }
+        /deep/.el-select {
+          width: 100%;
+        }
+        .btn {
+          text-align: right;
+          padding: 5px 5px;
+        }
+      }
+    }
+  }
+}
+.showinfo {
+  /deep/.el-dialog__body {
+    height: 650px;
+    overflow-y: auto;
+  }
+}
+/deep/.el-dialog__body {
+  padding: 10px;
+}
+</style>

+ 176 - 0
src/views/baoanexam/exam/questionType/index.vue

@@ -0,0 +1,176 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="one">
+          <el-col :span="24" class="one_1"> 考试题型 </el-col>
+          <el-col :span="24" class="one_2">
+            <el-form ref="searchFrom" :model="searchFrom" label-width="100px">
+              <el-col :span="24" class="one_2txt">
+                <el-col :span="24" class="btn">
+                  <el-button type="primary" size="mini" @click="toAdd()">添加</el-button>
+                  <!-- <el-button type="primary" size="mini" @click="toExport">导入文件样本下载</el-button> -->
+                </el-col>
+              </el-col>
+            </el-form>
+          </el-col>
+        </el-col>
+        <el-col :span="24" class="two">
+          <data-table @query="search" :fields="fields" :opera="opera" :data="list" :total="total" @edit="toEdit"> </data-table>
+        </el-col>
+      </el-col>
+    </el-row>
+    <el-dialog class="showinfo" title="题型信息" :visible.sync="showinfo" width="70%" :before-close="close" :close-on-click-modal="false">
+      <el-form label-position="left" label-width="120px" style="padding: 10% 20%">
+        <el-form-item label="题型别名" prop="title">
+          <el-input v-model="info.title" placeholder="请输入题型别名"></el-input>
+        </el-form-item>
+        <el-form-item label="题型" prop="type">
+          <el-select v-model="info.type" placeholder="请选择题型">
+            <el-option v-for="(i, index) in typeList" :key="`type${index}`" :value="i.value" :label="i.label"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="每题分数" prop="score">
+          <el-input-number :min="1" v-model="info.score"></el-input-number>
+        </el-form-item>
+        <el-form-item>
+          <el-row type="flex" justify="space-around">
+            <el-col :span="6">
+              <el-button type="primary" @click="toSave">保存</el-button>
+            </el-col>
+            <el-col :span="6">
+              <el-button @click="close">返回</el-button>
+            </el-col>
+          </el-row>
+        </el-form-item>
+      </el-form>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import answerType from '@/util/answer-type';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions } = createNamespacedHelpers('exam_question_type');
+const moment = require('moment');
+const _ = require('lodash');
+export default {
+  name: 'index',
+  props: {},
+  components: {},
+  data: function () {
+    return {
+      // 查询
+      searchFrom: {},
+      // 题型列表
+      typeList: answerType,
+      // 列表
+      fields: [
+        { label: '题型别名', model: 'title' },
+        {
+          label: '题型',
+          model: 'type',
+          format: (i) => {
+            const r = this.typeList.find((f) => f.value === i);
+            if (r) return r.label;
+            return i;
+          },
+        },
+        { label: '每题分数', model: 'score' },
+      ],
+      opera: [{ label: '修改', method: 'edit', type: 'primary' }],
+      list: [],
+      total: 0,
+      showinfo: false,
+      info: { score: 1 },
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...mapActions(['query', 'create', 'update', 'delete']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const condition = _.cloneDeep(this.searchFrom);
+      let res = await this.query({ skip, limit, ...condition, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    // 添加
+    toAdd() {
+      this.showinfo = true;
+    },
+    // 修改
+    toEdit({ data }) {
+      this.$set(this, `info`, data);
+      this.showinfo = true;
+    },
+    async toSave() {
+      let dup = _.cloneDeep(this.info);
+      let res;
+      if (dup._id) res = await this.update(dup);
+      else res = await this.create(dup);
+      if (this.$checkRes(res, '保存成功', res.errmsg || '保存失败')) {
+        this.search();
+        this.close();
+      }
+    },
+    // 关闭详情
+    close() {
+      this.showinfo = false;
+      this.info = { score: 1 };
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .one {
+    .one_1 {
+      height: 40px;
+      line-height: 40px;
+      border-bottom: 1px solid #ccc;
+      margin: 0 0 10px 0;
+      font-size: 18px;
+      font-weight: bold;
+    }
+    .one_2 {
+      .one_2txt {
+        border: 1px solid #ccc;
+        /deep/.el-form-item {
+          margin: 0;
+        }
+        /deep/.el-date-editor.el-input,
+        .el-date-editor.el-input__inner {
+          width: 100%;
+        }
+        /deep/.el-select {
+          width: 100%;
+        }
+        .btn {
+          text-align: right;
+          padding: 5px 5px;
+        }
+      }
+    }
+  }
+}
+.showinfo {
+  /deep/.el-dialog__body {
+    height: 650px;
+    overflow-y: auto;
+  }
+}
+/deep/.el-dialog__body {
+  padding: 10px;
+}
+</style>

+ 247 - 0
src/views/baoanexam/security/kcbp/add.vue

@@ -0,0 +1,247 @@
+<template>
+  <div id="add">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="one">
+          <el-button type="primary" size="mini" @click="back()">返回</el-button>
+        </el-col>
+        <el-col :span="24" class="two">
+          <el-form :model="form" :rules="rules" ref="form" label-width="130px">
+            <el-col :span="24">
+              <el-col :span="12">
+                <el-form-item label="考点编号" prop="testsite_num">
+                  <el-input v-model="form.testsite_num" disabled></el-input>
+                </el-form-item>
+              </el-col>
+            </el-col>
+            <el-col :span="24">
+              <el-col :span="12">
+                <el-form-item label="考试日期" prop="exam_date">
+                  <el-date-picker v-model="form.exam_date" placeholder="请选择" value-format="yyyy-MM-dd" format="yyyy-MM-dd" type="date" style="width: 100%">
+                  </el-date-picker>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="考试等级" prop="exam_grade">
+                  <el-select v-model="form.exam_grade" clearable placeholder="请选择" style="width: 100%">
+                    <el-option v-for="(item, index) in exam_gradeList" :key="index" :label="item" :value="item"></el-option>
+                  </el-select>
+                </el-form-item>
+              </el-col>
+            </el-col>
+            <el-col :span="24">
+              <el-col :span="6">
+                <el-form-item label="考试时间从" prop="startTime">
+                  <el-time-select
+                    placeholder="起始时间"
+                    v-model="form.startTime"
+                    :picker-options="{
+                      start: '08:00',
+                      step: '00:30',
+                      end: '18:00',
+                    }"
+                  >
+                  </el-time-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="6">
+                <el-form-item label="至" prop="endTime">
+                  <el-time-select
+                    placeholder="结束时间"
+                    v-model="form.endTime"
+                    :picker-options="{
+                      start: '08:00',
+                      step: '00:30',
+                      end: '18:00',
+                      minTime: form.startTime,
+                    }"
+                  >
+                  </el-time-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="考试地点" prop="exam_addr">
+                  <el-input v-model="form.exam_addr" placeholder="请输入考试地点"></el-input>
+                </el-form-item>
+              </el-col>
+            </el-col>
+            <el-col :span="24">
+              <el-col :span="12">
+                <el-form-item label="待考人数" prop="stayexam_personal">
+                  <el-input v-model="form.stayexam_personal" placeholder="请输入待考人数"></el-input>
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="计划参考人数" prop="test_number">
+                  <el-input v-model="form.test_number" placeholder="请输入计划参考人数"></el-input>
+                </el-form-item>
+              </el-col>
+            </el-col>
+            <el-col :span="24">
+              <el-col :span="24">
+                <el-form-item label="考场编排情况" prop="layout_situation">
+                  <el-input v-model="form.layout_situation" placeholder="请输入考场编排情况"></el-input>
+                </el-form-item>
+              </el-col>
+            </el-col>
+            <el-col :span="24">
+              <el-col :span="8">
+                <el-form-item label="公安机关" prop="police_office">
+                  <el-input v-model="form.police_office" placeholder="请输入公安机关"></el-input>
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="考场编排人" prop="layout_personal">
+                  <el-input v-model="form.layout_personal" placeholder="请输入考场编排人"></el-input>
+                </el-form-item>
+              </el-col>
+              <el-col :span="8">
+                <el-form-item label="编排时间" prop="room_date">
+                  <el-date-picker v-model="form.room_date" placeholder="请选择" value-format="yyyy-MM-dd" format="yyyy-MM-dd" type="date" style="width: 100%">
+                  </el-date-picker>
+                </el-form-item>
+              </el-col>
+            </el-col>
+            <el-col :span="24" class="btn">
+              <el-button type="danger" size="small" @click="back">取消保存</el-button>
+              <el-button type="primary" size="small" @click="submitForm('form')">提交保存</el-button>
+            </el-col>
+          </el-form>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+const { exam_grade } = require('@common/src/layout/deploy/dict');
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: examination_room } = createNamespacedHelpers('examination_room');
+export default {
+  name: 'add',
+  props: {},
+  components: {},
+  data: function () {
+    return {
+      form: {},
+      rules: {
+        exam_date: [{ required: true, message: '请选择', trigger: 'change' }],
+        // 考试时间
+        exam_time: [{ required: false, message: '请选择', trigger: 'change' }],
+        startTime: [{ required: true, message: '请选择', trigger: 'change' }],
+        endTime: [{ required: true, message: '请选择', trigger: 'change' }],
+        exam_grade: [{ required: true, message: '请选择', trigger: 'change' }],
+        stayexam_personal: [{ required: true, message: '请输入待考人数', trigger: 'blur' }],
+        test_number: [{ required: true, message: '请输入计划参考人数', trigger: 'blur' }],
+        exam_addr: [{ required: true, message: '请输入考试地点', trigger: 'blur' }],
+        num_range: [{ required: true, message: '请输入准考证号范围', trigger: 'blur' }],
+        layout_situation: [{ required: true, message: '请输入考场编排情况', trigger: 'blur' }],
+        police_office: [{ required: true, message: '请输入公安机关', trigger: 'blur' }],
+        layout_personal: [{ required: true, message: '请输入考场编排人', trigger: 'blur' }],
+        room_date: [{ required: true, message: '请选择', trigger: 'change' }],
+      },
+      // 考试等级
+      exam_gradeList: exam_grade,
+    };
+  },
+  async created() {
+    if (this.id) await this.search();
+    else this.searchNum();
+  },
+  methods: {
+    ...examination_room(['query', 'fetch', 'create', 'update']),
+    async search() {
+      let res = await this.fetch({ id: this.id });
+      if (this.$checkRes(res)) {
+        this.$set(this, `form`, res.data);
+      }
+    },
+    submitForm(formName) {
+      this.$refs[formName].validate(async (valid) => {
+        if (valid) {
+          let data = this.form;
+          if (data.id) {
+            data.exam_time = data.startTime + '-' + data.endTime;
+            let res = await this.update(data);
+            if (this.$checkRes(res)) {
+              this.$message({ type: `success`, message: `操作成功` });
+              this.back();
+            } else {
+              this.$message({ type: `success`, message: `${res.errmsg}` });
+            }
+          } else {
+            data.exam_time = data.startTime + '-' + data.endTime;
+            let res = await this.create(data);
+            if (this.$checkRes(res)) {
+              this.$message({ type: `success`, message: `操作成功` });
+              this.back();
+            } else {
+              this.$message({ type: `success`, message: `${res.errmsg}` });
+            }
+          }
+        } else {
+          console.log('error submit!!');
+          return false;
+        }
+      });
+    },
+    // 返回
+    back() {
+      this.$router.push({ path: '/baoanexam/security/kcbp' });
+    },
+    // 查询整理编号
+    async searchNum() {
+      let res = await this.query();
+      if (this.$checkRes(res)) {
+        // 部门编号
+        let dept_num = this.user.department_code || '2201';
+        if (dept_num.length < 6) {
+          dept_num = _.padEnd(dept_num, 6, '0');
+        }
+        // 获取当前年
+        let year = Number(new Date().getFullYear());
+        // 计算场次
+        let total = res.total + 1;
+        if (total.toString().length == 1) total = '00' + total;
+        else if (total.toString().length == 2) total = '0' + total;
+        else total;
+        let testsite_num = dept_num + year + total;
+        this.$set(this.form, `testsite_num`, testsite_num);
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .one {
+    text-align: right;
+    margin: 10px 0;
+  }
+  .two {
+    text-align: center;
+  }
+}
+/deep/.el-date-editor.el-input,
+.el-date-editor.el-input__inner {
+  width: 100%;
+}
+</style>

+ 389 - 0
src/views/baoanexam/security/kcbp/index.vue

@@ -0,0 +1,389 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="one">
+          <el-col :span="24" class="one_1"> 保安员考场编排查询 </el-col>
+          <el-col :span="24" class="one_2">
+            <el-form ref="searchFrom" :model="searchFrom" label-width="100px">
+              <el-col :span="24" class="one_2txt">
+                <el-col :span="8">
+                  <el-form-item label="考试地点">
+                    <el-input v-model="searchFrom.exam_addr"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="公安机关">
+                    <el-input v-model="searchFrom.police_office"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="考试等级">
+                    <el-select v-model="searchFrom.exam_grade" clearable placeholder="请选择">
+                      <el-option v-for="(item, index) in exam_gradeList" :key="index" :label="item" :value="item"></el-option>
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24" class="one_2txt">
+                <el-col :span="24" class="btn">
+                  <el-button type="primary" size="mini" @click="search()">查询</el-button>
+                  <el-button type="primary" size="mini" @click="toAdd()">添加</el-button>
+                  <!-- <el-button type="primary" size="mini" @click="toExport">导入文件样本下载</el-button> -->
+                </el-col>
+              </el-col>
+            </el-form>
+          </el-col>
+        </el-col>
+        <el-col :span="24" class="two">
+          <data-table
+            @query="search"
+            :fields="fields"
+            :opera="opera"
+            :data="list"
+            :total="total"
+            @edit="toEdit"
+            @view="toView"
+            @autolayout="autolayout"
+            @handlayout="handlayout"
+            @againlayout="againlayout"
+            @check="toCheck"
+          >
+          </data-table>
+        </el-col>
+      </el-col>
+    </el-row>
+    <el-dialog class="showinfo" title="信息详情" :visible.sync="showinfo" width="70%" :before-close="close" :close-on-click-modal="false">
+      <info-1 :info="info" :list="infoList"></info-1>
+    </el-dialog>
+    <el-dialog class="showlayout" title="手动编排" :visible.sync="showlayout" width="50%" :before-close="twoClose" :close-on-click-modal="false">
+      <layout-1 :romminfo="romminfo" :list="bmjfList" @layoutSubmit="layoutSubmit"></layout-1>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import info1 from './parts/info-1.vue';
+import layout1 from './parts/layout-1.vue';
+const { exam_grade } = require('@common/src/layout/deploy/dict');
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: examination_room } = createNamespacedHelpers('examination_room');
+const { mapActions: examination_examinee } = createNamespacedHelpers('examination_examinee');
+const { mapActions: security_guard_base } = createNamespacedHelpers('security_guard_base');
+const { mapActions: sendTemplate } = createNamespacedHelpers('sendTemplate');
+const moment = require('moment');
+export default {
+  name: 'index',
+  props: {},
+  components: { info1, layout1 },
+  data: function () {
+    return {
+      // 查询
+      searchFrom: {},
+      // 列表
+      fields: [
+        { label: '考场编码', model: 'testsite_num' },
+        { label: '考试日期', model: 'exam_date' },
+        { label: '考试时间', model: 'exam_time' },
+        { label: '考试等级', model: 'exam_grade' },
+        { label: '考试地点', model: 'exam_addr' },
+        { label: '计划参考人数', model: 'test_number' },
+        { label: '编排时间', model: 'room_date' },
+      ],
+      opera: [
+        { label: '修改', method: 'edit', type: 'primary', display: (i) => i.status === '0' },
+        { label: '详情', method: 'view', type: 'info' },
+        { label: '自动编排', method: 'autolayout', type: 'primary', display: (i) => i.status === '0' },
+        { label: '手动编排', method: 'handlayout', type: 'primary', display: (i) => i.status === '0' },
+        { label: '重新编排', method: 'againlayout', type: 'primary', display: (i) => i.status === '1' },
+        { label: '确认编排', method: 'check', type: 'success', display: (i) => i.status === '1' },
+      ],
+      list: [],
+      total: 0,
+      // 考试等级
+      exam_gradeList: exam_grade,
+      // 考场信息详情
+      showinfo: false,
+      info: {},
+      // 考场考生信息
+      infoList: [],
+      // 手动编排
+      showlayout: false,
+      romminfo: {},
+      bmjfList: [],
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...examination_room(['query', 'update']),
+    ...examination_examinee({ bmjfQuery: 'query', bmjfUpdate: 'update' }),
+    ...security_guard_base({ baseFetch: 'fetch', baseQuery: 'query', baseUpdate: 'update' }),
+    ...sendTemplate(['send']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const condition = _.cloneDeep(this.searchFrom);
+      let res = await this.query({ skip, limit, ...condition, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    // 添加
+    toAdd() {
+      this.$router.push({ path: '/baoanexam/security/kcbp/add' });
+    },
+    // 修改
+    toEdit({ data }) {
+      this.$router.push({ path: '/baoanexam/security/kcbp/add', query: { id: data.id } });
+    },
+    // 详情
+    async toView({ data }) {
+      let res = await this.bmjfQuery({ room_id: data.id });
+      if (this.$checkRes(res)) {
+        this.$set(this, `infoList`, res.data);
+        this.$set(this, `info`, data);
+        this.showinfo = true;
+      }
+    },
+    // 关闭详情
+    close() {
+      this.showinfo = false;
+    },
+    // 自动编排
+    async autolayout({ data }) {
+      // 查询已缴费和未编排的保安员
+      let status = '0';
+      let res = await this.bmjfQuery({ is_money: '已缴费', status, limit: data.test_number, exam_grade: data.exam_grade });
+      if (this.$checkRes(res)) {
+        if (res.total > 0) {
+          for (const val of res.data) {
+            val.status = '1';
+            val.seat_num = this.searchNum(res.data, val);
+            val.room_id = data.id;
+            val.police_office = data.police_office;
+            val.exam_date = data.exam_date;
+            val.exam_time = data.exam_time;
+            val.testsite_num = data.testsite_num;
+            val.exam_addr = data.exam_addr;
+            val.exam_num = data.testsite_num + val.seat_num;
+            let arr = await this.bmjfUpdate(val);
+            if (this.$checkRes(arr)) {
+              // 编排成功,改变考点状态
+              data.status = '1';
+              let room = await this.update(data);
+              if (this.$checkRes(room)) {
+                this.$message({ type: `success`, message: `操作成功` });
+              } else {
+                this.$message({ type: `fail`, message: `${arr.errmsg}` });
+              }
+            } else {
+              this.$message({ type: `fail`, message: `${arr.errmsg}` });
+            }
+          }
+        } else {
+          this.$message({ type: `success`, message: `暂无保安员进行考场编排` });
+        }
+      }
+    },
+    // 计算座位号
+    searchNum(data, val) {
+      let num = data.findIndex((i) => i.card == val.card) + 1;
+      if (num.toString().length == 1) num = '00' + num;
+      else if (num.toString().length == 2) num = '0' + num;
+      else num;
+      return num;
+    },
+    // 手动编排
+    async handlayout({ data }) {
+      let status = '0';
+      let res = await this.bmjfQuery({ is_money: '已缴费', status, limit: data.test_number, exam_grade: data.exam_grade });
+      if (this.$checkRes(res)) {
+        this.$set(this, `bmjfList`, res.data);
+        this.$set(this, `romminfo`, data);
+        this.showlayout = true;
+      }
+    },
+    // 手动编排提交
+    async layoutSubmit(data) {
+      let room = this.romminfo;
+      for (const val of data) {
+        val.status = '1';
+        val.seat_num = this.searchNum(data, val);
+        val.room_id = room.id;
+        val.police_office = room.police_office;
+        val.exam_date = room.exam_date;
+        val.exam_time = room.exam_time;
+        val.testsite_num = room.testsite_num;
+        val.exam_addr = room.exam_addr;
+        val.exam_num = room.testsite_num + val.seat_num;
+        let arr = await this.bmjfUpdate(val);
+        if (this.$checkRes(arr)) {
+          room.status = '1';
+          let p1 = await this.update(room);
+          if (this.$checkRes(p1)) {
+            this.$message({ type: `success`, message: `操作成功` });
+            this.twoClose();
+          } else {
+            this.$message({ type: `fail`, message: `${p1.errmsg}` });
+          }
+        } else {
+          this.$message({ type: `fail`, message: `${arr.errmsg}` });
+        }
+      }
+    },
+    // 关闭手动编排
+    twoClose() {
+      this.showlayout = false;
+    },
+    // 重新编排
+    async againlayout({ data }) {
+      let res = await this.bmjfQuery({ limit: data.test_number, room_id: data.id });
+      if (this.$checkRes(res)) {
+        for (const val of res.data) {
+          let p1 = {
+            exam_num: '',
+            police_office: '',
+            exam_date: '',
+            exam_time: '',
+            testsite_num: '',
+            exam_addr: '',
+            seat_num: '',
+            room_id: '',
+            status: '0',
+            id: val.id,
+          };
+          let arr = await this.bmjfUpdate(p1);
+          if (this.$checkRes(arr)) {
+            data.status = '0';
+            let room = await this.update(data);
+            if (this.$checkRes(room)) {
+              this.$message({ type: `success`, message: `操作成功` });
+              this.twoClose();
+            } else {
+              this.$message({ type: `fail`, message: `${arr.errmsg}` });
+            }
+          }
+        }
+      }
+    },
+    // 确认编排
+    async toCheck({ data }) {
+      let res = await this.bmjfQuery({ is_money: '已缴费', status: '1', room_id: data.id });
+      if (this.$checkRes(res)) {
+        for (const val of res.data) {
+          // 修改报名缴费
+          this.updateBmjf({ id: val.id, status: '2' });
+          // 修改房间表状态
+          this.updateRoom(data);
+          // 给考生发送消息
+          this.sendTem(data);
+        }
+      }
+    },
+    // 修改报名缴费
+    async updateBmjf(data) {
+      let res = await this.bmjfUpdate(data);
+      if (this.$checkRes(res));
+    },
+    // 修改房间表状态
+    async updateRoom(data) {
+      data.status = '2';
+      let res = await this.update(data);
+      if (this.$checkRes(res)) {
+        this.$message({ type: `success`, message: `操作完成` });
+      } else {
+        this.$message({ type: `fail`, message: `${res.errmsg}` });
+      }
+    },
+    // 给考生发放考试通知
+    async sendTem(data) {
+      let res = await this.bmjfQuery({ room_id: data.id });
+      if (this.$checkRes(res)) {
+        for (const val of res.data) {
+          let p1 = await this.baseFetch({ id: val.security_guard_id });
+          if (this.$checkRes(p1)) {
+            let p2 = {
+              openid: p1.data.gopenid,
+              unionid: p1.data.unionid,
+              templateId: process.env.VUE_APP_templateId,
+              url: '',
+              topColor: '',
+              data: {
+                first: { value: '保安员报名考试考场信息' },
+                keyword1: { value: '考场信息' },
+                keyword2: { value: '已编排' },
+                keyword3: { value: moment(new Date()).format('YYYY-MM-DD') },
+                remark: { value: '您的考试信息已发送,请您登录信息查看考试信息!' },
+              },
+            };
+            let p3 = await this.send(p2);
+            if (p3.errcode == '0' && p3.wxOpenid) {
+              let P4 = await this.baseUpdate({ id: this.basic_id, gopenid: p3.wxOpenid });
+              if (this.$checkRes(P4));
+            }
+          }
+        }
+      }
+    },
+    // 导入样本下载
+    toExport() {},
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .one {
+    .one_1 {
+      height: 40px;
+      line-height: 40px;
+      border-bottom: 1px solid #ccc;
+      margin: 0 0 10px 0;
+      font-size: 18px;
+      font-weight: bold;
+    }
+    .one_2 {
+      .one_2txt {
+        border: 1px solid #ccc;
+        /deep/.el-form-item {
+          margin: 0;
+        }
+        /deep/.el-date-editor.el-input,
+        .el-date-editor.el-input__inner {
+          width: 100%;
+        }
+        /deep/.el-select {
+          width: 100%;
+        }
+        .btn {
+          text-align: right;
+          padding: 5px 5px;
+        }
+      }
+    }
+  }
+}
+.showinfo {
+  /deep/.el-dialog__body {
+    height: 650px;
+    overflow-y: auto;
+  }
+}
+/deep/.el-dialog__body {
+  padding: 10px;
+}
+</style>

+ 153 - 0
src/views/baoanexam/security/kcbp/parts/info-1.vue

@@ -0,0 +1,153 @@
+<template>
+  <div id="info-1">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="one">
+          <el-col :span="12" class="left">
+            <el-form :model="info" ref="info" label-width="130px">
+              <el-col :span="24">
+                <el-col :span="24">
+                  <el-form-item label="考点编号">
+                    <el-input v-model="info.testsite_num" disabled></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24">
+                <el-col :span="12">
+                  <el-form-item label="考试日期">
+                    <el-input v-model="info.exam_date" readonly></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="考试时间">
+                    <el-input v-model="info.exam_time" readonly></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24">
+                <el-col :span="12">
+                  <el-form-item label="计划参考人数">
+                    <el-input v-model="info.test_number" readonly></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="实际参考人数">
+                    <el-input v-model="info.actual_examper" readonly></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24">
+                <el-col :span="12">
+                  <el-form-item label="考试等级">
+                    <el-input v-model="info.exam_grade" readonly></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="考试地点">
+                    <el-input v-model="info.exam_addr" readonly></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24">
+                <el-col :span="24">
+                  <el-form-item label="考场编排情况">
+                    <el-input v-model="info.layout_situation" readonly rows="2" type="textarea"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="24">
+                  <el-form-item label="考试情况">
+                    <el-input v-model="info.exam_situation" readonly rows="2" type="textarea"></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24">
+                <el-col :span="12">
+                  <el-form-item label="公安机关">
+                    <el-input v-model="info.police_office" readonly></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="编排人">
+                    <el-input v-model="info.layout_personal" readonly></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24">
+                <el-col :span="12">
+                  <el-form-item label="编排时间">
+                    <el-input v-model="info.room_date" readonly></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="登记人">
+                    <el-input v-model="info.register_personal" readonly></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24">
+                <el-col :span="24">
+                  <el-form-item label="考试登记时间">
+                    <el-input v-model="info.register_date" readonly></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+            </el-form>
+          </el-col>
+          <el-col :span="12" class="right">
+            <data-table :fields="fields" :opera="opera" :data="list" :usePage="false"> </data-table>
+          </el-col>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'info-1',
+  props: {
+    info: { type: Object },
+    list: { type: Array },
+  },
+  components: {},
+  data: function () {
+    return {
+      fields: [
+        { label: '姓名', model: 'name' },
+        { label: '联系电话', model: 'exam_num' },
+        { label: '准考证号', model: 'phone' },
+        { label: '考试成绩', model: 'exam_achieve' },
+        { label: '体能', model: 'stamina' },
+      ],
+      opera: [],
+    };
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .one {
+    .right {
+      position: relative;
+      top: -30px;
+    }
+  }
+}
+</style>

+ 84 - 0
src/views/baoanexam/security/kcbp/parts/layout-1.vue

@@ -0,0 +1,84 @@
+<template>
+  <div id="layout-1">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="one">
+          <el-transfer v-model="value" :titles="['未编排', '已编排']" :button-texts="['取消', '确认']" :data="data">
+            <el-col :span="24" slot-scope="{ option }">
+              <el-col :span="12" class="textOver">{{ option.name }}</el-col>
+              <el-col :span="12" class="textOver">{{ option.card }}</el-col>
+            </el-col>
+          </el-transfer>
+        </el-col>
+        <el-col :span="24" class="two">
+          <el-button type="primary" size="small" @click="onSubmit">确认编排</el-button>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: examination_examinee } = createNamespacedHelpers('examination_examinee');
+import _ from 'lodash';
+export default {
+  name: 'layout-1',
+  props: {
+    romminfo: { type: Object },
+    list: { type: Array },
+  },
+  components: {},
+  data: function () {
+    return {
+      value: [],
+      data: [],
+    };
+  },
+  created() {},
+  methods: {
+    ...examination_examinee(['fetch']),
+    // 整理数据
+    search() {
+      var data = this.list.map((i) => ({ key: i.id, name: i.name, card: i.card, disabled: false }));
+      this.$set(this, `data`, data);
+    },
+    async onSubmit() {
+      let res = _.cloneDeep(this.value);
+      res = res.map((i) => this.list.find((f) => f.id == i));
+      this.$emit('layoutSubmit', res);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    list: {
+      deep: true,
+      immediate: true,
+      handler(val) {
+        if (val) this.search();
+      },
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .one {
+    /deep/.el-transfer-panel {
+      width: 36%;
+      height: 500px;
+      overflow-y: auto;
+    }
+  }
+  .two {
+    text-align: center;
+    margin: 10px 0;
+  }
+}
+</style>

+ 233 - 0
src/views/baoanexam/security/kscjcx/index.vue

@@ -0,0 +1,233 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="one">
+          <el-col :span="24" class="one_1"> 保安员报名考试成绩查询 </el-col>
+          <el-col :span="24" class="one_2">
+            <el-form ref="searchFrom" :model="searchFrom" label-width="100px">
+              <el-col :span="24" class="one_2txt">
+                <el-col :span="8">
+                  <el-form-item label="姓名">
+                    <el-input v-model="searchFrom.name" placeholder="请输入姓名"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="身份证号">
+                    <el-input v-model="searchFrom.card" placeholder="请输入身份证号"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="联系电话">
+                    <el-input v-model="searchFrom.phone" placeholder="请输入联系电话"></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24" class="one_2txt">
+                <el-col :span="24" class="btn">
+                  <el-button type="primary" size="mini" @click="search()">查询</el-button>
+                </el-col>
+              </el-col>
+            </el-form>
+          </el-col>
+        </el-col>
+        <el-col :span="24" class="two">
+          <data-table @query="search" :fields="fields" :opera="opera" :data="list" :total="total" @pass="toPass" @nopass="toNopass"> </data-table>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: examination_examinee } = createNamespacedHelpers('examination_examinee');
+const { mapActions: security_guard_base } = createNamespacedHelpers('security_guard_base');
+const { mapActions: certificates_base } = createNamespacedHelpers('certificates_base');
+const { mapActions: sendTemplate } = createNamespacedHelpers('sendTemplate');
+const moment = require('moment');
+export default {
+  name: 'index',
+  props: {},
+  components: {},
+  data: function () {
+    return {
+      // 查询
+      searchFrom: {},
+      // 列表
+      fields: [
+        { label: '姓名', model: 'name' },
+        { label: '身份证号', model: 'card' },
+        { label: '考试等级', model: 'exam_grade' },
+        { label: '考试日期', model: 'exam_date' },
+        { label: '准考证号', model: 'exam_num' },
+        { label: '考试类型', model: 'exam_type' },
+        { label: '准考证打印日期', model: 'exam_type' },
+        { label: '考试成绩', model: 'exam_achieve' },
+        { label: '联系电话', model: 'phone' },
+        { label: '公安机关', model: 'police_office' },
+      ],
+      opera: [
+        { label: '合格', method: 'pass', type: 'primary', display: (i) => i.status === '3' && i.exam_achieve >= 60 },
+        { label: '不合格', method: 'nopass', type: 'primary', display: (i) => i.status === '3' && i.exam_achieve < 60 },
+      ],
+      list: [],
+      total: 0,
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...examination_examinee(['query', 'update']),
+    ...security_guard_base({ baseFetch: 'fetch', baseUpdate: 'update' }),
+    ...certificates_base(['create']),
+    ...sendTemplate(['send']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const condition = _.cloneDeep(this.searchFrom);
+      let res = await this.query({ skip, limit, ...condition, ...this.searchFrom, status: '3', ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    // 合格考生
+    async toPass({ data }) {
+      // 修改考生信息,4:考试合格的学生
+      data.status = '4';
+      let p0 = await this.update(data);
+      if (this.$checkRes(p0)) {
+        // 修改个人信息表,状态为7:待制证
+        this.updateBase(data);
+        // 发送消息,合格可预约领取保安员证
+        this.sendTem(data);
+      }
+    },
+    // 修改个人信息表,状态为7:待制证;并创建证件表
+    async updateBase(data) {
+      let p1 = await this.baseFetch({ id: data.security_guard_id });
+      if (this.$checkRes(p1)) {
+        p1.data.status = '7';
+        let p2 = await this.baseUpdate(p1.data);
+        if (this.$checkRes(p2)) {
+          // 创建证件表
+          this.createCert(data, p1.data);
+        } else {
+          this.$message({ type: `success`, message: `${p2.errmsg}` });
+        }
+      }
+    },
+    // 创建制证表
+    async createCert(data, base) {
+      let arr = {
+        name: data.name,
+        gender: data.gender,
+        card: data.card,
+        phone: data.phone,
+        exam_grade: data.exam_grade,
+        baoan_num: data.exam_num,
+        security_guard_id: data.security_guard_id,
+        birth: base.birth,
+        house_onaddress: base.house_onaddress,
+        is_cert: '0',
+        is_receive: '1',
+        cert_status: '0',
+      };
+      let p3 = await this.create(arr);
+      if (this.$checkRes(p3)) {
+        this.$message({ type: `success`, message: `操作成功` });
+        this.search();
+      } else {
+        this.$message({ type: `success`, message: `${res.errmsg}` });
+      }
+    },
+    // 不合格考生
+    async toNopass({ data }) {
+      data.status = '-4';
+      let p0 = await this.update(data);
+      if (this.$checkRes(p0)) {
+        this.$message({ type: `success`, message: `操作成功` });
+        // 发送消息,不合格,可以补考
+        this.sendTem(data);
+        this.search();
+      } else {
+        this.$message({ type: `success`, message: `${p0.errmsg}` });
+      }
+    },
+    // 发送消息
+    async sendTem(data) {
+      let res = await this.baseFetch({ id: data.security_guard_id });
+      if (this.$checkRes(res)) {
+        let p1 = {
+          openid: res.data.gopenid,
+          unionid: res.data.unionid,
+          templateId: process.env.VUE_APP_templateId,
+          url: '',
+          topColor: '',
+          data: {
+            first: { value: '保安员报名考试结果' },
+            keyword1: { value: '考试成绩' },
+            keyword2: { value: data.status == '4' ? '考试合格' : '考试不合格' },
+            keyword3: { value: moment(new Date()).format('YYYY-MM-DD') },
+            remark: {
+              value: data.status == '4' ? '保安员考试成绩合格。您可登录系统查看详细考试信息' : '保安员考试成绩不合格,您可联系相关人员,进行安排补考!',
+            },
+          },
+        };
+        let p2 = await this.send(p1);
+        if (p2.errcode == '0' && p2.wxOpenid) {
+          let p3 = await this.baseUpdate({ id: this.basic_id, gopenid: p2.wxOpenid });
+          if (this.$checkRes(p3));
+        }
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .one {
+    .one_1 {
+      height: 40px;
+      line-height: 40px;
+      border-bottom: 1px solid #ccc;
+      margin: 0 0 10px 0;
+      font-size: 18px;
+      font-weight: bold;
+    }
+    .one_2 {
+      .one_2txt {
+        border: 1px solid #ccc;
+        /deep/.el-form-item {
+          margin: 0;
+        }
+        /deep/.el-date-editor.el-input,
+        .el-date-editor.el-input__inner {
+          width: 100%;
+        }
+        /deep/.el-select {
+          width: 100%;
+        }
+        .btn {
+          text-align: right;
+          padding: 5px 5px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 226 - 0
src/views/baoanexam/security/ksqkdj/index.vue

@@ -0,0 +1,226 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="one">
+          <el-col :span="24" class="one_1"> 保安员考场考试情况登记查询 </el-col>
+          <el-col :span="24" class="one_2">
+            <el-form ref="searchFrom" :model="searchFrom" label-width="100px">
+              <el-col :span="24" class="one_2txt">
+                <el-col :span="8">
+                  <el-form-item label="考试地点">
+                    <el-input v-model="searchFrom.exam_addr"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="公安机关">
+                    <el-input v-model="searchFrom.police_office"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="考试等级">
+                    <el-select v-model="searchFrom.exam_grade" clearable placeholder="请选择">
+                      <el-option v-for="(item, index) in exam_gradeList" :key="index" :label="item" :value="item"></el-option>
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24" class="one_2txt">
+                <el-col :span="24" class="btn">
+                  <el-button type="primary" size="mini" @click="search()">查询</el-button>
+                  <el-button type="primary" size="mini" @click="getTemplate">导入格式文件下载</el-button>
+                </el-col>
+              </el-col>
+            </el-form>
+          </el-col>
+        </el-col>
+        <el-col :span="24" class="two">
+          <data-table @query="search" :fields="fields" :opera="opera" :data="list" :total="total" @check="check" @achieveExp="achieveExp" @conCheck="conCheck">
+          </data-table>
+        </el-col>
+      </el-col>
+    </el-row>
+    <el-dialog class="showinfo" title="信息登记" :visible.sync="showinfo" width="70%" :before-close="close" :close-on-click-modal="false">
+      <info-1 :info="info" :list="infoList" @saveBtn="saveBtn"></info-1>
+    </el-dialog>
+    <el-dialog class="showAchieve" title="成绩导入" :visible.sync="achieveShow" width="30%" :before-close="achClose" :close-on-click-modal="false">
+      <achieve-1 :form="achieveForm" @achClose="achClose" @twoSubmit="twoSubmit"></achieve-1>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import info1 from './parts/info-1.vue';
+import achieve1 from './parts/achieve-1.vue';
+const { exam_grade } = require('@common/src/layout/deploy/dict');
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: examination_room } = createNamespacedHelpers('examination_room');
+const { mapActions: examination_examinee } = createNamespacedHelpers('examination_examinee');
+const { mapActions: import_file } = createNamespacedHelpers('import_file');
+export default {
+  name: 'index',
+  props: {},
+  components: { info1, achieve1 },
+  data: function () {
+    return {
+      // 查询
+      searchFrom: {},
+      // 列表
+      fields: [
+        { label: '考场编码', model: 'testsite_num' },
+        { label: '考试日期', model: 'exam_date' },
+        { label: '考试时间', model: 'exam_time' },
+        { label: '考试等级', model: 'exam_grade' },
+        { label: '考试地点', model: 'exam_addr' },
+        { label: '计划参考人数', model: 'test_number' },
+        { label: '编排时间', model: 'room_date' },
+      ],
+      opera: [
+        { label: '登记', method: 'check', type: 'primary', display: (i) => i.status === '2' },
+        { label: '成绩导入', method: 'achieveExp', type: 'primary', display: (i) => i.status === '2' },
+        { label: '确认登记', method: 'conCheck', type: 'primary', display: (i) => i.status === '2' },
+      ],
+      list: [],
+      total: 0,
+      // 考试等级
+      exam_gradeList: exam_grade,
+      // 考场情况登记
+      showinfo: false,
+      info: {},
+      infoList: [],
+      // 考场考生成绩导入
+      achieveShow: false,
+      achieveForm: {},
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...examination_room(['query', 'update']),
+    ...examination_examinee({ bmjfQuery: 'query', bmjfUpdate: 'update' }),
+    ...import_file(['importExam']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const condition = _.cloneDeep(this.searchFrom);
+      let res = await this.query({ skip, limit, ...condition, status: '2', ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    // 考试情况登记
+    async check({ data }) {
+      let res = await this.bmjfQuery({ room_id: data.id });
+      if (this.$checkRes(res)) {
+        this.$set(this, `infoList`, res.data);
+        data.register_personal = this.user.name;
+        this.$set(this, `info`, data);
+        this.showinfo = true;
+      }
+    },
+    // 保存考场考试登记信息
+    async saveBtn({ data }) {
+      let res = await this.update(data);
+      if (this.$checkRes(res)) {
+        this.$message({ type: `success`, message: `操作成功` });
+      } else {
+        this.$message({ type: `success`, message: `${res.errmsg}` });
+      }
+    },
+    async conCheck({ data }) {
+      let res = await this.bmjfQuery({ room_id: data.id });
+      if (this.$checkRes(res)) {
+        for (const val of res.data) {
+          // 计算成绩是否合格,60分分界点
+          val.status = '3';
+          let p1 = await this.bmjfUpdate(val);
+        }
+        data.status = '3';
+        let p2 = await this.update(data);
+        if (this.$checkRes(p2)) {
+          this.$message({ type: `success`, message: `操作成功` });
+          this.search();
+        } else {
+          this.$message({ type: `success`, message: `${res.errmsg}` });
+        }
+      }
+    },
+    // 关闭查看
+    close() {
+      this.showinfo = false;
+    },
+    // 成绩导入
+    // 打开导入
+    achieveExp() {
+      this.achieveShow = true;
+    },
+    // 提交导入
+    async twoSubmit({ data }) {
+      let res = await this.importExam(data);
+      if (this.$checkRes(res)) {
+        this.$message({ type: `success`, message: `操作成功` });
+        this.achieveForm = { uri: '' };
+        this.achClose();
+      }
+    },
+    // 成绩导入关闭
+    achClose() {
+      this.achieveShow = false;
+    },
+    // 导入格式文件下载
+    getTemplate() {
+      window.open(`${process.env.VUE_APP_HOST}/${process.env.VUE_APP_ROUTER}/考试成绩导入文件模板.xlsx`, '_blank');
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .one {
+    .one_1 {
+      height: 40px;
+      line-height: 40px;
+      border-bottom: 1px solid #ccc;
+      margin: 0 0 10px 0;
+      font-size: 18px;
+      font-weight: bold;
+    }
+    .one_2 {
+      .one_2txt {
+        border: 1px solid #ccc;
+        /deep/.el-form-item {
+          margin: 0;
+        }
+        /deep/.el-date-editor.el-input,
+        .el-date-editor.el-input__inner {
+          width: 100%;
+        }
+        /deep/.el-select {
+          width: 100%;
+        }
+        .btn {
+          text-align: right;
+          padding: 5px 5px;
+        }
+      }
+    }
+  }
+}
+/deep/.el-dialog__body {
+  padding: 10px;
+}
+</style>

+ 64 - 0
src/views/baoanexam/security/ksqkdj/parts/achieve-1.vue

@@ -0,0 +1,64 @@
+<template>
+  <div id="achieve-1">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="one">
+          <el-form ref="form" :model="form" label-width="100px">
+            <el-form-item label="成绩导入文件">
+              <s-upload :limit="1" :data="form.uri" type="uri" listType="" :url="uri" @upload="uplSuc" @delete="uplDel"></s-upload>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="danger" size="mini" @click="achClose">取消导入</el-button>
+              <el-button type="primary" size="mini" @click="twoSubmit">提交导入</el-button>
+            </el-form-item>
+          </el-form>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'achieve-1',
+  props: { form: { type: Object }, uri: { type: String, default: '/files/baoan/examAchieve/upload' } },
+  components: {},
+  data: function () {
+    return {};
+  },
+  created() {},
+  methods: {
+    // 取消导入
+    achClose() {
+      this.$emit('achClose');
+    },
+    // 提交导入
+    twoSubmit() {
+      this.$emit('twoSubmit', { data: this.form });
+    },
+    // 上传图片
+    uplSuc({ type, data }) {
+      this.$set(this.form, `${type}`, data.uri);
+    },
+    uplDel({ type }) {
+      this.$set(this.form, `${type}`, null);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 183 - 0
src/views/baoanexam/security/ksqkdj/parts/info-1.vue

@@ -0,0 +1,183 @@
+<template>
+  <div id="info-1">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="one">
+          <el-col :span="12" class="left">
+            <el-form :model="info" ref="info" label-width="130px">
+              <el-col :span="24">
+                <el-col :span="24">
+                  <el-form-item label="考点编号">
+                    <el-input v-model="info.testsite_num" disabled></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24">
+                <el-col :span="12">
+                  <el-form-item label="考试日期">
+                    <el-input v-model="info.exam_date" readonly></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="考试时间">
+                    <el-input v-model="info.exam_time" readonly></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24">
+                <el-col :span="12">
+                  <el-form-item label="计划参考人数">
+                    <el-input v-model="info.test_number" readonly></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="实际参考人数">
+                    <el-input v-model="info.actual_examper" placeholder="请输入实际参考人数"></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24">
+                <el-col :span="12">
+                  <el-form-item label="考试等级">
+                    <el-input v-model="info.exam_grade" readonly></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="考试地点">
+                    <el-input v-model="info.exam_addr" readonly></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24">
+                <el-col :span="24">
+                  <el-form-item label="考场编排情况">
+                    <el-input v-model="info.layout_situation" readonly rows="2" type="textarea"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="24">
+                  <el-form-item label="考试情况">
+                    <el-input v-model="info.exam_situation" placeholder="请输入考试情况" rows="2" type="textarea"></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24">
+                <el-col :span="12">
+                  <el-form-item label="公安机关">
+                    <el-input v-model="info.police_office" readonly></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="编排人">
+                    <el-input v-model="info.layout_personal" readonly></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24">
+                <el-col :span="12">
+                  <el-form-item label="编排时间">
+                    <el-input v-model="info.room_date" readonly></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="登记人">
+                    <el-input v-model="info.register_personal" placeholder="请输入登记人"></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24">
+                <el-col :span="24">
+                  <el-form-item label="考试登记时间">
+                    <el-date-picker
+                      v-model="info.register_date"
+                      placeholder="请选择"
+                      value-format="yyyy-MM-dd"
+                      format="yyyy-MM-dd"
+                      type="date"
+                      style="width: 100%"
+                    >
+                    </el-date-picker>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24" class="btn">
+                <el-button type="primary" size="small" @click="saveBtn">保存信息</el-button>
+              </el-col>
+            </el-form>
+          </el-col>
+          <el-col :span="12" class="right">
+            <el-col :span="24" class="right_1">
+              <data-table :fields="fields" :opera="opera" :data="list" :usePage="false"> </data-table>
+            </el-col>
+          </el-col>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'info-1',
+  props: {
+    info: { type: Object },
+    list: { type: Array },
+  },
+  components: {},
+  data: function () {
+    return {
+      fields: [
+        { label: '姓名', model: 'name' },
+        { label: '联系电话', model: 'phone' },
+        { label: '准考证号', model: 'exam_num' },
+        { label: '考试成绩', model: 'exam_achieve' },
+        { label: '体能', model: 'stamina' },
+      ],
+      opera: [],
+    };
+  },
+  created() {},
+  methods: {
+    // 保存考场信息
+    saveBtn() {
+      this.$emit('saveBtn', { data: this.info });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .one {
+    .left {
+      .btn {
+        text-align: center;
+      }
+    }
+    .right {
+      position: relative;
+      top: -30px;
+      .right_1 {
+        height: 595px;
+        overflow-y: auto;
+      }
+      .btn {
+        text-align: center;
+      }
+    }
+  }
+}
+</style>

+ 175 - 0
src/views/baoanexam/security/ksxxdc/index.vue

@@ -0,0 +1,175 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="one">
+          <el-col :span="24" class="one_1"> 保安员考试信息导出 </el-col>
+          <el-col :span="24" class="one_2">
+            <el-form ref="searchFrom" :model="searchFrom" label-width="100px">
+              <el-col :span="24" class="one_2txt">
+                <el-col :span="8">
+                  <el-form-item label="姓名">
+                    <el-input v-model="searchFrom.name"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="公民身份号码">
+                    <el-input v-model="searchFrom.card"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="准考证号">
+                    <el-input v-model="searchFrom.exam_num"></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24" class="one_2txt">
+                <el-col :span="8">
+                  <el-form-item label="考点编号">
+                    <el-input v-model="searchFrom.testsite_num"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="16" class="btn">
+                  <el-button type="primary" size="mini" @click="search()">查询</el-button>
+                  <download-excel :data="selected" :fields="excel_fields.fields" :name="excel_fields.name" style="display: inline; margin: 0 0 0 10px">
+                    <el-button type="primary" size="mini">批量导出</el-button>
+                  </download-excel>
+                </el-col>
+              </el-col>
+            </el-form>
+          </el-col>
+        </el-col>
+        <el-col :span="24" class="two">
+          <data-table
+            @query="search"
+            :fields="fields"
+            :opera="opera"
+            :data="list"
+            :total="total"
+            :select="true"
+            @check="toCheck"
+            :selected="selected"
+            @handleSelect="handleSelect"
+          >
+          </data-table>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+const { exam_grade, baoan_exam } = require('@common/src/layout/deploy/dict');
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: examination_examinee } = createNamespacedHelpers('examination_examinee');
+export default {
+  name: 'index',
+  props: {},
+  components: {},
+  data: function () {
+    return {
+      // 查询
+      searchFrom: {},
+      // 列表
+      fields: [
+        { label: '姓名', model: 'name' },
+        { label: '性别', model: 'gender' },
+        { label: '身份号码', model: 'card' },
+        { label: '准考证号', model: 'exam_num' },
+        { label: '考试等级', model: 'exam_grade' },
+        { label: '考点编号', model: 'testsite_num' },
+        { label: '考试日期', model: 'exam_date' },
+        { label: '考试时间', model: 'exam_time' },
+        { label: '考试地点', model: 'exam_addr' },
+        { label: '准考证打印日期', model: 'exam_printdate' },
+        { label: '准考证打印人', model: 'exam_printper' },
+        { label: '公安机关', model: 'police_office' },
+      ],
+      // 考试考生信息导出头
+      excel_fields: baoan_exam,
+      opera: [
+        // { label: '导出', method: 'check' }
+      ],
+      list: [],
+      total: 0,
+      // 考试等级
+      exam_gradeList: exam_grade,
+      // 批量导出列表
+      selected: [],
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...examination_examinee(['query']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const condition = _.cloneDeep(this.searchFrom);
+      let res = await this.query({ skip, limit, status: '2', ...condition, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    // 批量选择打印数
+    handleSelect(data) {
+      this.$set(this, `selected`, data);
+    },
+    // 批量导出
+    toExport() {
+      console.log(this.selected);
+    },
+    // 单个导出
+    toCheck({ data }) {
+      console.log(data);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .one {
+    .one_1 {
+      height: 40px;
+      line-height: 40px;
+      border-bottom: 1px solid #ccc;
+      margin: 0 0 10px 0;
+      font-size: 18px;
+      font-weight: bold;
+    }
+    .one_2 {
+      .one_2txt {
+        border: 1px solid #ccc;
+        /deep/.el-form-item {
+          margin: 0;
+        }
+        /deep/.el-date-editor.el-input,
+        .el-date-editor.el-input__inner {
+          width: 100%;
+        }
+        /deep/.el-select {
+          width: 100%;
+        }
+        .btn {
+          text-align: right;
+          padding: 5px 5px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 139 - 0
src/views/baoanexam/security/zkzcx/index.vue

@@ -0,0 +1,139 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="one">
+          <el-col :span="24" class="one_1"> 保安员报名准考证查询 </el-col>
+          <el-col :span="24" class="one_2">
+            <el-form ref="searchFrom" :model="searchFrom" label-width="100px">
+              <el-col :span="24" class="one_2txt">
+                <el-col :span="8">
+                  <el-form-item label="姓名">
+                    <el-input v-model="searchFrom.name"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="公民身份号码">
+                    <el-input v-model="searchFrom.card"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="准考证号">
+                    <el-input v-model="searchFrom.exam_num"></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24" class="one_2txt">
+                <el-col :span="24" class="btn">
+                  <el-button type="primary" size="mini" @click="search()">查询</el-button>
+                </el-col>
+              </el-col>
+            </el-form>
+          </el-col>
+        </el-col>
+        <el-col :span="24" class="two">
+          <data-table @query="search" :fields="fields" :opera="opera" :data="list" :total="total"> </data-table>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+const { exam_grade } = require('@common/src/layout/deploy/dict');
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: examination_examinee } = createNamespacedHelpers('examination_examinee');
+export default {
+  name: 'index',
+  props: {},
+  components: {},
+  data: function () {
+    return {
+      // 查询
+      searchFrom: {},
+      // 列表
+      fields: [
+        { label: '姓名', model: 'name' },
+        { label: '性别', model: 'gender' },
+        { label: '身份号码', model: 'card' },
+        { label: '准考证号', model: 'exam_num' },
+        { label: '考试等级', model: 'exam_grade' },
+        { label: '考试日期', model: 'exam_date' },
+        { label: '考试时间', model: 'exam_time' },
+        { label: '考试地点', model: 'exam_addr' },
+        { label: '准考证打印日期', model: 'exam_printdate' },
+        { label: '准考证打印人', model: 'exam_printper' },
+        { label: '公安机关', model: 'police_office' },
+      ],
+      opera: [],
+      list: [],
+      total: 0,
+      // 考试等级
+      exam_gradeList: exam_grade,
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...examination_examinee(['query']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const condition = _.cloneDeep(this.searchFrom);
+      let res = await this.query({ skip, limit, ...condition, status: '2', ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    // 导入
+    toExport() {},
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .one {
+    .one_1 {
+      height: 40px;
+      line-height: 40px;
+      border-bottom: 1px solid #ccc;
+      margin: 0 0 10px 0;
+      font-size: 18px;
+      font-weight: bold;
+    }
+    .one_2 {
+      .one_2txt {
+        border: 1px solid #ccc;
+        /deep/.el-form-item {
+          margin: 0;
+        }
+        /deep/.el-date-editor.el-input,
+        .el-date-editor.el-input__inner {
+          width: 100%;
+        }
+        /deep/.el-select {
+          width: 100%;
+        }
+        .btn {
+          text-align: right;
+          padding: 5px 5px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 142 - 0
src/views/baoanexam/security/zkzdy/index.vue

@@ -0,0 +1,142 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="one">
+          <el-col :span="24" class="one_1"> 准考证打印 </el-col>
+          <el-col :span="24" class="one_2">
+            <el-form ref="searchFrom" :model="searchFrom" label-width="100px">
+              <el-col :span="24" class="one_2txt">
+                <el-col :span="8">
+                  <el-form-item label="姓名">
+                    <el-input v-model="searchFrom.name"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="公民身份号码">
+                    <el-input v-model="searchFrom.card"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="准考证号">
+                    <el-input v-model="searchFrom.exam_num"></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24" class="one_2txt">
+                <el-col :span="24" class="btn">
+                  <el-button type="primary" size="mini" @click="search()">查询</el-button>
+                </el-col>
+              </el-col>
+            </el-form>
+          </el-col>
+        </el-col>
+        <el-col :span="24" class="two">
+          <data-table @query="search" :fields="fields" :opera="opera" :data="list" :total="total" @check="toCheck"> </data-table>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+const { exam_grade } = require('@common/src/layout/deploy/dict');
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: examination_examinee } = createNamespacedHelpers('examination_examinee');
+export default {
+  name: 'index',
+  props: {},
+  components: {},
+  data: function () {
+    return {
+      // 查询
+      searchFrom: {},
+      // 列表
+      fields: [
+        { label: '姓名', model: 'name' },
+        { label: '性别', model: 'gender' },
+        { label: '身份号码', model: 'card' },
+        { label: '准考证号', model: 'exam_num' },
+        { label: '考试等级', model: 'exam_grade' },
+        { label: '考试日期', model: 'exam_date' },
+        { label: '考试时间', model: 'exam_time' },
+        { label: '考试地点', model: 'exam_addr' },
+        { label: '准考证打印日期', model: 'exam_printdate' },
+        { label: '准考证打印人', model: 'exam_printper' },
+        { label: '公安机关', model: 'police_office' },
+      ],
+      opera: [{ label: '打印', method: 'check' }],
+      list: [],
+      total: 0,
+      // 考试等级
+      exam_gradeList: exam_grade,
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...examination_examinee(['query']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const condition = _.cloneDeep(this.searchFrom);
+      let res = await this.query({ skip, limit, ...condition, status: '2', ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+
+    // 单个打印,跳转页面
+    toCheck({ data }) {
+      this.$router.push({ path: '/printA4/index', query: { id: data.id, type: 'zkz' } });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .one {
+    .one_1 {
+      height: 40px;
+      line-height: 40px;
+      border-bottom: 1px solid #ccc;
+      margin: 0 0 10px 0;
+      font-size: 18px;
+      font-weight: bold;
+    }
+    .one_2 {
+      .one_2txt {
+        border: 1px solid #ccc;
+        /deep/.el-form-item {
+          margin: 0;
+        }
+        /deep/.el-date-editor.el-input,
+        .el-date-editor.el-input__inner {
+          width: 100%;
+        }
+        /deep/.el-select {
+          width: 100%;
+        }
+        .btn {
+          text-align: right;
+          padding: 5px 5px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 150 - 0
src/views/baoanexam/security/zkzdyrz/index.vue

@@ -0,0 +1,150 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main">
+        <el-col :span="24" class="one">
+          <el-col :span="24" class="one_1"> 保安员准考证打印日志 </el-col>
+          <el-col :span="24" class="one_2">
+            <el-form ref="searchFrom" :model="searchFrom" label-width="100px">
+              <el-col :span="24" class="one_2txt">
+                <el-col :span="8">
+                  <el-form-item label="保安员姓名">
+                    <el-input v-model="searchFrom.name"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="打印公安机关">
+                    <el-input v-model="searchFrom.print_exam_office"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="准考证号">
+                    <el-input v-model="searchFrom.exam_num"></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24" class="one_2txt">
+                <el-col :span="16">
+                  <el-form-item label="打印时间">
+                    <el-date-picker
+                      v-model="searchFrom.print_date"
+                      type="daterange"
+                      range-separator="至"
+                      start-placeholder="开始日期"
+                      end-placeholder="结束日期"
+                    >
+                    </el-date-picker>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="打印人">
+                    <el-input v-model="searchFrom.print_name"></el-input>
+                  </el-form-item>
+                </el-col>
+              </el-col>
+              <el-col :span="24" class="one_2txt">
+                <el-col :span="8">
+                  <el-form-item label="打印次数">
+                    <el-input v-model="searchFrom.print_start_num"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8">
+                  <el-form-item label="至">
+                    <el-input v-model="searchFrom.print_end_num"></el-input>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="8" class="btn">
+                  <el-button type="primary" size="mini" @click="search()">查询</el-button>
+                </el-col>
+              </el-col>
+            </el-form>
+          </el-col>
+        </el-col>
+        <el-col :span="24" class="two">
+          <data-table @query="search" :fields="fields" :opera="opera" :data="list" :total="total"> </data-table>
+        </el-col>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'index',
+  props: {},
+  components: {},
+  data: function () {
+    return {
+      // 查询
+      searchFrom: {},
+      // 列表
+      fields: [
+        { label: '报名编号', model: 'sign_num' },
+        { label: '准考证号', model: 'exam_num' },
+        { label: '保安员姓名', model: 'name' },
+        { label: '打印人', model: 'print_name' },
+        { label: '打印次数', model: 'print_num' },
+        { label: '打印时间', model: 'print_date' },
+        { label: '打印公安机关', model: 'print_exam_office' },
+      ],
+      opera: [],
+      list: [],
+      total: 0,
+    };
+  },
+  created() {},
+  methods: {
+    async search() {
+      console.log(this.searchFrom);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .one {
+    .one_1 {
+      height: 40px;
+      line-height: 40px;
+      border-bottom: 1px solid #ccc;
+      margin: 0 0 10px 0;
+      font-size: 18px;
+      font-weight: bold;
+    }
+    .one_2 {
+      .one_2txt {
+        border: 1px solid #ccc;
+        /deep/.el-form-item {
+          margin: 0;
+        }
+        /deep/.el-date-editor.el-input,
+        .el-date-editor.el-input__inner {
+          width: 100%;
+        }
+        /deep/.el-select {
+          width: 100%;
+        }
+        .btn {
+          text-align: right;
+          padding: 5px 5px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 36 - 0
src/views/baoanexam/security/zkzfmdy/index.vue

@@ -0,0 +1,36 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main"> 准考证封面打印 </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'index',
+  props: {},
+  components: {},
+  data: function () {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 36 - 0
src/views/homeIndex/index.vue

@@ -0,0 +1,36 @@
+<template>
+  <div id="index">
+    <el-row>
+      <el-col :span="24" class="main"> 长春市保安服务监管系统-考试端 </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'index',
+  props: {},
+  components: {},
+  data: function () {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 37 - 0
src/views/index.vue

@@ -0,0 +1,37 @@
+<template>
+  <div id="index">
+    <admin-frame></admin-frame>
+  </div>
+</template>
+
+<script>
+import adminFrame from '@common/src/components/adminCommon/frame.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'index',
+  props: {},
+  components: {
+    adminFrame,
+  },
+  data: function () {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 126 - 0
src/views/login.vue

@@ -0,0 +1,126 @@
+<template>
+  <div id="login">
+    <el-row>
+      <el-col :span="24">
+        <el-row>
+          <el-col :span="24" class="main">
+            <div class="w_1200">
+              <el-col :span="24" class="login">
+                <div class="ms-title">长春市保安服务监管系统-考试系统</div>
+                <el-form :model="form" :rules="rules" ref="login" label-width="0px" class="ms-content">
+                  <el-form-item prop="phone">
+                    <el-input v-model="form.phone" placeholder="请输入手机号" maxlength="11">
+                      <el-button slot="prepend" icon="el-icon-user"></el-button>
+                    </el-input>
+                  </el-form-item>
+                  <el-form-item prop="password">
+                    <el-input type="password" placeholder="请输入密码" v-model="form.password">
+                      <el-button slot="prepend" icon="el-icon-lock"></el-button>
+                    </el-input>
+                  </el-form-item>
+                  <div class="login-btn">
+                    <el-button type="primary" size="mini" @click="submitForm()">登录</el-button>
+                  </div>
+                </el-form>
+              </el-col>
+            </div>
+          </el-col>
+        </el-row>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions } = createNamespacedHelpers('login');
+export default {
+  metaInfo: { title: '登录' },
+  name: 'login',
+  props: {},
+  components: {},
+  data: function () {
+    return {
+      form: {},
+      rules: {
+        phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
+        password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
+      },
+    };
+  },
+  created() {},
+  methods: {
+    ...mapActions(['login']),
+    submitForm() {
+      this.$refs.login.validate(async (valid) => {
+        if (valid) {
+          // this.$router.push('/adminCenter/homeIndex');
+          let res = await this.login({ table: 'exam_account', ...this.form });
+          if (this.$checkRes(res)) {
+            this.$message.success('登录成功');
+            this.$router.push('/adminCenter/homeIndex');
+          }
+        } else {
+          this.$message.error('请输入账号和密码');
+          console.log('error submit!!');
+          return false;
+        }
+      });
+    },
+  },
+  computed: {},
+};
+</script>
+
+<style lang="less" scoped>
+.w_1200 {
+  width: 1200px;
+  margin: 0 auto;
+}
+.main {
+  background-image: url(../assets/img/login-bg.jpg);
+  height: 100vh;
+  background-repeat: no-repeat;
+  background-size: cover;
+  .login {
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    width: 350px;
+    margin: -190px 0 0 -175px;
+    border-radius: 5px;
+    background: hsla(0, 0%, 100%, 0.3);
+    overflow: hidden;
+    .ms-title {
+      width: 100%;
+      line-height: 50px;
+      text-align: center;
+      font-size: 20px;
+      color: #fff;
+      border-bottom: 1px solid #ddd;
+    }
+    .ms-content {
+      padding: 30px 30px;
+    }
+    .login-btn {
+      text-align: center;
+    }
+    .login-btn button {
+      width: 100%;
+      height: 36px;
+      margin-bottom: 10px;
+    }
+    .login-tips {
+      font-size: 12px;
+      line-height: 30px;
+      color: #fff;
+    }
+    /deep/.js .el-form-item__content {
+      padding: 0 25px;
+      .el-radio {
+        color: #fff;
+      }
+    }
+  }
+}
+</style>

+ 42 - 0
vue.config.js

@@ -0,0 +1,42 @@
+const path = require('path');
+const common = path.resolve(__dirname, '../baoan-common');
+module.exports = {
+  publicPath: `/${process.env.VUE_APP_ROUTER}`,
+  outputDir: process.env.VUE_APP_ROUTER,
+  productionSourceMap: false,
+  configureWebpack: (config) => {
+    Object.assign(config, {
+      resolve: {
+        alias: {
+          '@': path.resolve(__dirname, './src'),
+          '@c': path.resolve(__dirname, './src/components'),
+          '@a': path.resolve(__dirname, './src/assets'),
+          '@common': common,
+        },
+      },
+    });
+  },
+  devServer: {
+    port: '11201',
+    proxy: {
+      '/files': {
+        target: 'http://baoan.fwedzgc.com:8090',
+      },
+      '/api/position': {
+        target: 'http://106.12.161.200',
+        changeOrigin: true,
+        ws: false,
+      },
+      '/api/exam': {
+        target: 'http://127.0.0.1:6104',
+        changeOrigin: true,
+        ws: false,
+      },
+      '/api': {
+        target: 'http://baoan.fwedzgc.com:8090',
+        changeOrigin: true,
+        ws: false,
+      },
+    },
+  },
+};