lex-xin 3 years ago
parent
commit
81f0744017
73 changed files with 7158 additions and 1423 deletions
  1. 25 24
      babel.config.js
  2. 1 0
      dist/app.037adc037a362df5cbf4.js
  3. 0 0
      dist/app.bdeed0d939d5efed04fd.js
  4. 0 0
      dist/css/visitList.10c3e310.css
  5. 1 0
      dist/css/visitList.a1189626.css
  6. 1 0
      dist/index.html
  7. 0 0
      dist/js/privacy.2219b85f.js
  8. 0 0
      dist/js/visitList.ca4ce45c.js
  9. 533 86
      package-lock.json
  10. 4 0
      package.json
  11. 103 101
      src/App.vue
  12. 109 0
      src/assets/icon_font/iconfont.css
  13. 0 0
      src/assets/icon_font/iconfont.js
  14. 177 0
      src/assets/icon_font/iconfont.json
  15. BIN
      src/assets/icon_font/iconfont.ttf
  16. 101 100
      src/common/axios.js
  17. 14 0
      src/common/loading.js
  18. 169 112
      src/components/MHeader.vue
  19. 61 60
      src/helpers/request.js
  20. 22 2
      src/main.js
  21. 107 105
      src/router/index.js
  22. 37 0
      src/router/messageRouter.js
  23. 329 329
      src/router/teacherRouter.js
  24. 78 79
      src/views/app/HelpCenterDetail.vue
  25. 0 1
      src/views/audition/ArrangeWork.vue
  26. 134 0
      src/views/message/Approval.vue
  27. 1013 0
      src/views/message/ApprovalAction.vue
  28. 393 0
      src/views/message/ApprovalApply.vue
  29. 194 0
      src/views/message/api.js
  30. 134 0
      src/views/message/control/cascader.vue
  31. 98 0
      src/views/message/control/checkbox.vue
  32. 40 0
      src/views/message/control/controlCommon.less
  33. 133 0
      src/views/message/control/date.vue
  34. 40 0
      src/views/message/control/divider.vue
  35. 324 0
      src/views/message/control/fileUpload.vue
  36. 139 0
      src/views/message/control/imgFile.vue
  37. 86 0
      src/views/message/control/input.vue
  38. 76 0
      src/views/message/control/number.vue
  39. 157 0
      src/views/message/control/organ.vue
  40. 88 0
      src/views/message/control/radio.vue
  41. 107 0
      src/views/message/control/rate.vue
  42. 193 0
      src/views/message/control/school.vue
  43. 166 0
      src/views/message/control/select.vue
  44. 269 0
      src/views/message/control/subform.vue
  45. 52 0
      src/views/message/control/text.vue
  46. 79 0
      src/views/message/control/textarea.vue
  47. BIN
      src/views/message/images/a_1.png
  48. BIN
      src/views/message/images/a_2.png
  49. BIN
      src/views/message/images/a_3.png
  50. BIN
      src/views/message/images/default_icon.png
  51. BIN
      src/views/message/images/icon_close.png
  52. BIN
      src/views/message/images/icon_close_round.png
  53. BIN
      src/views/message/images/icon_ding.png
  54. BIN
      src/views/message/images/icon_edit.png
  55. BIN
      src/views/message/images/icon_reject.png
  56. BIN
      src/views/message/images/icon_resolve.png
  57. BIN
      src/views/message/images/icon_share.png
  58. BIN
      src/views/message/images/icon_transfer.png
  59. BIN
      src/views/message/images/music_icon.png
  60. BIN
      src/views/message/images/pay_icon.png
  61. BIN
      src/views/message/images/student_icon.png
  62. BIN
      src/views/message/images/system-complete-default.png
  63. BIN
      src/views/message/images/system-complete.png
  64. BIN
      src/views/message/images/system-reject.png
  65. BIN
      src/views/message/images/system-transfer.png
  66. BIN
      src/views/message/images/system-wait.png
  67. BIN
      src/views/message/images/work_icon.png
  68. 118 0
      src/views/message/modal/applyModal.vue
  69. 206 0
      src/views/message/modal/formModal.vue
  70. 240 0
      src/views/message/modal/transferModal.vue
  71. 376 0
      src/views/message/myApproval.vue
  72. 323 323
      src/views/teacher/privacy.vue
  73. 108 101
      vue.config.js

+ 25 - 24
babel.config.js

@@ -1,24 +1,25 @@
-// [["@vue/app", 
-//         { useBuiltIns: "entry",
-//         polyfills: [
-//             'es6.promise',
-//             'es6.symbol'
-//            ]},
-//     ]],
-// ["@vue/app"]
-module.exports = {
-    presets: [["@vue/app", 
-        { useBuiltIns: "entry",
-        polyfills: [
-            'es6.promise',
-            'es6.symbol'
-            ]},
-    ]],
-    plugins: [
-        ['import', {
-          libraryName: 'vant',
-          libraryDirectory: 'es',
-          style: name => `${name}/style/less`
-        }]
-    ]
-}
+// [["@vue/app", 
+//         { useBuiltIns: "entry",
+//         polyfills: [
+//             'es6.promise',
+//             'es6.symbol'
+//            ]},
+//     ]],
+// ["@vue/app"]
+module.exports = {
+    presets: [["@vue/app", 
+        { useBuiltIns: "entry",
+        polyfills: [
+            'es6.promise',
+            'es6.symbol'
+            ]},
+    ]],
+    plugins: [
+        ['import', {
+          libraryName: 'vant',
+          libraryDirectory: 'es',
+          style: name => `${name}/style/less`
+        }],
+        ["@babel/plugin-proposal-optional-chaining"]
+    ]
+}

File diff suppressed because it is too large
+ 1 - 0
dist/app.037adc037a362df5cbf4.js


File diff suppressed because it is too large
+ 0 - 0
dist/app.bdeed0d939d5efed04fd.js


File diff suppressed because it is too large
+ 0 - 0
dist/css/visitList.10c3e310.css


File diff suppressed because it is too large
+ 1 - 0
dist/css/visitList.a1189626.css


File diff suppressed because it is too large
+ 1 - 0
dist/index.html


File diff suppressed because it is too large
+ 0 - 0
dist/js/privacy.2219b85f.js


File diff suppressed because it is too large
+ 0 - 0
dist/js/visitList.ca4ce45c.js


File diff suppressed because it is too large
+ 533 - 86
package-lock.json


+ 4 - 0
package.json

@@ -13,8 +13,10 @@
     "caniuse": "^0.1.3",
     "caniuse-lite": "^1.0.30001313",
     "clean-deep": "^3.4.0",
+    "copy-to-clipboard": "^3.3.1",
     "core-js": "^2.6.5",
     "dayjs": "^1.8.31",
+    "e-icon-picker": "^1.1.7",
     "es6-promise": "^4.2.8",
     "install": "^0.13.0",
     "js-storage": "^1.1.0",
@@ -30,6 +32,8 @@
     "vuex": "^3.0.1"
   },
   "devDependencies": {
+    "@babel/core": "^7.17.9",
+    "@babel/plugin-proposal-optional-chaining": "^7.16.7",
     "@vue/cli-plugin-babel": "^3.10.0",
     "@vue/cli-plugin-eslint": "^3.10.0",
     "@vue/cli-service": "^3.10.0",

+ 103 - 101
src/App.vue

@@ -1,101 +1,103 @@
-<template>
-  <div id="app">
-    <transition name="fade">
-      <keep-alive>
-        <router-view v-if="$route.meta.keepAlive" />
-      </keep-alive>
-    </transition>
-    <transition name="fade">
-      <router-view v-if="!$route.meta.keepAlive" />
-    </transition>
-  </div>
-</template>
-
-<script>
-import { queryTeacherInfo } from "@/api/app";
-export default {
-  name: "app",
-  async created() {
-    const whiteList = ["#/order"];
-    // const routePath = this.$route.path;
-    const locationHash = window.location.hash;
-    // console.log(this.$route.path, locationHash);
-    if (locationHash.indexOf('#/order') == -1) {
-      try {
-        let Authorization = this.getQueryVariable("Authorization") || null;
-        if (window.location.hash.indexOf("+") >= 0) {
-          Authorization = Authorization ? Authorization.split("+")[1] : null;
-          Authorization = "bearer " + Authorization;
-        } else {
-          Authorization = decodeURI(Authorization);
-        }
-        if (Authorization && Authorization != "null") {
-          localStorage.setItem("Authorization", Authorization);
-          localStorage.setItem("userInfo", Authorization);
-        }
-        const auth = localStorage.getItem("Authorization") || "";
-        const userInfo = localStorage.getItem("userInfo") || "";
-        if (userInfo || auth) {
-          await queryTeacherInfo().then((res) => {
-            const result = res.data || null;
-            const tenantId = result.data.tenantId || 0;
-            sessionStorage.setItem("tenantId", tenantId);
-          });
-        }
-      } catch (e) {
-        console.log(e);
-      }
-    }
-  },
-  async mounted() {
-    if (document.querySelector("#m_loading")) {
-      document.querySelector("#m_loading").remove();
-    }
-  },
-  methods: {
-    getQueryVariable(variable) {
-      if (window.location.hash.indexOf("?") < 0) {
-        return null;
-      }
-      let query = window.location.hash.split("?")[1];
-      let vars = query.split("&");
-      for (let i = 0; i < vars.length; i++) {
-        let pair = vars[i].split("=");
-        if (pair[0] == variable) {
-          return pair[1];
-        }
-      }
-      return false;
-    },
-  },
-};
-</script>
-
-<style lang="less">
-@import url("./assets/commonLess/common");
-#app {
-  font-family: "Avenir", Helvetica, Arial, sans-serif;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  background: #f3f4f8;
-  // overflow-x: hidden;
-  // overflow-y: auto;
-  user-select: none;
-  -webkit-text-size-adjust: none !important;
-}
-
-// /deep/.van-icon.van-icon-success{
-
-// }
-// .fade-enter-active,
-// .fade-leave-active {
-//   transition: opacity 0.5s;
-// }
-// .fade-enter,
-// .fade-leave-active {
-//   opacity: 0;
-// }
-body {
-  -webkit-text-size-adjust: none !important;
-}
-</style>
+<template>
+  <div id="app">
+    <transition name="fade">
+      <keep-alive>
+        <router-view v-if="$route.meta.keepAlive" />
+      </keep-alive>
+    </transition>
+    <transition name="fade">
+      <router-view v-if="!$route.meta.keepAlive" />
+    </transition>
+  </div>
+</template>
+
+<script>
+import { queryTeacherInfo } from "@/api/app";
+export default {
+  name: "app",
+  async created() {
+    const whiteList = ["#/order"];
+    // const routePath = this.$route.path;
+    const locationHash = window.location.hash;
+    // console.log(this.$route.path, locationHash);
+    if (locationHash.indexOf('#/order') == -1) {
+      try {
+        let Authorization = this.getQueryVariable("Authorization") || null;
+        if (window.location.hash.indexOf("+") >= 0) {
+          Authorization = Authorization ? Authorization.split("+")[1] : null;
+          Authorization = "Bearer " + Authorization;
+        } else {
+          Authorization = decodeURI(Authorization);
+        }
+        if (Authorization && Authorization != "null") {
+          localStorage.setItem("Authorization", Authorization);
+          localStorage.setItem("userInfo", Authorization);
+        }
+        const auth = localStorage.getItem("Authorization") || "";
+        const userInfo = localStorage.getItem("userInfo") || "";
+        if (userInfo || auth) {
+          await queryTeacherInfo().then((res) => {
+            const result = res.data || null;
+            const tenantId = result.data.tenantId || 0;
+            sessionStorage.setItem("tenantId", tenantId);
+            sessionStorage.setItem("userId", result.data.id || 0);
+          });
+        }
+      } catch (e) {
+        console.log(e);
+      }
+    }
+  },
+  async mounted() {
+    if (document.querySelector("#m_loading")) {
+      document.querySelector("#m_loading").remove();
+    }
+  },
+  methods: {
+    getQueryVariable(variable) {
+      if (window.location.hash.indexOf("?") < 0) {
+        return null;
+      }
+      let query = window.location.hash.split("?")[1];
+      let vars = query.split("&");
+      for (let i = 0; i < vars.length; i++) {
+        let pair = vars[i].split("=");
+        if (pair[0] == variable) {
+          return pair[1];
+        }
+      }
+      return false;
+    },
+  },
+};
+</script>
+
+<style lang="less">
+@import url("./assets/commonLess/common");
+#app {
+  font-family: "Avenir", Helvetica, Arial, sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  background: #f3f4f8;
+  // overflow-x: hidden;
+  // overflow-y: auto;
+  user-select: none;
+  -webkit-text-size-adjust: none !important;
+  overflow: hidden;
+}
+
+// /deep/.van-icon.van-icon-success{
+
+// }
+// .fade-enter-active,
+// .fade-leave-active {
+//   transition: opacity 0.5s;
+// }
+// .fade-enter,
+// .fade-leave-active {
+//   opacity: 0;
+// }
+body {
+  -webkit-text-size-adjust: none !important;
+}
+</style>

+ 109 - 0
src/assets/icon_font/iconfont.css

@@ -0,0 +1,109 @@
+@font-face {
+  font-family: "iconfont"; /* Project id  */
+  src: url('iconfont.ttf?t=1625536907435') format('truetype');
+}
+
+/* .iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+} */
+
+.icon-icon_oa10:before {
+  content: "\e604";
+}
+
+.icon-icon_oa8:before {
+  content: "\e605";
+}
+
+.icon-icon_oa11:before {
+  content: "\e606";
+}
+
+.icon-icon_oa12:before {
+  content: "\e607";
+}
+
+.icon-icon_oa13:before {
+  content: "\e608";
+}
+
+.icon-icon_oa9:before {
+  content: "\e609";
+}
+
+.icon-icon_oa15:before {
+  content: "\e60a";
+}
+
+.icon-icon_oa14:before {
+  content: "\e60b";
+}
+
+.icon-icon_oa26:before {
+  content: "\e60c";
+}
+
+.icon-icon_oa27:before {
+  content: "\e60d";
+}
+
+.icon-icon_oa16:before {
+  content: "\e60e";
+}
+
+.icon-icon_oa25:before {
+  content: "\e60f";
+}
+
+.icon-icon_oa30:before {
+  content: "\e610";
+}
+
+.icon-icon_oa22:before {
+  content: "\e611";
+}
+
+.icon-icon_oa19:before {
+  content: "\e612";
+}
+
+.icon-icon_oa23:before {
+  content: "\e613";
+}
+
+.icon-icon_oa17:before {
+  content: "\e614";
+}
+
+.icon-icon_oa28:before {
+  content: "\e615";
+}
+
+.icon-icon_oa29:before {
+  content: "\e616";
+}
+
+.icon-icon_oa31:before {
+  content: "\e617";
+}
+
+.icon-icon_oa18:before {
+  content: "\e618";
+}
+
+.icon-icon_oa21:before {
+  content: "\e619";
+}
+
+.icon-icon_oa20:before {
+  content: "\e61a";
+}
+
+.icon-icon_oa24:before {
+  content: "\e61b";
+}
+

File diff suppressed because it is too large
+ 0 - 0
src/assets/icon_font/iconfont.js


+ 177 - 0
src/assets/icon_font/iconfont.json

@@ -0,0 +1,177 @@
+{
+  "id": "",
+  "name": "",
+  "font_family": "iconfont",
+  "css_prefix_text": "icon-",
+  "description": "",
+  "glyphs": [
+    {
+      "icon_id": "22685958",
+      "name": "icon_oa10",
+      "font_class": "icon_oa10",
+      "unicode": "e604",
+      "unicode_decimal": 58884
+    },
+    {
+      "icon_id": "22685959",
+      "name": "icon_oa8",
+      "font_class": "icon_oa8",
+      "unicode": "e605",
+      "unicode_decimal": 58885
+    },
+    {
+      "icon_id": "22685960",
+      "name": "icon_oa11",
+      "font_class": "icon_oa11",
+      "unicode": "e606",
+      "unicode_decimal": 58886
+    },
+    {
+      "icon_id": "22685961",
+      "name": "icon_oa12",
+      "font_class": "icon_oa12",
+      "unicode": "e607",
+      "unicode_decimal": 58887
+    },
+    {
+      "icon_id": "22685962",
+      "name": "icon_oa13",
+      "font_class": "icon_oa13",
+      "unicode": "e608",
+      "unicode_decimal": 58888
+    },
+    {
+      "icon_id": "22685963",
+      "name": "icon_oa9",
+      "font_class": "icon_oa9",
+      "unicode": "e609",
+      "unicode_decimal": 58889
+    },
+    {
+      "icon_id": "22685964",
+      "name": "icon_oa15",
+      "font_class": "icon_oa15",
+      "unicode": "e60a",
+      "unicode_decimal": 58890
+    },
+    {
+      "icon_id": "22685965",
+      "name": "icon_oa14",
+      "font_class": "icon_oa14",
+      "unicode": "e60b",
+      "unicode_decimal": 58891
+    },
+    {
+      "icon_id": "22686018",
+      "name": "icon_oa26",
+      "font_class": "icon_oa26",
+      "unicode": "e60c",
+      "unicode_decimal": 58892
+    },
+    {
+      "icon_id": "22686019",
+      "name": "icon_oa27",
+      "font_class": "icon_oa27",
+      "unicode": "e60d",
+      "unicode_decimal": 58893
+    },
+    {
+      "icon_id": "22686020",
+      "name": "icon_oa16",
+      "font_class": "icon_oa16",
+      "unicode": "e60e",
+      "unicode_decimal": 58894
+    },
+    {
+      "icon_id": "22686021",
+      "name": "icon_oa25",
+      "font_class": "icon_oa25",
+      "unicode": "e60f",
+      "unicode_decimal": 58895
+    },
+    {
+      "icon_id": "22686022",
+      "name": "icon_oa30",
+      "font_class": "icon_oa30",
+      "unicode": "e610",
+      "unicode_decimal": 58896
+    },
+    {
+      "icon_id": "22686023",
+      "name": "icon_oa22",
+      "font_class": "icon_oa22",
+      "unicode": "e611",
+      "unicode_decimal": 58897
+    },
+    {
+      "icon_id": "22686024",
+      "name": "icon_oa19",
+      "font_class": "icon_oa19",
+      "unicode": "e612",
+      "unicode_decimal": 58898
+    },
+    {
+      "icon_id": "22686025",
+      "name": "icon_oa23",
+      "font_class": "icon_oa23",
+      "unicode": "e613",
+      "unicode_decimal": 58899
+    },
+    {
+      "icon_id": "22686026",
+      "name": "icon_oa17",
+      "font_class": "icon_oa17",
+      "unicode": "e614",
+      "unicode_decimal": 58900
+    },
+    {
+      "icon_id": "22686027",
+      "name": "icon_oa28",
+      "font_class": "icon_oa28",
+      "unicode": "e615",
+      "unicode_decimal": 58901
+    },
+    {
+      "icon_id": "22686028",
+      "name": "icon_oa29",
+      "font_class": "icon_oa29",
+      "unicode": "e616",
+      "unicode_decimal": 58902
+    },
+    {
+      "icon_id": "22686029",
+      "name": "icon_oa31",
+      "font_class": "icon_oa31",
+      "unicode": "e617",
+      "unicode_decimal": 58903
+    },
+    {
+      "icon_id": "22686030",
+      "name": "icon_oa18",
+      "font_class": "icon_oa18",
+      "unicode": "e618",
+      "unicode_decimal": 58904
+    },
+    {
+      "icon_id": "22686031",
+      "name": "icon_oa21",
+      "font_class": "icon_oa21",
+      "unicode": "e619",
+      "unicode_decimal": 58905
+    },
+    {
+      "icon_id": "22686032",
+      "name": "icon_oa20",
+      "font_class": "icon_oa20",
+      "unicode": "e61a",
+      "unicode_decimal": 58906
+    },
+    {
+      "icon_id": "22686033",
+      "name": "icon_oa24",
+      "font_class": "icon_oa24",
+      "unicode": "e61b",
+      "unicode_decimal": 58907
+    }
+  ]
+}

BIN
src/assets/icon_font/iconfont.ttf


+ 101 - 100
src/common/axios.js

@@ -1,100 +1,101 @@
-"use strict";
-/* eslint-disable */
-import axios from "axios"
-// import router from '../router/index'
-import { browser } from '@/common/common'
-
-// Full config:  https://github.com/axios/axios#request-config
-// axios.defaults.baseURL = process.env.baseURL || process.env.apiUrl || '';
-// axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
-// axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
-
-let config = {
-  // baseURL: process.env.baseURL || process.env.apiUrl || ""
-  // timeout: 60 * 1000, // Timeout
-  // withCredentials: true, // Check cross-site Access-Control
-  // transformRequest: [function(data) {
-  //     console.log(data)
-  //     if(!data.qStringify) {
-  //       data = qs.stringify({
-  //           ...data
-  //       })
-  //     }
-  //     return data
-  // }]
-
-};
-
-const _axios = axios.create(config);
-
-_axios.interceptors.request.use(
-  function (config) {
-    // Do something before request is sent
-    // 判断用户是否登录
-    if (browser().android || browser().iPhone) { // app里面
-      let userInfo = decodeURI(localStorage.getItem('Authorization'))
-
-      if (userInfo) {
-        config.headers['Authorization'] = userInfo
-      }
-    } else { // 网页里面
-      let auth = decodeURI(localStorage.getItem('userInfo'))
-      if (auth) {
-        config.headers['Authorization'] = auth
-      }
-    }
-    const tenantId = sessionStorage.getItem('tenantId')
-    if(tenantId && tenantId != 'undefined') {
-      config.headers['tenantId'] = tenantId
-    }
-
-    // config.headers['Authorization'] = 'bearer 5848d682-f5a3-4ce1-b86d-5b5f466ae665'
-    return config;
-  },
-  function (error) {
-    // Do something with request error
-    return Promise.reject(error);
-  }
-);
-
-// Add a response interceptor
-_axios.interceptors.response.use(
-  function (response) {
-    // Do something with response data
-    if (response.data.code == 403) {
-      window.localStorage.removeItem('userInfo') // 删除用户信息
-      window.localStorage.removeItem('Authorization') // 删除用户信息
-      // android ios 注册方法
-      if (browser().android) {
-        DAYA.postMessage(JSON.stringify({ api: 'login' }))
-      } else if (browser().iPhone) {
-        window.webkit.messageHandlers.DAYA.postMessage(JSON.stringify({ api: 'login' }))
-      }
-    }
-    return response;
-  },
-  function (error) {
-    // Do something with response error
-    return Promise.reject(error);
-  }
-);
-
-// Plugin.install = function(Vue) {
-//   Vue.axios = _axios;
-//   window.axios = _axios;
-//   Object.defineProperties(Vue.prototype, {
-//     axios: {
-//       get() {
-//         return _axios;
-//       }
-//     },
-//     $axios: {
-//       get() {
-//         return _axios;
-//       }
-//     },
-//   });
-// };
-
-// Vue.use(Plugin)
-export default _axios
+"use strict";
+/* eslint-disable */
+import axios from "axios"
+// import router from '../router/index'
+import { browser } from '@/common/common'
+
+// Full config:  https://github.com/axios/axios#request-config
+// axios.defaults.baseURL = process.env.baseURL || process.env.apiUrl || '';
+// axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
+// axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
+
+let config = {
+  // baseURL: process.env.baseURL || process.env.apiUrl || ""
+  // timeout: 60 * 1000, // Timeout
+  // withCredentials: true, // Check cross-site Access-Control
+  // transformRequest: [function(data) {
+  //     console.log(data)
+  //     if(!data.qStringify) {
+  //       data = qs.stringify({
+  //           ...data
+  //       })
+  //     }
+  //     return data
+  // }]
+
+};
+
+const _axios = axios.create(config);
+
+_axios.interceptors.request.use(
+  function (config) {
+    // Do something before request is sent
+    // 判断用户是否登录
+    if (browser().android || browser().iPhone) { // app里面
+      let userInfo = decodeURI(localStorage.getItem('Authorization'))
+      userInfo = userInfo ? userInfo[0].toUpperCase() + userInfo.slice(1) : userInfo
+      if (userInfo) {
+        config.headers['Authorization'] = userInfo
+      }
+    } else { // 网页里面
+      let auth = decodeURI(localStorage.getItem('userInfo'))
+      auth = auth ? auth[0].toUpperCase() + auth.slice(1) : auth
+      if (auth) {
+        config.headers['Authorization'] = auth
+      }
+    }
+    const tenantId = sessionStorage.getItem('tenantId')
+    if(tenantId && tenantId != 'undefined') {
+      config.headers['tenantId'] = tenantId
+    }
+
+    // config.headers['Authorization'] = 'bearer 5848d682-f5a3-4ce1-b86d-5b5f466ae665'
+    return config;
+  },
+  function (error) {
+    // Do something with request error
+    return Promise.reject(error);
+  }
+);
+
+// Add a response interceptor
+_axios.interceptors.response.use(
+  function (response) {
+    // Do something with response data
+    if (response.data.code == 403) {
+      window.localStorage.removeItem('userInfo') // 删除用户信息
+      window.localStorage.removeItem('Authorization') // 删除用户信息
+      // android ios 注册方法
+      if (browser().android) {
+        DAYA.postMessage(JSON.stringify({ api: 'login' }))
+      } else if (browser().iPhone) {
+        window.webkit.messageHandlers.DAYA.postMessage(JSON.stringify({ api: 'login' }))
+      }
+    }
+    return response;
+  },
+  function (error) {
+    // Do something with response error
+    return Promise.reject(error);
+  }
+);
+
+// Plugin.install = function(Vue) {
+//   Vue.axios = _axios;
+//   window.axios = _axios;
+//   Object.defineProperties(Vue.prototype, {
+//     axios: {
+//       get() {
+//         return _axios;
+//       }
+//     },
+//     $axios: {
+//       get() {
+//         return _axios;
+//       }
+//     },
+//   });
+// };
+
+// Vue.use(Plugin)
+export default _axios

+ 14 - 0
src/common/loading.js

@@ -0,0 +1,14 @@
+import { Toast } from 'vant'
+
+// 加载
+export default function setLoading(status) {
+    if(status) {
+        Toast.loading({
+            duration: 0, // 持续展示 toast
+            forbidClick: true,
+            message: '加载中...',
+        })
+    } else {
+        Toast.clear()
+    }
+}

+ 169 - 112
src/components/MHeader.vue

@@ -1,113 +1,170 @@
-<template>
-    <div class="mheader">
-        <header class="m-nav-header" :class="[isFixed ? 'fixed' : '']">
-            <div class="m-nav-bar__left" @click="goBack" v-show="isBack">
-                <van-icon class="arrow-left" name="arrow-left"></van-icon>
-            </div>
-            <div class="m-nav-bar__title">
-                <slot>{{ name ? name : this.$route.meta.descrition }}</slot>
-            </div>
-            <div class="m-nav-bar__right">
-                <slot name="right"></slot>
-            </div>
-        </header>
-    </div>
-</template>
-
-<script>
-/**
- * 插槽使用方式
- *  <template v-slot:right>
-        这里
-    </template>
- */
-export default {
-    name: 'mheader',
-    props: {
-        name: String, // 标题名称
-        isBack: { // 是否显示返回按钮
-            type: Boolean,
-            default: true
-        },
-        isFixed: { // 是否固定顶部
-            type: Boolean,
-            default: true
-        },
-        backUrl: { // 跳转指定的URL
-            type: Object,
-            default: () => {
-                return {
-                    callBack: null, // 方法
-                    path: '', // 跳转路径
-                    params: {}, // 跳转参数 目前只能用query的方式
-                }
-            }
-        }
-    },
-    methods: {
-        goBack() { // 返回上层
-            let urlObj = this.backUrl
-            // console.log(typeof urlObj.callBack)
-            if(typeof urlObj.callBack == 'function') {
-                urlObj.callBack()
-            } else {
-                if(urlObj.path) {
-                    this.$router.push({
-                        path: urlObj.path,
-                        query: urlObj.params
-                    })
-                } else {
-                    history.go(-1)
-                }
-            }
-
-            
-        }
-    }
-}
-</script>
-
-<style lang="less" scoped>
-@import url('../assets/commonLess/variable');
-.mheader {
-    height: .46rem;
-    overflow: hidden;
-}
-.m-nav-header {
-    position: absolute;
-    left: 0;
-    top: 0;
-    width: 100%;
-    height: .46rem;
-    line-height: .46rem;
-    background-color:  @whiteColor;
-    text-align: center;
-    user-select: none;
-    color: @blackColor;
-    &.fixed {
-        position: fixed;
-        z-index: 99;
-    }
-    .m-nav-bar__title {
-        max-width: 60%;
-        margin: 0 auto;
-        color: @blackColor;
-        font-weight: 500;
-        font-size: .16rem;
-    }
-    .m-nav-bar__left, .m-nav-bar__right {
-        position: absolute;
-        bottom: 0;
-    }
-    .m-nav-bar__left {
-        left: .12rem;
-        .arrow-left {
-            font-size: .21rem;
-            vertical-align: middle;
-        }
-    }
-    .m-nav-bar__right {
-        right: .12rem;
-    }
-}
+<template>
+    <div class="mheader" :style="{ height: `calc(${titleHeight}px + ${navBarHeight}px)` }">
+        <header class="m-nav-header" :class="[isFixed ? 'fixed' : '']"  :style="[headStyle]">
+            <div class="m-nav-bar__left" @click="goBack" v-show="isBack">
+                <van-icon class="arrow-left" name="arrow-left"></van-icon>
+            </div>
+            <div class="m-nav-bar__title">
+                <slot>{{ name ? name : this.$route.meta.descrition }}</slot>
+            </div>
+            <div class="m-nav-bar__right">
+                <slot name="right"></slot>
+            </div>
+        </header>
+    </div>
+</template>
+
+<script>
+/**
+ * 插槽使用方式
+ *  <template v-slot:right>
+        这里
+    </template>
+ */
+import { postMessage } from '@/helpers/native-message'
+export default {
+    name: 'mheader',
+    props: {
+        name: String, // 标题名称
+        isNative: {
+            type: Boolean,
+            default: true
+        },
+        isBack: { // 是否显示返回按钮
+            type: Boolean,
+            default: true
+        },
+        isFixed: { // 是否固定顶部
+            type: Boolean,
+            default: true
+        },
+        backUrl: { // 跳转指定的URL
+            type: Object,
+            default: () => {
+                return {
+                    callBack: null, // 方法
+                    path: '', // 跳转路径
+                    params: {}, // 跳转参数 目前只能用query的方式
+                }
+            }
+        }
+    },
+    data() {
+        return {
+            navBarHeight: 0, // 顶部导航栏高度
+            headStyle: {},
+            titleHeight: 44, // 顶部导航高度(默认44px)
+        }
+    },
+    mounted() {
+        if (!this.isNative) {
+            postMessage({ api: 'setBarStatus', content: { status: 0 }})
+            this.getNav()
+            this.isBack = false
+        }
+    },
+    methods: {
+        async getNav() {
+            let sNavHeight = sessionStorage.getItem('navHeight')
+            let sTitleHeight = sessionStorage.getItem('titleHeight')
+            if(sNavHeight && sTitleHeight) {
+                this.navBarHeight = sNavHeight
+                this.headStyle = {
+                    paddingTop: sNavHeight + 'px',
+                    height: sTitleHeight + 'px',
+                    lineHeight: sTitleHeight + 'px'
+                }
+            } else {
+                await postMessage({ api: 'getNavHeight'}, (res) => {
+                    const { content } = res
+                    let headStyle = {}
+                    const dpi = content.dpi || 2
+                    if(content.navHeight) {
+                        const navHeight = content.navHeight / dpi
+                        sessionStorage.setItem('navHeight', navHeight)
+                        this.navBarHeight = navHeight
+                        headStyle = {
+                            paddingTop: navHeight + 'px'
+                        }
+                    }
+                    if(content.titleHeight) { // 导航栏的高度
+                        const titleHeight = content.titleHeight / dpi
+                        sessionStorage.setItem('titleHeight', titleHeight)
+                        this.titleHeight = titleHeight
+                        headStyle.height = titleHeight + 'px'
+                        headStyle.lineHeight = titleHeight + 'px'
+                    }
+                    this.headStyle = headStyle
+                })
+            }
+        },
+        goBack() { // 返回上层
+            let urlObj = this.backUrl
+            // console.log(typeof urlObj.callBack)
+            if(typeof urlObj.callBack == 'function') {
+                urlObj.callBack()
+            } else {
+                if(urlObj.path) {
+                    this.$router.push({
+                        path: urlObj.path,
+                        query: urlObj.params
+                    })
+                } else {
+                    history.go(-1)
+                }
+            }
+        }
+    },
+    beforeDestroy() {
+        if (!this.isNative) {
+            postMessage({ api: 'setBarStatus', content: { status: 1 }})
+            this.isBack = true
+        }
+    }
+}
+</script>
+
+<style lang="less" scoped>
+@import url('../assets/commonLess/variable');
+.mheader {
+    height: .46rem;
+    overflow: hidden;
+}
+.m-nav-header {
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: .46rem;
+    line-height: .46rem;
+    background-color:  @whiteColor;
+    text-align: center;
+    user-select: none;
+    color: @blackColor;
+    &.fixed {
+        position: fixed;
+        z-index: 99;
+    }
+    .m-nav-bar__title {
+        max-width: 60%;
+        margin: 0 auto;
+        color: @blackColor;
+        font-weight: 500;
+        font-size: .16rem;
+    }
+    .m-nav-bar__left, .m-nav-bar__right {
+        position: absolute;
+        bottom: 0;
+    }
+    .m-nav-bar__left {
+        left: .12rem;
+        .arrow-left {
+            font-size: .21rem;
+            vertical-align: middle;
+        }
+    }
+    .m-nav-bar__right {
+        right: .12rem;
+    }
+}
 </style>

+ 61 - 60
src/helpers/request.js

@@ -1,60 +1,61 @@
-import qs from 'qs'
-import axios from 'axios'
-import { Toast } from 'vant'
-import cleanDeep from 'clean-deep'
-import { sessionStorage as storage } from 'js-storage'
-import setLoading from '@/utils/loading'
-const instance = axios.create({
-  baseURL: '/api-teacher'
-})
-
-instance.interceptors.request.use(config => {
-  if (config.hideLoading !== true) {
-    setLoading(true)
-  }
-  config.data = cleanDeep(config.data)
-  config.params = cleanDeep(config.params)
-
-  if (config.method.toLocaleUpperCase() === 'POST' && config.requestType === 'form') {
-    config.data = qs.stringify(config.data)
-  }
-  const Authorization = storage.get('token') || localStorage.getItem('Authorization') || localStorage.getItem('userInfo')
-  if (config.baseURL !== '/api-auth' && Authorization) {
-    config.headers = {
-      ...config.headers,
-      Authorization,
-    }
-  }
-  const tenantId = sessionStorage.getItem('tenantId')
-  if(tenantId && tenantId != 'undefined') {
-    config.headers['tenantId'] = tenantId
-  }
-  return config
-})
-
-instance.interceptors.response.use(res => {
-  if (res.config.hideLoading !== true) {
-    setLoading(false)
-  }
-  if (!(res.status > 200 || res.status < 200)) {
-    if (res.data.code === 200) {
-      return res.data
-    } else if(res.data.code == 100 || res.data.code == 201) { // 支付时会用到的自定交code
-      return res.data
-    } else {
-      if (res.config.hint !== true) {
-        Toast(res.data.msg || '接口返回错误')
-      }
-      return Promise.reject(res.data)
-    }
-  } else {
-    if (res.config.hint !== true) {
-      Toast(res.data.msg || '接口返回错误')
-    }
-    return Promise.reject('网络错误')
-  }
-}, () => {
-  setLoading(false)
-})
-
-export default instance
+import qs from 'qs'
+import axios from 'axios'
+import { Toast } from 'vant'
+import cleanDeep from 'clean-deep'
+import { sessionStorage as storage } from 'js-storage'
+import setLoading from '@/utils/loading'
+const instance = axios.create({
+  baseURL: '/api-teacher'
+})
+
+instance.interceptors.request.use(config => {
+  if (config.hideLoading !== true) {
+    setLoading(true)
+  }
+  config.data = cleanDeep(config.data)
+  config.params = cleanDeep(config.params)
+
+  if (config.method.toLocaleUpperCase() === 'POST' && config.requestType === 'form') {
+    config.data = qs.stringify(config.data)
+  }
+  let Authorization = storage.get('token') || localStorage.getItem('Authorization') || localStorage.getItem('userInfo')
+  Authorization = Authorization ? Authorization[0].toUpperCase() + Authorization.slice(1) : Authorization
+  if (config.baseURL !== '/api-auth' && Authorization) {
+    config.headers = {
+      ...config.headers,
+      Authorization,
+    }
+  }
+  const tenantId = sessionStorage.getItem('tenantId')
+  if(tenantId && tenantId != 'undefined') {
+    config.headers['tenantId'] = tenantId
+  }
+  return config
+})
+
+instance.interceptors.response.use(res => {
+  if (res.config.hideLoading !== true) {
+    setLoading(false)
+  }
+  if (!(res.status > 200 || res.status < 200)) {
+    if (res.data.code === 200) {
+      return res.data
+    } else if(res.data.code == 100 || res.data.code == 201) { // 支付时会用到的自定交code
+      return res.data
+    } else {
+      if (res.config.hint !== true) {
+        Toast(res.data.msg || '接口返回错误')
+      }
+      return Promise.reject(res.data)
+    }
+  } else {
+    if (res.config.hint !== true) {
+      Toast(res.data.msg || '接口返回错误')
+    }
+    return Promise.reject('网络错误')
+  }
+}, () => {
+  setLoading(false)
+})
+
+export default instance

+ 22 - 2
src/main.js

@@ -10,7 +10,8 @@ import { Button, Icon, Tag, Swipe, SwipeItem, Popup, Picker,
     Circle, Field, DatetimePicker, Image, Loading,
     ActionSheet, RadioGroup, Radio, Checkbox, CheckboxGroup,
     CountDown, Panel, Dialog, Sticky, Rate, Switch, ImagePreview, NoticeBar, NavBar, Divider,
-    Grid, GridItem, Calendar, Empty} from 'vant'
+    Grid, GridItem, Calendar, Empty, Form, GoodsAction, GoodsActionButton, GoodsActionIcon, Uploader,
+  Step, Steps, TreeSelect} from 'vant'
 Vue.use(Button).use(Icon).use(Tag).use(Swipe).use(SwipeItem)
    .use(Popup).use(Picker).use(DropdownMenu).use(DropdownItem).use(Search)
    .use(PullRefresh).use(Toast).use(List).use(Collapse).use(CollapseItem)
@@ -18,7 +19,8 @@ Vue.use(Button).use(Icon).use(Tag).use(Swipe).use(SwipeItem)
    .use(Circle).use(Field).use(DatetimePicker).use(Image).use(Loading)
    .use(ActionSheet).use(RadioGroup).use(Radio).use(Checkbox).use(CheckboxGroup)
    .use(CountDown).use(Panel).use(Dialog).use(Sticky).use(Rate).use(Switch).use(ImagePreview).use(NoticeBar)
-   .use(NavBar).use(Divider).use(Grid).use(GridItem).use(Calendar).use(Empty)
+   .use(NavBar).use(Divider).use(Grid).use(GridItem).use(Calendar).use(Empty).use(Form).use(GoodsAction).use(GoodsActionButton)
+   .use(GoodsActionIcon).use(Uploader).use(Step).use(Steps).use(TreeSelect)
 
 // import Vconsole from 'vconsole'
 // const vconsole = new Vconsole()
@@ -32,6 +34,24 @@ Vue.mixin({
     }
 })
 
+import eIconPicker, { eIconSymbol } from 'e-icon-picker'
+import "e-icon-picker/lib/symbol.js"; //基本彩色图标库
+import 'e-icon-picker/lib/index.css'; // 基本样式,包含基本图标
+import 'font-awesome/css/font-awesome.min.css'; //font-awesome 图标库
+import iconfont from './assets/icon_font/iconfont.json'
+import './assets/icon_font/iconfont.css' // 引入css
+import './assets/icon_font/iconfont.js' // 引入css
+const forIconfont = eIconSymbol(iconfont) // 解析彩色图标
+// 全局删除增加图标
+Vue.use(eIconPicker, {
+  FontAwesome: false,
+  ElementUI: false,
+  eIcon: false, // 自带的图标,来自阿里妈妈
+  eIconSymbol: true, // 是否开启彩色图标
+  addIconList: forIconfont.list,
+  removeIconList: [] }) // 全局注册图标
+
+
 import VueAMap from 'vue-amap'
 Vue.use(VueAMap)
 VueAMap.initAMapApiLoader({

+ 107 - 105
src/router/index.js

@@ -1,105 +1,107 @@
-import Vue from 'vue'
-import Router from 'vue-router'
-import TeacherRouter from './teacherRouter'
-import AppRouter from './appRouter'
-import AuditionRouter from './auditionRouter'
-
-
-Vue.use(Router)
-
-let defaultRouter = [
-  {
-    path: '/',
-    redirect: {
-      name: 'business'
-    }
-  }, {
-    path: '/queryFortuneBag',
-    name: 'FortuneBag',
-    component: () => import(/* webpackChunkName:'CallNames'*/'@/views/teacher/queryList/queryFortuneBag'),
-    meta: {
-      descrition: '页面查看',
-      weight: 1 // 页面权重
-    }
-  },
-  {
-    path: '/queryActiveList',
-    name: 'ActiveList',
-    component: () => import(/* webpackChunkName:'CallNames'*/'@/views/teacher/queryList/queryActiveList'),
-    meta: {
-      descrition: '页面查看',
-      weight: 1 // 页面权重
-    }},
-    {
-      path: '/guide',
-      name: 'guide',
-      component: () => import(/* webpackChunkName:'CallNames'*/'@/views/rules/guide'),
-      meta: {
-        descrition: '投屏引导',
-        weight: 1 // 页面权重
-      }
-    }, {
-      path: '/applyActive',
-      name: 'applyActive',
-      component: () => import(/* webpackChunkName:'applyActive' */'@/views/applyActive/index.vue'),
-      meta: {
-          descrition: '考级活动',
-          weight: 0
-      }
-    }, {
-      path: '/registerProtocol',
-      name: 'registerProtocol',
-      component: () => import(/* webpackChunkName:'registerProtocol' */'@/views/protocol/registerProtocol.vue'),
-      meta: {
-          descrition: '查看协议',
-          weight: 0
-      }
-    },{
-      path: '/auth',
-      name: 'auth',
-      component: () => import(/* webpackChunkName:'auth' */'@/views/protocol/auth.vue'),
-      meta: {
-          descrition: '实名认证',
-          weight: 0
-      }
-    }, {
-      path: '/afterClassEvaluate',
-      name: 'afterClassEvaluate',
-      component: () => import(/* webpackChunkName:'afterClassEvaluate' */'@/views/afterClassEvaluate/index.vue'),
-      meta: {
-          descrition: '课后评价',
-          weight: 0
-      }
-    }, {
-      path: '/afterClassEvaluateDetail',
-      name: 'afterClassEvaluateDetail',
-      component: () => import(/* webpackChunkName:'afterClassEvaluateDetail' */'@/views/afterClassEvaluate/detail.vue'),
-      meta: {
-          descrition: '课后详情',
-          weight: 0
-      }
-    }
-]
-
-defaultRouter = defaultRouter.concat(TeacherRouter)
-                             .concat(AppRouter)
-                             .concat(AuditionRouter)
-
-const router = new Router({
-  // mode: 'history',
-  base: process.env.BASE_URL,
-  routes: defaultRouter,
-  scrollBehavior () {
-    return { x: 0, y: 0 }
-  }
-})
-router.onError((error) => {
-  const pattern = /Loading chunk (\d)+ failed/g;
-  const isChunkLoadFailed = error.message.match(pattern);
-  const targetPath = router.history.pending.fullPath;
-  if (isChunkLoadFailed) {
-    router.replace(targetPath);
-  }
-})
-
-export default router
+import Vue from 'vue'
+import Router from 'vue-router'
+import TeacherRouter from './teacherRouter'
+import AppRouter from './appRouter'
+import AuditionRouter from './auditionRouter'
+import MessageRouter from './messageRouter'
+
+
+Vue.use(Router)
+
+let defaultRouter = [
+  {
+    path: '/',
+    redirect: {
+      name: 'business'
+    }
+  }, {
+    path: '/queryFortuneBag',
+    name: 'FortuneBag',
+    component: () => import(/* webpackChunkName:'CallNames'*/'@/views/teacher/queryList/queryFortuneBag'),
+    meta: {
+      descrition: '页面查看',
+      weight: 1 // 页面权重
+    }
+  },
+  {
+    path: '/queryActiveList',
+    name: 'ActiveList',
+    component: () => import(/* webpackChunkName:'CallNames'*/'@/views/teacher/queryList/queryActiveList'),
+    meta: {
+      descrition: '页面查看',
+      weight: 1 // 页面权重
+    }},
+    {
+      path: '/guide',
+      name: 'guide',
+      component: () => import(/* webpackChunkName:'CallNames'*/'@/views/rules/guide'),
+      meta: {
+        descrition: '投屏引导',
+        weight: 1 // 页面权重
+      }
+    }, {
+      path: '/applyActive',
+      name: 'applyActive',
+      component: () => import(/* webpackChunkName:'applyActive' */'@/views/applyActive/index.vue'),
+      meta: {
+          descrition: '考级活动',
+          weight: 0
+      }
+    }, {
+      path: '/registerProtocol',
+      name: 'registerProtocol',
+      component: () => import(/* webpackChunkName:'registerProtocol' */'@/views/protocol/registerProtocol.vue'),
+      meta: {
+          descrition: '查看协议',
+          weight: 0
+      }
+    },{
+      path: '/auth',
+      name: 'auth',
+      component: () => import(/* webpackChunkName:'auth' */'@/views/protocol/auth.vue'),
+      meta: {
+          descrition: '实名认证',
+          weight: 0
+      }
+    }, {
+      path: '/afterClassEvaluate',
+      name: 'afterClassEvaluate',
+      component: () => import(/* webpackChunkName:'afterClassEvaluate' */'@/views/afterClassEvaluate/index.vue'),
+      meta: {
+          descrition: '课后评价',
+          weight: 0
+      }
+    }, {
+      path: '/afterClassEvaluateDetail',
+      name: 'afterClassEvaluateDetail',
+      component: () => import(/* webpackChunkName:'afterClassEvaluateDetail' */'@/views/afterClassEvaluate/detail.vue'),
+      meta: {
+          descrition: '课后详情',
+          weight: 0
+      }
+    }
+]
+
+defaultRouter = defaultRouter.concat(TeacherRouter)
+                             .concat(AppRouter)
+                             .concat(MessageRouter)
+                             .concat(AuditionRouter)
+
+const router = new Router({
+  // mode: 'history',
+  base: process.env.BASE_URL,
+  routes: defaultRouter,
+  scrollBehavior () {
+    return { x: 0, y: 0 }
+  }
+})
+router.onError((error) => {
+  const pattern = /Loading chunk (\d)+ failed/g;
+  const isChunkLoadFailed = error.message.match(pattern);
+  const targetPath = router.history.pending.fullPath;
+  if (isChunkLoadFailed) {
+    router.replace(targetPath);
+  }
+})
+
+export default router

+ 37 - 0
src/router/messageRouter.js

@@ -0,0 +1,37 @@
+let messageRouter = [{
+  path: '/approval',
+  name: 'approval',
+  component: () => import(/* webpackChunkName:'Approval'*/'@/views/message/Approval.vue'),
+  meta: {
+      descrition: 'OA审批',
+      weight: 8 // 页面权重
+  }
+},{
+  path: '/approvalApply',
+  name: 'approvalApply',
+  component: () => import(/* webpackChunkName:'ApprovalApply'*/'@/views/message/ApprovalApply.vue'),
+  meta: {
+      descrition: '审批',
+      weight: 11 // 页面权重
+  }
+},
+{
+  path: '/approvalAction',
+  name: 'approvalAction',
+  component: () => import(/* webpackChunkName:'ApprovalAction'*/'@/views/message/ApprovalAction.vue'),
+  meta: {
+      descrition: '详情',
+      weight: 11 // 页面权重
+  }
+},
+{
+  path: '/myApproval',
+  name: 'myApproval',
+  component: () => import(/* webpackChunkName:'ApprovalApply'*/'@/views/message/myApproval.vue'),
+  meta: {
+      descrition: '我的审批',
+      weight: 11 // 页面权重
+  }
+}]
+
+export default messageRouter

+ 329 - 329
src/router/teacherRouter.js

@@ -1,329 +1,329 @@
-let teacherRouter = [
-  {
-    path: "/callnames",
-    name: "callnames",
-    component: () =>
-      import(/* webpackChunkName:'CallNames'*/ "@/views/teacher/CallNames.vue"),
-    meta: {
-      descrition: "点名",
-      weight: 1 // 页面权重
-    }
-  },
-  {
-    path: "/attendance",
-    name: "attendance",
-    component: () =>
-      import(
-        /* webpackChunkName:'Attendance'*/ "@/views/teacher/Attendance.vue"
-      ),
-    meta: {
-      descrition: "历史考勤统计",
-      weight: 1 // 页面权重
-    }
-  },
-  {
-    path: "/bookingset",
-    name: "bookingset",
-    component: () =>
-      import(
-        /* webpackChunkName:'BookingSet'*/ "@/views/teacher/BookingSet.vue"
-      ),
-    meta: {
-      descrition: "试听课设置",
-      weight: 1 // 页面权重
-    }
-  },
-  {
-    path: "/teachingschool",
-    name: "teachingschool",
-    component: () =>
-      import(
-        /* webpackChunkName:'TeachingSchool'*/ "@/views/teacher/TeachingSchool.vue"
-      ),
-    meta: {
-      descrition: "教学点设置",
-      weight: 1 // 页面权重
-    }
-  },
-  {
-    path: "/teachingset",
-    name: "teachingset",
-    component: () =>
-      import(
-        /* webpackChunkName:'TeachingSet'*/ "@/views/teacher/TeachingSet.vue"
-      ),
-    meta: {
-      descrition: "教学点设置",
-      weight: 2 // 页面权重
-    }
-  },
-  {
-    path: "/business",
-    name: "business",
-    component: () =>
-      import(/* webpackChunkName:'Business'*/ "@/views/teacher/Business.vue"),
-    meta: {
-      descrition: "业务",
-      weight: 1 // 页面权重
-    }
-  },
-  {
-    path: "/approval",
-    name: "approval",
-    component: () =>
-      import(/* webpackChunkName:'Approval'*/ "@/views/teacher/Approval.vue"),
-    meta: {
-      descrition: "需我审批",
-      weight: 2 // 页面权重
-    }
-  },
-  {
-    path: "/ccme",
-    name: "ccme",
-    component: () =>
-      import(/* webpackChunkName:'CcMe'*/ "@/views/teacher/CcMe.vue"),
-    meta: {
-      descrition: "抄送我的",
-      weight: 3 // 页面权重
-    }
-  },
-  {
-    path: "/istarted",
-    name: "istarted",
-    component: () =>
-      import(/* webpackChunkName:'IStarted'*/ "@/views/teacher/IStarted.vue"),
-    meta: {
-      descrition: "我发起的",
-      weight: 4 // 页面权重
-    }
-  },
-  {
-    path: "/starteddetail",
-    name: "starteddetail",
-    component: () =>
-      import(
-        /* webpackChunkName:'StartedDetail'*/ "@/views/teacher/StartedDetail.vue"
-      ),
-    meta: {
-      descrition: "我发起的",
-      weight: 5 // 页面权重
-    }
-  },
-  {
-    path: "/periodadjust",
-    name: "periodadjust",
-    component: () =>
-      import(
-        /* webpackChunkName:'PeriodAdjust'*/ "@/views/teacher/PeriodAdjust.vue"
-      ),
-    meta: {
-      descrition: "课程调整",
-      weight: 6 // 页面权重
-    }
-  },
-  {
-    path: "/periodchange",
-    name: "periodchange",
-    component: () =>
-      import(
-        /* webpackChunkName:'PeriodChange'*/ "@/views/teacher/PeriodChange.vue"
-      ),
-    meta: {
-      descrition: "课时交换",
-      weight: 7 // 页面权重
-    }
-  },
-  {
-    path: "/leave",
-    name: "leave",
-    component: () =>
-      import(/* webpackChunkName:'Leave'*/ "@/views/teacher/Leave.vue"),
-    meta: {
-      descrition: "请假",
-      weight: 1 // 页面权重
-    }
-  },
-  {
-    path: "/vipapply",
-    name: "vipapply",
-    component: () =>
-      import(/* webpackChunkName:'VIPApply'*/ "@/views/teacher/VIPApply.vue"),
-    meta: {
-      descrition: "VIP课程班申请",
-      weight: 1 // 页面权重
-    }
-  },
-  {
-    path: "/order",
-    name: "order",
-    component: () =>
-      import(/* webpackChunkName:'order'*/ "@/views/teacher/order.vue"),
-    meta: {
-      descrition: "订单列表",
-      weight: 1 // 页面权重
-    }
-  },
-  {
-    path: "/privacy",
-    name: "privacy",
-    component: () =>
-      import(/* webpackChunkName: "privacy" */ "@/views/teacher/privacy"),
-    meta: {
-      descrition: "隐私协议",
-      weight: 8 // 页面权重
-    }
-  },
-  {
-    path: "/studyReport",
-    name: "studyReport",
-    component: () =>
-      import(/* webpackChunkName: "privacy" */ "@/views/teacher/studyReport"),
-    meta: {
-      descrition: "学习报告",
-      weight: 8 // 页面权重
-    }
-  },
-  {
-    path: "/studyReportNew",
-    name: "studyReportNew",
-    component: () =>
-      import(/* webpackChunkName: "privacy" */ "@/views/teacher/studyReportNew"),
-    meta: {
-      descrition: "学习报告",
-      weight: 8 // 页面权重
-    }
-  },
-  {
-    path: "/reportDetailNew",
-    name: "reportDetailNew",
-    component: () =>
-      import(/* webpackChunkName: "privacy" */ "@/views/teacher/reportDetailNew"),
-    meta: {
-      descrition: "学习报告",
-      weight: 8 // 页面权重
-    }
-  },
-  {
-    path: "/reportDetail",
-    name: "reportDetail",
-    component: () =>
-      import(/* webpackChunkName: "privacy" */ "@/views/teacher/reportDetail"),
-    meta: {
-      descrition: "学习报告",
-      weight: 8 // 页面权重
-    }
-  },
-  {
-    path: "/tobeReport",
-    name: "tobeReport",
-    component: () =>
-      import(/* webpackChunkName: "privacy" */ "@/views/teacher/tobeReport"),
-    meta: {
-      descrition: "待完成月报",
-      weight: 8 // 页面权重
-    }
-  },
-  {
-    path: "/visitList",
-    name: "visitList",
-    component: () =>
-      import(/* webpackChunkName: "visitList" */ "@/views/visitManager/visitList"),
-    meta: {
-      descrition: "回访记录",
-      weight: 8 // 页面权重
-    }
-  },
-  {
-    path: "/addVisit",
-    name: "addVisit",
-    component: () =>
-      import(/* webpackChunkName: "addVisit" */ "@/views/visitManager/addVisit"),
-    meta: {
-      descrition: "回访记录",
-      weight: 8 // 页面权重
-    }
-  }, {
-    path: "/visitDetail",
-    name: "visitDetail",
-    component: () =>
-      import(/* webpackChunkName: "visitDetail" */ "@/views/visitManager/visitDetail"),
-    meta: {
-      descrition: "回访记录",
-      weight: 8 // 页面权重
-    }
-  }, {
-    path: '/rules',
-    name: 'rules',
-    component: () => import(/* webpackChunkName:'UserProtocol'*/'@/views/rules/index.vue'),
-    meta: {
-        descrition: '签到签到课酬扣减规则',
-        weight: 3 // 页面权重
-    }
-  }, {
-    path: '/assistCenter',
-    name: 'assistCenter',
-    component: () => import(/* webpackChunkName:'assistCenter'*/'@/views/rules/assistCenter.vue'),
-    meta: {
-        descrition: '帮助中心',
-        weight: 3 // 页面权重
-    }
-  }, {
-    path: '/specialDetail',
-    name: 'specialDetail',
-    component: () => import(/* webpackChunkName:'specialDetail'*/'@/views/teacher/specialDetail.vue'),
-    meta: {
-        descrition: '系统通知',
-        weight: 3 // 页面权重
-    }
-  }, {
-    path: '/special',
-    name: 'special',
-    component: () => import(/* webpackChunkName:'special'*/'@/views/teacher/special.vue'),
-    meta: {
-        descrition: '系统通知列表',
-        weight: 3 // 页面权重
-    }
-  }, {
-    path: '/trainStatistics',
-    name: 'trainStatistics',
-    component: () => import(/* webpackChunkName:'trainStatistics'*/'@/views/trainStatistics/index.vue'),
-    meta: {
-        descrition: '训练统计',
-        weight: 3 // 页面权重
-    }
-  }, {
-    path: '/trainDetail',
-    name: 'trainDetail',
-    component: () => import(/* webpackChunkName:'trainDetail'*/'@/views/trainStatistics/trainDetail.vue'),
-    meta: {
-        descrition: '评测详情',
-        weight: 3 // 页面权重
-    }
-  }, {
-    path: '/activeProgram',
-    name: 'activeProgram',
-    component: () => import(/* webpackChunkName:'activeProgram'*/'@/views/activeProgram/index.vue'),
-    meta: {
-        descrition: '活动方案',
-        weight: 3 // 页面权重
-    }
-  }, {
-    path: '/activeDetail',
-    name: 'activeDetail',
-    component: () => import(/* webpackChunkName:'activeDetail'*/'@/views/activeProgram/activeDetail.vue'),
-    meta: {
-        descrition: '活动详情',
-        weight: 3 // 页面权重
-    }
-  }, {
-    path: '/program',
-    name: 'program',
-    component: () => import(/* webpackChunkName:'program'*/'@/views/activeProgram/program.vue'),
-    meta: {
-        descrition: '活动详情',
-        weight: 3 // 页面权重
-    }
-  }
-];
-
-export default teacherRouter;
+let teacherRouter = [
+  {
+    path: "/callnames",
+    name: "callnames",
+    component: () =>
+      import(/* webpackChunkName:'CallNames'*/ "@/views/teacher/CallNames.vue"),
+    meta: {
+      descrition: "点名",
+      weight: 1 // 页面权重
+    }
+  },
+  {
+    path: "/attendance",
+    name: "attendance",
+    component: () =>
+      import(
+        /* webpackChunkName:'Attendance'*/ "@/views/teacher/Attendance.vue"
+      ),
+    meta: {
+      descrition: "历史考勤统计",
+      weight: 1 // 页面权重
+    }
+  },
+  {
+    path: "/bookingset",
+    name: "bookingset",
+    component: () =>
+      import(
+        /* webpackChunkName:'BookingSet'*/ "@/views/teacher/BookingSet.vue"
+      ),
+    meta: {
+      descrition: "试听课设置",
+      weight: 1 // 页面权重
+    }
+  },
+  {
+    path: "/teachingschool",
+    name: "teachingschool",
+    component: () =>
+      import(
+        /* webpackChunkName:'TeachingSchool'*/ "@/views/teacher/TeachingSchool.vue"
+      ),
+    meta: {
+      descrition: "教学点设置",
+      weight: 1 // 页面权重
+    }
+  },
+  {
+    path: "/teachingset",
+    name: "teachingset",
+    component: () =>
+      import(
+        /* webpackChunkName:'TeachingSet'*/ "@/views/teacher/TeachingSet.vue"
+      ),
+    meta: {
+      descrition: "教学点设置",
+      weight: 2 // 页面权重
+    }
+  },
+  {
+    path: "/business",
+    name: "business",
+    component: () =>
+      import(/* webpackChunkName:'Business'*/ "@/views/teacher/Business.vue"),
+    meta: {
+      descrition: "业务",
+      weight: 1 // 页面权重
+    }
+  },
+  // {
+  //   path: "/approval",
+  //   name: "approval",
+  //   component: () =>
+  //     import(/* webpackChunkName:'Approval'*/ "@/views/teacher/Approval.vue"),
+  //   meta: {
+  //     descrition: "需我审批",
+  //     weight: 2 // 页面权重
+  //   }
+  // },
+  {
+    path: "/ccme",
+    name: "ccme",
+    component: () =>
+      import(/* webpackChunkName:'CcMe'*/ "@/views/teacher/CcMe.vue"),
+    meta: {
+      descrition: "抄送我的",
+      weight: 3 // 页面权重
+    }
+  },
+  {
+    path: "/istarted",
+    name: "istarted",
+    component: () =>
+      import(/* webpackChunkName:'IStarted'*/ "@/views/teacher/IStarted.vue"),
+    meta: {
+      descrition: "我发起的",
+      weight: 4 // 页面权重
+    }
+  },
+  {
+    path: "/starteddetail",
+    name: "starteddetail",
+    component: () =>
+      import(
+        /* webpackChunkName:'StartedDetail'*/ "@/views/teacher/StartedDetail.vue"
+      ),
+    meta: {
+      descrition: "我发起的",
+      weight: 5 // 页面权重
+    }
+  },
+  {
+    path: "/periodadjust",
+    name: "periodadjust",
+    component: () =>
+      import(
+        /* webpackChunkName:'PeriodAdjust'*/ "@/views/teacher/PeriodAdjust.vue"
+      ),
+    meta: {
+      descrition: "课程调整",
+      weight: 6 // 页面权重
+    }
+  },
+  {
+    path: "/periodchange",
+    name: "periodchange",
+    component: () =>
+      import(
+        /* webpackChunkName:'PeriodChange'*/ "@/views/teacher/PeriodChange.vue"
+      ),
+    meta: {
+      descrition: "课时交换",
+      weight: 7 // 页面权重
+    }
+  },
+  {
+    path: "/leave",
+    name: "leave",
+    component: () =>
+      import(/* webpackChunkName:'Leave'*/ "@/views/teacher/Leave.vue"),
+    meta: {
+      descrition: "请假",
+      weight: 1 // 页面权重
+    }
+  },
+  {
+    path: "/vipapply",
+    name: "vipapply",
+    component: () =>
+      import(/* webpackChunkName:'VIPApply'*/ "@/views/teacher/VIPApply.vue"),
+    meta: {
+      descrition: "VIP课程班申请",
+      weight: 1 // 页面权重
+    }
+  },
+  {
+    path: "/order",
+    name: "order",
+    component: () =>
+      import(/* webpackChunkName:'order'*/ "@/views/teacher/order.vue"),
+    meta: {
+      descrition: "订单列表",
+      weight: 1 // 页面权重
+    }
+  },
+  {
+    path: "/privacy",
+    name: "privacy",
+    component: () =>
+      import(/* webpackChunkName: "privacy" */ "@/views/teacher/privacy"),
+    meta: {
+      descrition: "隐私协议",
+      weight: 8 // 页面权重
+    }
+  },
+  {
+    path: "/studyReport",
+    name: "studyReport",
+    component: () =>
+      import(/* webpackChunkName: "privacy" */ "@/views/teacher/studyReport"),
+    meta: {
+      descrition: "学习报告",
+      weight: 8 // 页面权重
+    }
+  },
+  {
+    path: "/studyReportNew",
+    name: "studyReportNew",
+    component: () =>
+      import(/* webpackChunkName: "privacy" */ "@/views/teacher/studyReportNew"),
+    meta: {
+      descrition: "学习报告",
+      weight: 8 // 页面权重
+    }
+  },
+  {
+    path: "/reportDetailNew",
+    name: "reportDetailNew",
+    component: () =>
+      import(/* webpackChunkName: "privacy" */ "@/views/teacher/reportDetailNew"),
+    meta: {
+      descrition: "学习报告",
+      weight: 8 // 页面权重
+    }
+  },
+  {
+    path: "/reportDetail",
+    name: "reportDetail",
+    component: () =>
+      import(/* webpackChunkName: "privacy" */ "@/views/teacher/reportDetail"),
+    meta: {
+      descrition: "学习报告",
+      weight: 8 // 页面权重
+    }
+  },
+  {
+    path: "/tobeReport",
+    name: "tobeReport",
+    component: () =>
+      import(/* webpackChunkName: "privacy" */ "@/views/teacher/tobeReport"),
+    meta: {
+      descrition: "待完成月报",
+      weight: 8 // 页面权重
+    }
+  },
+  {
+    path: "/visitList",
+    name: "visitList",
+    component: () =>
+      import(/* webpackChunkName: "visitList" */ "@/views/visitManager/visitList"),
+    meta: {
+      descrition: "回访记录",
+      weight: 8 // 页面权重
+    }
+  },
+  {
+    path: "/addVisit",
+    name: "addVisit",
+    component: () =>
+      import(/* webpackChunkName: "addVisit" */ "@/views/visitManager/addVisit"),
+    meta: {
+      descrition: "回访记录",
+      weight: 8 // 页面权重
+    }
+  }, {
+    path: "/visitDetail",
+    name: "visitDetail",
+    component: () =>
+      import(/* webpackChunkName: "visitDetail" */ "@/views/visitManager/visitDetail"),
+    meta: {
+      descrition: "回访记录",
+      weight: 8 // 页面权重
+    }
+  }, {
+    path: '/rules',
+    name: 'rules',
+    component: () => import(/* webpackChunkName:'UserProtocol'*/'@/views/rules/index.vue'),
+    meta: {
+        descrition: '签到签到课酬扣减规则',
+        weight: 3 // 页面权重
+    }
+  }, {
+    path: '/assistCenter',
+    name: 'assistCenter',
+    component: () => import(/* webpackChunkName:'assistCenter'*/'@/views/rules/assistCenter.vue'),
+    meta: {
+        descrition: '帮助中心',
+        weight: 3 // 页面权重
+    }
+  }, {
+    path: '/specialDetail',
+    name: 'specialDetail',
+    component: () => import(/* webpackChunkName:'specialDetail'*/'@/views/teacher/specialDetail.vue'),
+    meta: {
+        descrition: '系统通知',
+        weight: 3 // 页面权重
+    }
+  }, {
+    path: '/special',
+    name: 'special',
+    component: () => import(/* webpackChunkName:'special'*/'@/views/teacher/special.vue'),
+    meta: {
+        descrition: '系统通知列表',
+        weight: 3 // 页面权重
+    }
+  }, {
+    path: '/trainStatistics',
+    name: 'trainStatistics',
+    component: () => import(/* webpackChunkName:'trainStatistics'*/'@/views/trainStatistics/index.vue'),
+    meta: {
+        descrition: '训练统计',
+        weight: 3 // 页面权重
+    }
+  }, {
+    path: '/trainDetail',
+    name: 'trainDetail',
+    component: () => import(/* webpackChunkName:'trainDetail'*/'@/views/trainStatistics/trainDetail.vue'),
+    meta: {
+        descrition: '评测详情',
+        weight: 3 // 页面权重
+    }
+  }, {
+    path: '/activeProgram',
+    name: 'activeProgram',
+    component: () => import(/* webpackChunkName:'activeProgram'*/'@/views/activeProgram/index.vue'),
+    meta: {
+        descrition: '活动方案',
+        weight: 3 // 页面权重
+    }
+  }, {
+    path: '/activeDetail',
+    name: 'activeDetail',
+    component: () => import(/* webpackChunkName:'activeDetail'*/'@/views/activeProgram/activeDetail.vue'),
+    meta: {
+        descrition: '活动详情',
+        weight: 3 // 页面权重
+    }
+  }, {
+    path: '/program',
+    name: 'program',
+    component: () => import(/* webpackChunkName:'program'*/'@/views/activeProgram/program.vue'),
+    meta: {
+        descrition: '活动详情',
+        weight: 3 // 页面权重
+    }
+  }
+];
+
+export default teacherRouter;

+ 78 - 79
src/views/app/HelpCenterDetail.vue

@@ -1,80 +1,79 @@
-<template>
-  <div class="KeepRepaireDetail">
-    <m-header background="white" v-if="headerStatus"> </m-header>
-    <h4 v-if="item && item.title">{{ item.title }}</h4>
-    <div class="concat" v-if="item && item.content" v-html="item.content"></div>
-    <van-image-preview
-      v-model="show"
-      :images="images"
-      :showIndex="false"
-    ></van-image-preview>
-  </div>
-</template>
-<script>
-import MHeader from "@/components/MHeader";
-import { browser } from "@/common/common";
-export default {
-  components: { MHeader },
-  data() {
-    return {
-      headerStatus: true,
-      item: null,
-      backUrl: {
-        callBack: () => {
-          if (browser().android) {
-            // eslint-disable-next-line
-            DAYA.postMessage(JSON.stringify({ api: "back" }));
-          } else if (browser().iPhone) {
-            window.webkit.messageHandlers.DAYA.postMessage(
-              JSON.stringify({ api: "back" })
-            );
-          }
-        },
-      },
-      images: [],
-      show: false,
-    };
-  },
-
-  mounted() {
-    document.title = "帮助中心";
-    if (browser().android || browser().iPhone) {
-      this.headerStatus = true;
-    }
-    let params = this.$route.query;
-    if (params.Authorization) {
-      localStorage.setItem("Authorization", decodeURI(params.Authorization));
-      localStorage.setItem("userInfo", decodeURI(params.Authorization));
-    }
-    this.item = JSON.parse(this.$route.query.item);
-    console.log(this.item);
-    window.showImg = (e) => {
-      if (e.src) {
-        this.images = [e.src];
-        this.show = true;
-      }
-    };
-  },
-};
-</script>
-<style lang="less">
-.KeepRepaireDetail {
-  background-color: #fff;
-  padding: 0.1rem;
-  h4 {
-    font-size: 0.18rem;
-    margin-bottom: 0.1rem;
-    font-weight: 500;
-  }
-  .concat {
-    font-size: 0.14rem;
-    color: #808080;
-    p {
-      margin-bottom: 0.1rem;
-    }
-    img {
-      vertical-align: middle;
-    }
-  }
-}
+<template>
+  <div class="KeepRepaireDetail">
+    <m-header background="white" v-if="headerStatus"> </m-header>
+    <h4 v-if="item && item.title">{{ item.title }}</h4>
+    <div class="concat" v-if="item && item.content" v-html="item.content"></div>
+    <van-image-preview
+      v-model="show"
+      :images="images"
+      :showIndex="false"
+    ></van-image-preview>
+  </div>
+</template>
+<script>
+import MHeader from "@/components/MHeader";
+import { browser } from "@/common/common";
+export default {
+  components: { MHeader },
+  data() {
+    return {
+      headerStatus: true,
+      item: null,
+      backUrl: {
+        callBack: () => {
+          if (browser().android) {
+            // eslint-disable-next-line
+            DAYA.postMessage(JSON.stringify({ api: "back" }));
+          } else if (browser().iPhone) {
+            window.webkit.messageHandlers.DAYA.postMessage(
+              JSON.stringify({ api: "back" })
+            );
+          }
+        },
+      },
+      images: [],
+      show: false,
+    };
+  },
+
+  mounted() {
+    document.title = "帮助中心";
+    if (browser().android || browser().iPhone) {
+      this.headerStatus = true;
+    }
+    let params = this.$route.query;
+    if (params.Authorization) {
+      localStorage.setItem("Authorization", decodeURI(params.Authorization));
+      localStorage.setItem("userInfo", decodeURI(params.Authorization));
+    }
+    this.item = JSON.parse(this.$route.query.item);
+    window.showImg = (e) => {
+      if (e.src) {
+        this.images = [e.src];
+        this.show = true;
+      }
+    };
+  },
+};
+</script>
+<style lang="less">
+.KeepRepaireDetail {
+  background-color: #fff;
+  padding: 0.1rem;
+  h4 {
+    font-size: 0.18rem;
+    margin-bottom: 0.1rem;
+    font-weight: 500;
+  }
+  .concat {
+    font-size: 0.14rem;
+    color: #808080;
+    p {
+      margin-bottom: 0.1rem;
+    }
+    img {
+      vertical-align: middle;
+    }
+  }
+}
 </style>

+ 0 - 1
src/views/audition/ArrangeWork.vue

@@ -258,7 +258,6 @@ export default {
       return val
     },
     onSelectMusic(value) {
-      console.log(value)
       this.tabActiveList.musicScoreIdList[this.tabActiveIndex] = {
         id: value.examSongId,
         name: value.name

+ 134 - 0
src/views/message/Approval.vue

@@ -0,0 +1,134 @@
+<template>
+    <div class="approval">
+        <van-sticky>
+            <!-- <m-header :isFixed="false" :isNative="false" :backUrl="backUrl" /> -->
+            <div class="user-container m-shadow">
+                <van-grid :column-num="3" :border="false">
+                    <van-grid-item text="需我审批" :badge="upcoming" @click="gotoMyApproval(1)">
+                        <template #icon><i class="icon icon1"></i></template>
+                    </van-grid-item>
+                    <van-grid-item text="我发起的" @click="gotoMyApproval(2)">
+                        <template #icon><i class="icon icon3"></i></template>
+                    </van-grid-item>
+                    <van-grid-item text="我相关的" @click="gotoMyApproval(3)">
+                        <template #icon><i class="icon icon2"></i></template>
+                    </van-grid-item>
+                </van-grid>
+            </div>
+        </van-sticky>
+
+        <apply-modal v-if="dataShow" :applyList="applyList" />
+        <m-empty v-else />
+    </div>
+</template>
+<script>
+import MHeader from '@/components/MHeader'
+import MEmpty from '@/components/MEmpty'
+import { classify, todoCount } from './api'
+import ApplyModal from './modal/applyModal'
+export default {
+    name: 'approval',
+    components: { MHeader, ApplyModal, MEmpty },
+    data() {
+        return {
+            applyStatus: false,
+            applyList: [],
+            dataShow: true,
+            upcoming: null,
+            backUrl: {
+                status: true,
+                path: '/home'
+            },
+        }
+    },
+    async mounted(){
+        document.title = 'OA审批'
+        try {
+            const res = await todoCount()
+            // 我的待审批数
+            const result = res.data
+            const upcoming = result > 0 ?result : null
+            this.upcoming = upcoming && upcoming > 99 ? '99+' : upcoming
+        } catch {
+            //
+        }
+        this.onApplyList()
+    },
+    methods: {
+        async onApplyList() {
+            try {
+                let res = await classify()
+                let tempList = res.data || []
+                tempList.forEach(e => {
+                    e.process_list2 = []
+                    // 判断是否有子分类
+                    if(e.process_list && e.process_list.length > 0) {
+                        e.process_list.forEach(child => {
+                            // 父级分类
+                            if(child.sub == 0) {
+                                e.process_list2.push(child)
+                            }
+                        })
+                    }
+                })
+                this.applyList = res.data
+                this.dataShow = res.data.length > 0 ? true : false
+            } catch {
+                //
+                this.dataShow = false
+            }
+            this.applyStatus = true
+        },
+        gotoMyApproval(val){
+            this.$router.push({path:"/myApproval",query:{classify:val}})
+        }
+    }
+}
+</script>
+<style lang='less' scoped>
+@import url("../../assets/commonLess/variable.less");
+/deep/.van-tab--active {
+    color: #EF5A50;
+}
+/deep/.van-tabs__line {
+    background-color: @mColor;
+}
+.approval {
+    min-height: 100vh;
+}
+.header_block {
+    // background: @mColor;
+}
+.user-container {
+    background: @whiteColor;
+    padding: .05rem .08rem;
+    // margin: 0 .16rem .1rem;
+    margin-bottom: .1rem;
+    color: @tFontColor;
+    font-size: .14rem;
+    // border-radius: .05rem;
+    .item {
+        flex: 1;
+        color: #4F4F4F;
+        font-size: .14rem;
+    }
+    .icon {
+        display: inline-block;
+        width: .34rem;
+        height: .35rem;
+    }
+    .icon1 {
+        background: url('./images/a_1.png') no-repeat center;
+        background-size: contain;
+    }
+    .icon2 {
+        background: url('./images/a_2.png') no-repeat center;
+        background-size: contain;
+    }
+    .icon3 {
+        background: url('./images/a_3.png') no-repeat center;
+        background-size: contain;
+    }
+}
+
+</style>

+ 1013 - 0
src/views/message/ApprovalAction.vue

@@ -0,0 +1,1013 @@
+<template>
+    <div :class="(userAuthority || ownerApply) && is_end == 0 && share != 1 ? 'approvalAction' : null">
+        <m-header v-if="share != 1" :isNative="false">
+            <template #right>
+                <van-icon @click="onShare" :name="iconShare" style="transform: translateY(5px);" size="22"></van-icon>
+            </template>
+        </m-header>
+        <!-- <van-notice-bar
+            v-if="activeIndex !== nodeStepList.length && is_end===1"
+            style="margin-bottom: .1rem"
+            :text="alertMessage"
+            :scrollable="false"
+        /> -->
+
+        <van-cell class="headerInfo" v-if="processStructureValue.workOrder.priority">
+            <template #title>
+                <div class="titleSection" v-if="title">
+                    <span class="titleContent">{{ title }}</span>
+                    <template v-if="processStructureValue.workOrder.priority===2">
+                        <van-tag size="small" style="background: #fff9f6;vertical-align: text-bottom;" plain type="warning">紧急</van-tag>
+                    </template>
+                    <template v-else-if="processStructureValue.workOrder.priority===3">
+                        <van-tag size="small" style="background: #fff6f7;vertical-align: text-bottom;" plain type="danger">非常紧急</van-tag>
+                    </template>
+                    <template v-else-if="processStructureValue.workOrder.priority===1">
+                        <van-tag size="small" style="background: #edfff6;vertical-align: text-bottom;" plain type="success">一般</van-tag>
+                    </template>
+                </div>
+                <template v-if="is_end == 0">
+                    <span v-if="userAuthority" class="waitStatus">等待我审批</span>
+                    <span v-else class="waitStatus">{{ principals }}</span>
+                </template>
+                <template v-else>
+                    <template v-if="is_cancel == 0">
+                        <span v-if="processStructureValue.workOrder.is_denied" class="waitStatus">审批拒绝</span>
+                        <span v-else class="waitStatus">审批通过</span>
+
+                        <img v-if="processStructureValue.workOrder.is_denied" class="imgStatus" src="./images/icon_reject.png" />
+                        <img v-else class="imgStatus" src="./images/icon_resolve.png" />
+                    </template>
+                    <template v-else>
+                        <span class="waitStatus">已关闭</span>
+                        <img class="imgStatus" src="./images/icon_close_round.png" />
+                    </template>
+                </template>
+            </template>
+        </van-cell>
+
+        <div style="margin: 0 .12rem .12rem;border-radius: .05rem;overflow: hidden;">
+            <template v-for="(tplItem, tplIndex) in processStructureValue.tpls">
+                <form-modal
+                    style="padding-bottom: .12rem; background: #fff;"
+                    :key="tplIndex"
+                    v-if="(currentNode.hideTpls!==undefined &&
+                        currentNode.hideTpls!==null &&
+                        currentNode.hideTpls.indexOf(tplItem.form_structure.id)!==-1) ||
+                        (currentNode.writeTpls===undefined ||
+                        currentNode.writeTpls===null ||
+                        currentNode.writeTpls.indexOf(tplItem.form_structure.id)===-1)"
+                    :formData="tplItem.form_structure"
+                    :value="tplItem.form_data"
+                    :preview="true" />
+            </template>
+        </div>
+        <template v-if="userAuthority">
+            <van-form ref="headForm" validate-first @submit="onSubmitHead" :scroll-to-error="true" :show-error="false">
+                <template v-for="(tplItem, tplIndex) in processStructureValue.tpls">
+                    <form-modal
+                        :key="tplIndex"
+                        v-if="(currentNode.writeTpls!==undefined &&
+                        currentNode.writeTpls!==null &&
+                        currentNode.writeTpls.indexOf(tplItem.form_structure.id)>-1)?true:false"
+                        :ref="'generateForm-'+tplItem.id"
+                        :value="tplItem.form_data"
+                        :formData="tplItem.form_structure" />
+                </template>
+            </van-form>
+        </template>
+
+        <div class="step_section" v-if="circulationHistoryList.length > 0">
+            <div class="step_title">流程</div>
+            <van-steps v-if="currentNode.clazz !== undefined && currentNode.clazz !== null && currentNode.clazz !== ''" :active="activeIndex"  direction="vertical" inactive-color="#D8D8D8" active-color="#D8D8D8">
+                <template v-for="(item, index) in circulationHistoryList">
+                    <van-step v-if="item.isHideNode === false ||
+                    item.isHideNode === undefined ||
+                    item.isHideNode == null ||
+                    item.id === processStructureValue.workOrder.current_state"
+                    :key="index" class="step">
+                        <template #inactive-icon>
+                            <i class="inactive-icon"></i>
+                        </template>
+                        <template #active-icon>
+                            <van-icon :name="iconWait" />
+                        </template>
+                        <template #finish-icon>
+                            <template v-if="item.circulation == '转交'">
+                                <van-icon :name="iconSystemTransfer" />
+                            </template>
+                            <template v-else>
+                                <van-icon :name="iconReject" v-if="item.status == 0" />
+                                <van-icon :name="iconComplete" v-else />
+                            </template>
+                        </template>
+                        <div class="step-t">
+                            <p class="apply-state">{{ item.state || item.label }}</p>
+                            <p class="apply-time">{{ item.create_time ? dayjs(item.create_time).format('MM-DD HH:mm') : null }}</p>
+                        </div>
+                        <!-- 判断是否有审核数据 -->
+                        <template v-if="!item.create_time">
+                            <p class="apply-status" v-if="item.assignUsers && item.assignUsers.length > 0">
+                                <!-- 判断是否是自己审批 -->
+                                <template v-if="item.assignUsers[0].userId == userInfo.userId && activeIndex == index">
+                                    我(审批中)
+                                </template>
+                                <template v-else>
+                                     <template v-if="item.isCounterSign">
+                                        <span v-for="(au, aIndex) in item.assignUsers" :key="aIndex">
+                                            {{ au.username }}{{ aIndex<(item.assignUsers.length-1) ? ',' : null }}
+                                        </span>
+                                    </template>
+                                    <template v-else>
+                                        {{ item.assignUsers[0].username }}
+                                    </template>
+                                    {{ activeIndex == index ? `(审批中)` : null }}
+                                    <!-- {{ item.assignUsers[0].username }}{{ activeIndex == index ? `(审批中)` : null }} -->
+                                </template>
+                            </p>
+                        </template>
+                        <template v-else>
+                            <p class="apply-status" v-if="item.processor">{{ item.processor }}{{ item.circulation ? `(${item.circulation})` : null }}</p>
+                            <p class="remarks" v-if="item.remarks">{{ item.remarks }}</p>
+                        </template>
+                        <!-- 有抄送人并且,本节点已经审批完成了 -->
+                        <template v-if="item.cc_user && item.cc_user.length > 0 && activeIndex > index">
+                            <!-- 已抄送1人 -->
+                            <div class="ccUsers" @click="onCCChange(item)">
+                                <span>已抄送{{ item.cc_user.length }}人</span>
+                                <van-icon v-show="!item.ccStatus" style="color: #CCCCCC" name="arrow-down" />
+                                <van-icon v-show="item.ccStatus" style="color: #CCCCCC" name="arrow-up" />
+                            </div>
+                            <div class="ccUserDetail" v-if="item.ccStatus">
+                                <span>{{ item.cc_user.join(',') }}</span>
+                            </div>
+                        </template>
+                    </van-step>
+                </template>
+            </van-steps>
+        </div>
+
+        <!-- 是自己审批 或者 是自己提交的记录 并且不是分享连接 -->
+        <van-goods-action v-if="(userAuthority || ownerApply) && is_end == 0 && share != 1">
+            <van-goods-action-icon v-if="is_end == 0 && ownerApply" :icon="iconDing" text="催办" @click="handleUrge" />
+            <van-goods-action-icon v-if="is_end == 0 && ownerApply" :icon="iconClose" text="关闭" @click="handleUnity" />
+            <van-goods-action-icon v-if="is_end == 0 && userAuthority" :icon="iconTransfer" text="转交" @click="exchange" />
+            <template v-if="userAuthority">
+                <van-goods-action-button
+                    v-for="(item, index) in btn_group"
+                    :key="index"
+                    :type="item.className"
+                    :disabled="submitDisabled"
+                    :text="item.labelShow"
+                    plain
+                    @click="onCheckSubmit(item)"
+                />
+                <!-- 拒绝按钮内置 -->
+                <van-goods-action-button
+                    v-if="endNodeDetail.id"
+                    :disabled="submitDisabled"
+                    plain
+                    :text="endNodeDetail.label" type="danger"
+                     @click="onCheckSubmit(endNodeDetail)" />
+            </template>
+        </van-goods-action>
+
+        <!-- 审批时填写意见 -->
+        <van-popup v-model="showMarks" closeable class="markModel">
+            <div class="popup-marks">
+                <h2>确认{{ popupMarkTxt }}</h2>
+                <van-field
+                    name='remarks'
+                    v-model="remarks"
+                    type="textarea"
+                    row="5"
+                    :autosize=" { maxHeight: 300, minHeight: 180 }"
+                    :placeholder="'请输入审批意见'"
+                />
+                <van-button type="info" round @click="popupSubmit">确认{{ popupMarkTxt }}</van-button>
+            </div>
+        </van-popup>
+
+        <!-- 转交 -->
+        <van-popup v-model="transferStatus" position="bottom" :style="{ height: '100%', width: '100%' }">
+            <m-header name="转交" :backUrl="backUrl2" />
+            <transfer-modal v-if="transferStatus" :popupForm="popupForm" :columns="columns" :nodeList="nodeList" />
+        </van-popup>
+    </div>
+</template>
+<script>
+import MHeader from '@/components/MHeader'
+import { processStructure, handleWorkOrder, sysUserList, urgeWorkOrder, getInfo, unityWorkOrder, asyncPlayLog } from './api'
+import FormModal from './modal/formModal'
+import TransferModal from './modal/transferModal'
+import copy from "copy-to-clipboard";
+import dayjs from 'dayjs'
+export default {
+    name: 'approvalAction',
+    components: { MHeader, FormModal, TransferModal },
+    data() {
+        let query = this.$route.query
+        return {
+            iconComplete: require('./images/system-complete-default.png'),
+            iconWait: require('./images/system-wait.png'),
+            iconReject: require('./images/system-reject.png'),
+            iconSystemTransfer: require('./images/system-transfer.png'),
+            iconTransfer: require('./images/icon_transfer.png'),
+            iconDing: require('./images/icon_ding.png'),
+            iconClose: require('./images/icon_close.png'),
+            iconShare: require('./images/icon_share.png'),
+            showMarks: false,
+            popupMarkTxt: null,
+            backUrl2: {
+                callBack: () => {
+                    this.transferStatus = false
+                }
+            },
+            dayjs,
+            active: 0,
+            // classify: query.classify,
+            processId: query.processId,
+            workOrderId: query.workOrderId,
+            principals: null,
+            share: query.share,
+            title: null,
+            submitDisabled: false,
+            ruleForm: {
+                title: '',
+                priority: 1,
+                process: '',
+                classify: '',
+                state: [],
+                source: '',
+                source_state: '',
+                process_method: '',
+                tpls: {
+                    'form_structure': [],
+                    'form_data': []
+                },
+                tasks: []
+            },
+            currentNode: {
+                hideTpls: null,
+                writeTpls: null
+            },
+            isActiveProcessing: false,
+            tpls: [],
+            remarks: '', // 备注信息
+            alertMessage: '',
+            nodeStepList: [],
+            circulationHistoryList: [],
+            endNodeDetail: {}, // 结束结节信息
+            activeIndex: 0,
+            processStructureValue: {
+                workOrder: { title: '' }
+            },
+            selectItem: null,
+            btn_group: [],
+            transferStatus: false,
+            columns: [],
+            popupForm: {
+                work_order_id: '',
+                node_id: '',
+                user_id: '',
+                remarks: ''
+            },
+            userInfo: {},
+            userAuthority: false, // 是否是自己审批
+            ownerApply: false, // 是否是自己提交的申请
+            is_end: 0, // 是否结束
+            is_cancel: 0, // 是否取消
+        }
+    },
+    async mounted() {
+        await this.getProcessNodeList()
+        if(this.share != 1) {
+            await this.getUserInfo()
+        }
+    },
+    methods: {
+        onShare() {
+            const url = window.location.href + '&share=1'
+            copy(url);
+            copy(url);
+            this.$toast('分享连接复制成功')
+        },
+        // 获取对应元素的值
+        getFormDataDetail(formData, model) {
+            let modelStatus = {
+                status: false,
+                value: null
+            }
+            for(let data in formData) {
+                if(typeof formData[data] == 'object') {
+                    // 没有子表单里面有子表单
+                    for(let child in formData[data]) {
+                        if(child == model) {
+                            modelStatus = {
+                                status: true,
+                                model: child,
+                                value: formData[data][child] ? formData[data][child].split(',') : []
+                            }
+                        }
+                    }
+                } else {
+                    if(data == model) {
+                        modelStatus = {
+                            status: true,
+                            model: data,
+                            value: formData[data] ? formData[data].split(',') : []
+                        }
+                    }
+                }
+            }
+            return modelStatus
+        },
+        getSelectValueObject(tpls, type = 'value', tplValues = []) {
+            const tempData = tpls || []
+            let selectList = []
+            tempData.forEach((temp, index) => {
+                let tempList = temp.form_structure.list || []
+                let tempSelectList = tplValues[index] || []
+                let listArray = []
+                tempList.forEach(list => {
+                    if(list.type == 'select') {
+                        if(type == 'value') {
+                            const result = this.getFormDataDetail(temp.form_data, list.model)
+                            if(result.status) {
+                                listArray.push(result)
+                            }
+                        } else {
+                            let selectOptions = []
+                            let selectValue = []
+                            tempSelectList.forEach(tsl => {
+                                if(tsl.model == list.model) {
+                                    selectOptions = list.options?.options || []
+                                    selectValue = tsl.value || []
+                                }
+                            })
+                            selectOptions.forEach(so => {
+                              if(selectValue.includes(so.value)) {
+                                  let tempRo = so.relationOptions || []
+                                  listArray.push(...tempRo)
+                              }
+                            })
+                        }
+                    }
+                    if(list.type == 'subform') {
+                        let childList = list.columns || []
+                        childList.forEach(child => {
+                            let childList = child.list || []
+                            childList.forEach(c => {
+                                if(c.type == 'select') {
+                                    if(type == 'value') {
+                                        const originObj = JSON.parse(JSON.stringify(c))
+                                        const result = this.getFormDataDetail(temp.form_data, originObj.model)
+                                        if(result.status) {
+                                            listArray.push(result)
+                                        }
+                                    } else {
+                                        let selectOptions = []
+                                        let selectValue = []
+                                        tempSelectList.forEach(tsl => {
+                                            if(tsl.model == c.model) {
+                                                selectOptions = c.options?.options || []
+                                                selectValue = tsl.value || []
+                                            }
+                                        })
+                                        selectOptions.forEach(so => {
+                                            if(selectValue.includes(so.value)) {
+                                                let tempRo = so.relationOptions || []
+                                                listArray.push(...tempRo)
+                                            }
+                                        })
+                                    }
+                                }
+                            })
+                        });
+                    }
+                })
+                selectList.push(listArray)
+            })
+            return selectList
+        },
+        async getProcessNodeList() {
+            try {
+                let params = {
+                    processId: this.processId,
+                    workOrderId: this.workOrderId
+                }
+                const userId = sessionStorage.getItem('userId')
+                if(this.share != 1 && userId) {
+                    params.userId = userId
+                }
+                let res = await processStructure(params)
+                this.isActiveProcessing = false
+                let tempData = res.data.tpls
+                // 获取对应模板中,下拉框的key, value
+                let selectList = this.getSelectValueObject(tempData)
+
+                // 获取对应模板中,需要隐藏的字段
+                let hiddenFormList = this.getSelectValueObject(tempData, 'hiddenForm', selectList)
+
+                tempData.forEach((temp, index) => {
+                    let tempList = temp.form_structure.list || []
+                    tempList.forEach(item => {
+                        if(hiddenFormList[index].length > 0) {
+                            if(item.type != 'text' && !item.options.relationStatus) {
+                                item.hidden = true
+                            } else {
+                                item.hidden = false
+                            }
+                            // item.hidden = false
+                            if(hiddenFormList[index].includes(item.model)) {
+                                item.hidden = false
+                            }
+                        } else {
+                            item.hidden = false
+                        }
+                        // 子表单
+                        if(item.type == 'subform') {
+                            let childList = item.columns || []
+                            let subFormStatus = true
+                            childList.forEach(child => {
+                                let childList = child.list || []
+                                childList.forEach(c => {
+                                    if(hiddenFormList[index].length > 0) {
+                                        if(c.type != 'text' && !c.options.relationStatus) {
+                                            c.hidden = true
+                                        } else {
+                                            c.hidden = false
+                                            subFormStatus = false
+                                        }
+                                        if(hiddenFormList[index].includes(c.model)) {
+                                            c.hidden = false
+                                            subFormStatus = false
+                                        }
+                                    } else {
+                                        c.hidden = false
+                                        subFormStatus = false
+                                    }
+                                })
+                            });
+                            item.hidden = subFormStatus
+                        }
+                    })
+                })
+                this.processStructureValue = res.data
+                this.circulationHistoryList = this.processStructureValue.circulationHistory
+                this.userAuthority = this.processStructureValue.userAuthority
+                this.is_end = res.data.workOrder.is_end
+                this.is_cancel = res.data.workOrder.is_cancel
+                if(res.data.workOrder.title) {
+                    this.title = res.data.workOrder.title
+                }
+
+                // 获取当前展示节点列表
+                this.nodeStepList = []
+                this.tempNodeStepList = []
+                const nodes = this.processStructureValue.nodes
+                this.principals = '处理中'
+                for (var i = 0; i < nodes.length; i++) {
+                    if (nodes[i].id === this.processStructureValue.workOrder.current_state) {
+                        // 当前节点
+                        this.nodeStepList.push(nodes[i])
+                        this.activeIndex = this.nodeStepList.length - 1
+                        if (i + 1 === nodes.length) {
+                            this.activeIndex = this.nodeStepList.length
+                        }
+                        this.currentNode = nodes[i]
+
+                        // 处理是认谁在处理,已处理完成则显示处理中
+                        const assignUsers = nodes[i].assignUsers
+                        if(assignUsers && assignUsers.length > 0) {
+                            this.principals = assignUsers[0].username + '处理中'
+                        }
+                    } else if (!nodes[i].isHideNode) {
+                        // 非隐藏节点
+                        this.nodeStepList.push(nodes[i])
+                    }
+
+                    // 判断节点里面是否有结束节点,而且一个流程里面只能有一个结束结点,
+                    if(nodes[i].clazz == 'end') {
+                        this.endNodeDetail = JSON.parse(JSON.stringify(nodes[i]))
+                        this.endNodeDetail.target = nodes[i].id
+                        this.endNodeDetail.flowProperties = 0 // 拒绝属性
+                        this.endNodeDetail.label = '拒绝'
+                    }
+                }
+                this.circulationHistoryList.reverse()
+                // 如果审批流程没有结束则,流程和历史记录合并显示;结束了,就只显示历史记录
+                if (!this.is_end) {
+                    this.circulationHistoryList.forEach(cir => {
+                        this.nodeStepList.forEach(node => {
+                            if(cir.source == node.id) {
+                                cir.label = node.label
+                                cir.assignType = node.assignType
+                                cir.assignValue = node.assignValue
+                                cir.assignUsers = node.assignUsers
+                                cir.id = node.id
+                            }
+                        })
+                    })
+
+                    let tempNodes = []
+                    this.nodeStepList.forEach(node => {
+                        let count = 0
+                        this.circulationHistoryList.forEach(cir => {
+                            if(node.id === cir.source) {
+                                count += 1
+                            }
+                        })
+                        if(count <= 0) {
+                            tempNodes.push(node)
+                        }
+                    })
+                    this.circulationHistoryList.push(...tempNodes)
+                    this.circulationHistoryList.forEach((cir, index) => {
+                        if(cir.id == this.processStructureValue.workOrder.current_state) {
+                            this.activeIndex = index
+                            if(index + 1 == this.circulationHistoryList.length) {
+                                this.activeIndex = this.circulationHistoryList.length
+                            }
+                        }
+                    })
+                } else {
+                    this.activeIndex = this.circulationHistoryList.length
+                }
+                // 添加抄送状态
+                this.circulationHistoryList.forEach((res) => {
+                    res.ccStatus = true
+                })
+
+
+                // 如果回退到初始节点则可编辑。
+                if (this.activeIndex === 0 && this.currentNode.clazz === 'start') {
+                    this.currentNode.writeTpls = []
+                    for (var tplTmp of this.processStructureValue.tpls) {
+                        this.currentNode.writeTpls.push(tplTmp.form_structure.id)
+                    }
+                }
+
+                // 判断是否需要主动处理
+                for (var stateValue of this.processStructureValue.workOrder.state) {
+                    if (this.processStructureValue.workOrder.current_state === stateValue.id && stateValue.processor.length > 1) {
+                        this.isActiveProcessing = true
+                        break
+                    }
+                }
+
+                 let psv = res.data?.edges || []
+                 let btn_group = []
+                psv.forEach(item => {
+                    // console.log(this.currentNode)
+                    // 过滤其它类型的操作
+                    if(this.is_end===0 && item.source===this.currentNode.id && item.flowProperties == 1) {
+                        if(item.flowProperties == 1) {
+                            item.className = 'info'
+                            item.labelShow = '同意'
+                        } else if(item.flowProperties == 0) {
+                            item.className = 'danger'
+                        } else if(item.flowProperties == 2) {
+                            item.className = 'info'
+                        }
+                        btn_group.push(item)
+                    } else {
+                        item.className = 'info'
+                    }
+                })
+                this.btn_group = btn_group
+                // console.log(this.endNodeDetail, btn_group , 'btn_group')
+
+                this.getAlertMessage()
+            } catch {
+                //
+            }
+        },
+        relationFormChange(value) {
+            let temp = value || []
+            this.formData.list?.forEach(item => {
+                item.hidden = false
+                if(temp.includes(item.model)) {
+                    item.hidden = true
+                }
+                // 子表单
+                if(item.type == 'subform') {
+                    let childList = item.columns || []
+                    childList.forEach(child => {
+                        if(child.list?.length > 0) {
+                            child.list.forEach(c => {
+                                c.hidden = false
+                                if(temp.includes(c.originModel)) {
+                                    c.hidden = true
+                                }
+                            })
+                        }
+                    });
+                    // 重置数据
+                    let subForm = this.$refs.subform
+                    subForm.forEach(item => {
+                        item.reSetFormData()
+                    })
+                }
+            })
+        },
+        async getUserInfo() {
+            // 获取用户信息
+            try {
+                let user = await getInfo()
+                this.userInfo = user.data
+
+                this.ownerApply = this.processStructureValue.workOrder.creator == this.userInfo.userId ? true : false
+            } catch {
+                //
+            }
+        },
+        // 获取提示消息
+        getAlertMessage() {
+            if (this.is_end === 1) {
+                this.alertMessage = '当前工单已结束'
+            }
+        },
+        // 转交
+        async exchange() {
+            let workOrder = this.processStructureValue.workOrder
+            this.popupForm.work_order_id = workOrder.id
+            this.nodeList = workOrder.state || []
+            this.nodeList.forEach(item => {
+                item.text = item.label
+            })
+            if (this.nodeList.length === 1) {
+                this.popupForm.node_id = this.nodeList[0].id
+            }
+            try {
+                // 获取所有用户
+                const res = await sysUserList({ pageSize: 9999 })
+                const users = res.data.list || []
+                users.forEach(item => {
+                    this.columns.push({
+                        avatar: item.avatar,
+                        text: item.nickName,
+                        phone: item.phone,
+                        id: item.userId
+                    })
+                })
+            } catch {
+                //
+            }
+            this.transferStatus = true
+        },
+        // 催办
+        handleUrge() {
+            this.$dialog.confirm({
+                title: '催办',
+                allowHtml : true,
+                message: '<span style="font-size:15px ">对此工单处理人进行催办通知提醒, 是否继续?</span><br><span style="color: #c33; font-size: 10px">注意:十分钟内只能催办一次。</span>',
+                confirmButtonColor: '#01C1B5'
+            }).then(async () => {
+                try {
+                    let workOrder = this.processStructureValue.workOrder
+                    let workOrderId = workOrder.id
+                    await urgeWorkOrder({ workOrderId })
+                    this.$toast('已进行催办通知')
+                } catch {
+                    //
+                }
+            })
+        },
+        handleUnity() {
+            // 关闭
+            this.$dialog.confirm({
+                title: '提示',
+                allowHtml : true,
+                message: '<span style="font-size:15px ">此操作将会关闭该工单, 是否继续?</span>',
+                confirmButtonColor: '#01C1B5'
+            }).then(async () => {
+                try {
+                    let workOrder = this.processStructureValue.workOrder
+                    let work_oroder_id = workOrder.id
+                    await unityWorkOrder({ work_oroder_id })
+                    this.$toast('已关闭工单')
+                    setTimeout(() => {
+                        this.$router.push({
+                            path: '/myApproval',
+                            query: {
+                                classify: 2
+                            }
+                        })
+                    }, 500)
+                } catch {
+                    //
+                }
+            })
+        },
+        onCheckSubmit(item) {
+            this.selectItem = item
+            this.$refs.headForm.validate().then(() => {
+                this.showMarks = true
+                this.popupMarkTxt = item.label
+            })
+        },
+        popupSubmit() {
+            this.$refs.headForm.submit()
+            this.showMarks = false
+            this.popupMarkTxt = null
+        },
+        onCCChange(item) {
+            item.ccStatus = !item.ccStatus
+            this.$forceUpdate()
+        },
+        async onSubmitHead(values) {
+            // remarks
+            let { ...res } = values
+            let promiseList = []
+            this.tpls = []
+            for (let tpl of this.processStructureValue.tpls) {
+                this.tpls.push({
+                    tplDataId: tpl.id,
+                    tplId: tpl.form_structure.id
+                })
+                // 说明是当前用户需要填写的列表数据
+                if((this.currentNode.writeTpls!==undefined &&
+                    this.currentNode.writeTpls!==null &&
+                    this.currentNode.writeTpls.indexOf(tpl.form_structure.id)>-1)) {
+                    let fd = this.formatData(res)
+                    promiseList.push(fd)
+                } else {
+                    // 已存在的表单数据
+                    promiseList.push(tpl.form_data)
+                }
+            }
+
+            for (let tplDataIndex in this.tpls) {
+                this.tpls[tplDataIndex].tplValue = promiseList[tplDataIndex]
+            }
+
+            let params = {
+                tasks: this.processStructureValue.process.task,
+                source_state: this.processStructureValue.workOrder.current_state,
+                target_state: this.selectItem.target,
+                circulation: this.selectItem.label,
+                flow_properties: this.selectItem.flowProperties === undefined ? 2 : parseInt(this.selectItem.flowProperties),
+                work_order_id: parseInt(this.workOrderId),
+                remarks: this.remarks,
+                tpls: this.tpls
+            }
+
+            let fileList = []
+            this.tpls && this.tpls.forEach(tpl => {
+                for(let val in tpl.tplValue) {
+                    if(val.indexOf('file') != -1) {
+                        const file = tpl.tplValue[val] || []
+                        file.forEach(item => {
+                            fileList.push(item.url)
+                        })
+                    }
+                }
+            })
+            try {
+                // const fileUrl = ''
+                // console.log(params)
+                // return
+                // 确认提交
+                await handleWorkOrder(params)
+
+                await asyncPlayLog({ workOrderId: parseInt(this.workOrderId), fileUrl: fileList.join(',')})
+                this.submitDisabled = false
+                // workOrderId
+                this.getProcessNodeList()
+            } catch {
+                //
+                this.submitDisabled = false
+            }
+        },
+        formatData(json) {
+            let tempJson = {}
+            let subFormList = {}
+            for(let v in json) {
+                let nameList = v.split('.')
+                // 只要大于1说明是子表单
+                if(nameList.length > 1) {
+                    nameList.push(v)
+                    let delName = nameList.shift()
+                    if(subFormList[delName]) {
+                        subFormList[delName].push(nameList)
+                    } else {
+                        subFormList[delName] = []
+                        subFormList[delName].push(nameList)
+                    }
+                } else {
+                    tempJson[v] = json[v]
+                }
+            }
+
+            for(let sub in subFormList) {
+                let subList = subFormList[sub] || []
+                let subDown = []
+                subList.forEach(item => {
+                    if(subDown[item[1]]) {
+                        subDown[item[1]][item[0]] = json[item[2]]
+                    } else {
+                        subDown[item[1]] = {}
+                        subDown[item[1]][item[0]] = json[item[2]]
+                    }
+                })
+                tempJson[sub] = subDown
+            }
+            return tempJson
+        },
+        getHideTplData(tpl) {
+            // 獲取隱藏模板數據
+            let list = tpl.list
+            let data = {}
+            list.forEach(item => {
+                if(item.type == 'subform') {
+                    if(item.columns.length > 0) {
+                        data[item.model] = []
+                        let arr = []
+                        item.columns?.forEach(col => {
+                            if(col.list?.length > 0) {
+                                arr.push(...col.list)
+                            }
+                        })
+                        let tempSub = {}
+                        arr.forEach(a => {
+                            tempSub[a.model] = a.options.defaultValue
+                        })
+                        data[item.model].push(tempSub)
+                    } else {
+                        data[item.model] = ''
+                    }
+                } else {
+                    data[item.model] = item.options.defaultValue
+                }
+            })
+            return data
+        }
+    }
+}
+</script>
+<style lang='less' scoped>
+@import url("../../assets/commonLess/variable.less");
+.approvalAction {
+    margin-bottom: 85px;
+    padding-bottom: env(safe-area-inset-bottom)
+}
+
+/deep/.van-goods-action {
+    box-shadow: 0 4px 10px 0 #cecece;
+    z-index: 99;
+    justify-content: space-around;
+    height: 70px;
+    .van-goods-action-icon__icon {
+        font-size: 20px;
+        min-width: 55px;
+    }
+    .van-goods-action-button--last {
+        margin-right: .12rem;
+    }
+    .van-button--danger {
+        background: #fff;
+        border: 1px solid #01C1B5;
+        color: #01C1B5;
+    }
+}
+
+.style {
+    margin-bottom: .12rem;
+    padding: 12px 16px;
+    line-height: 1.3;
+    font-size: 16px;
+    &.info {
+        display: flex;
+        flex-direction: column;
+    }
+    /deep/.van-field__label {
+        margin-bottom: 7px;
+        width: auto;
+        color: #111F2C;
+    }
+    /deep/.van-field__value {
+        padding: .03rem 0;
+    }
+}
+
+.headerInfo {
+    margin-bottom: 0.1rem;
+    padding: 0.18rem 0.12rem;
+    overflow: inherit;
+    /deep/.van-cell__title {
+        flex-direction: column;
+        align-items: flex-start;
+    }
+}
+.titleSection {
+    .titleContent {
+        font-size: 0.18rem;
+        color: #333333;
+        padding-right: 0.08rem;
+    }
+}
+.waitStatus {
+    color: #F39001;
+    font-size: .14rem;
+    display: inline-block;
+    padding-top: .06rem;
+}
+
+.imgStatus {
+    bottom: -35%;
+    width: 0.75rem;
+    height: 0.75rem;
+    position: absolute;
+    z-index: 98;
+    right: .2rem;
+}
+
+.markModel {
+    width: 90%;
+    border-radius: .08rem;
+    padding: .2rem 0;
+}
+.popup-marks {
+    // width: 95%;
+    h2 {
+        font-size: .16rem;
+        text-align: center;
+    }
+    /deep/.van-cell {
+        font-size: .14rem;
+    }
+    /deep/.van-button {
+        width: 90%;
+        margin-left: 5%;
+        font-size: .16rem;
+    }
+}
+
+.step_section {
+    margin: 0px 0.12rem 0.12rem;
+    border-radius: .1rem;
+    overflow: hidden;
+    .step_title {
+        background: #fff;
+        padding: .1rem 0.16rem;
+        color: #333333;
+        font-size: .18rem;
+        font-weight: 500;
+    }
+    /deep/.van-steps--vertical {
+        padding-left: .45rem;
+        .van-step__circle-container, .van-step__line {
+            left: -22px;
+        }
+    }
+    .step {
+        font-size: .13rem;
+        .step-t {
+            display: flex;
+            align-content: center;
+            justify-content: space-between;
+            font-size: .16rem;
+            color: #444;
+        }
+        .inactive-icon {
+            width: .1rem;
+            height: .1rem;
+            background: #D8D8D8;
+            display: inline-block;
+            border-radius: 1rem;
+        }
+        .apply-state {
+            line-height: 1.5;
+            flex-basis: 73%;
+        }
+        .apply-time {
+            font-size: .14rem;
+            color: #999999;
+            flex-basis: 27%;
+        }
+        .apply-status {
+            padding: .05rem 0;
+            color: #999;
+        }
+        .remarks {
+            background: #f5f5f5;
+            padding: .08rem;
+            color: #323233;
+        }
+        .van-icon {
+            font-size: .17rem;
+        }
+    }
+    .ccUsers {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        color: #444;
+        padding-top: .08rem;
+    }
+    .ccUserDetail {
+        color: #444;
+    }
+}
+</style>

+ 393 - 0
src/views/message/ApprovalApply.vue

@@ -0,0 +1,393 @@
+<template>
+    <div :class="[btn_group && btn_group.length > 0 ? 'approvalApply' : null]">
+        <!-- <m-header :name="name" :isNative="false" /> -->
+
+        <van-form ref="headForm" validate-first @submit="onSubmitHead" @failed="onFailed" validate-trigger="onSubmit" :scroll-to-error="false" :show-error="false">
+            <van-field
+                clearable
+                name='deptName'
+                v-show="deptList.length > 1"
+                class="style"
+                required
+                v-model="deptName"
+                placeholder="请选择申请部门"
+                readonly
+                @click="showSelect = true"
+                :rules="[{ required: true, message: '请选择申请部门' }]"
+            >
+                <template #label>申请部门
+                    <span style="color: red" v-if="!socialId">
+                        (未设置社保部门)
+                    </span>
+                    <template v-else>
+                        <span style="color: red" v-if="socialId != ruleForm.deptId">
+                            (该部门非社保部门)
+                        </span>
+                    </template>
+                </template>
+                <template #right-icon>
+                    <i class="van-icon van-icon-arrow van-cell__right-icon"></i>
+                </template>
+            </van-field>
+            <van-field
+                :label="'优先级'"
+                clearable
+                name='priority'
+                class="style"
+                required
+                :rules="[{ required: true, message: '请选择工单优先级' }]"
+            >
+                <template #input>
+                    <van-radio-group v-model="ruleForm.priority" direction="horizontal">
+                        <van-radio :name="1">一般</van-radio>
+                        <van-radio :name="2">紧急</van-radio>
+                        <van-radio :name="3">非常紧急</van-radio>
+                    </van-radio-group>
+                </template>
+            </van-field>
+            <template v-for="(tplItem, tplIndex) in processStructureValue.tpls">
+                <form-modal
+                    :key="tplIndex"
+                    v-if="currentNode.hideTpls === undefined || currentNode.hideTpls === null || currentNode.hideTpls.indexOf(tplItem.id) === -1"
+                    :ref="'generateForm-'+tplItem.id"
+                    :formData="tplItem.form_structure"
+                    :disabled="currentNode.readonlyTpls===undefined || currentNode.readonlyTpls===null || currentNode.readonlyTpls.indexOf(tplItem.id)===-1?null:true" />
+            </template>
+        </van-form>
+        <!-- 是自己审批 或者 是自己提交的记录 -->
+        <van-goods-action v-if="btn_group && btn_group.length > 0" style="padding: 0 4%;">
+            <van-goods-action-button
+                v-for="(item, index) in btn_group"
+                :key="index"
+                :type="item.className"
+                :disabled="submitDisabled"
+                text="提交"
+                plain
+                @click="onCheckSubmit(item)"
+            />
+        </van-goods-action>
+
+        <van-popup v-model="showSelect" position="bottom">
+            <van-picker
+                show-toolbar
+                :columns="deptList"
+                @confirm="onConfirm"
+                visible-item-count="4"
+                @cancel="showSelect = false"
+            />
+        </van-popup>
+    </div>
+</template>
+<script>
+import MHeader from '@/components/MHeader'
+import { processStructure, createWorkOrder, checkCourseReturnFee } from './api'
+import FormModal from './modal/formModal'
+// import { launchInfo, approvalCancel } from '@/api/message'
+export default {
+    name: 'approvalApply',
+    components: { MHeader, FormModal },
+    data() {
+        const query = this.$route.query
+        return {
+            active: 0,
+            processId: query.processId,
+            name: query.name,
+            submitDisabled: false,
+            showSelect: false,
+            deptName: null, // 部门名称
+            socialId: null, // 是否是社保分部
+            ruleForm: {
+                deptId: null, // 部门编号
+                priority: 1,
+                process: '',
+                classify: '',
+                state: [],
+                source: '',
+                source_state: '',
+                process_method: '',
+                tpls: {
+                    'form_structure': [],
+                    'form_data': []
+                },
+                tasks: []
+            },
+            currentNode: {},
+            processStructureValue: {},
+            tempProcessStructureValue: {},
+            selectTarget: null,
+            btn_group: [],
+            deptList: [], // 部门列表
+        }
+    },
+    async mounted() {
+        document.title = this.name
+        this.getProcessNodeList()
+    },
+    methods: {
+        async getProcessNodeList() {
+            try {
+                const userId = sessionStorage.getItem('userId')
+                let params = {
+                    processId: this.processId
+                }
+                if(userId) {
+                    params.userId = userId
+                }
+                const res = await processStructure(params)
+                this.processStructureValue = res.data || {}
+                this.tempProcessStructureValue = JSON.parse(JSON.stringify(res.data || {}))
+                this.processStructureValue.nodes.forEach((node, index) => {
+                    if(node.clazz == 'start') {
+                        this.active = index
+                        this.currentNode = node
+                    }
+                })
+
+                this.deptList = res.data.depts || []
+                const defaultDept = res.data.deptId
+                this.socialId = defaultDept
+                this.deptList.forEach((item, index) => {
+                    if(defaultDept) {
+                        if(item.deptId == defaultDept) {
+                            this.deptName = item.deptName
+                            this.ruleForm.deptId = item.deptId
+                        }
+                    } else {
+                        if(index == 0) {
+                            this.deptName = item.deptName
+                            this.ruleForm.deptId = item.deptId
+                        }
+                    }
+                    item.text = item.deptName
+                })
+                let psv = res.data?.edges || []
+                let btn_group = []
+                psv.forEach(item => {
+                    if(item.source===this.currentNode.id && item.flowProperties == 1) {
+                        if(item.flowProperties == 1) {
+                            item.className = 'info'
+                        } else if(item.flowProperties == 0) {
+                            item.className = 'danger'
+                        } else if(item.flowProperties == 2) {
+                            item.className = 'info'
+                        }
+                        btn_group.push(item)
+                    } else {
+                        item.className = 'info'
+                    }
+                })
+                this.btn_group = btn_group
+
+                if(!this.socialId && this.deptList.length <= 0) {
+                    this.$dialog.alert({
+                        message: '您当前暂未设置所属部门,请联系管理员',
+                        confirmButtonColor: '#01C1B5'
+                    })
+                }
+            } catch {
+                //
+            }
+        },
+        onConfirm(value) {
+            this.deptName = value.deptName
+            this.ruleForm.deptId = value.deptId
+            this.showSelect = false
+        },
+        onCheckSubmit(item) {
+            if(!this.socialId && this.deptList.length <= 0 || !this.deptName) {
+                this.$dialog.alert({
+                    message: '您当前暂未设置所属部门,请联系管理员',
+                    confirmButtonColor: '#01C1B5'
+                })
+                return
+            }
+            this.selectTarget = item.target
+            this.$refs.headForm.submit()
+        },
+        onFailed() {
+            // console.log(errorInfo)
+        },
+        async onSubmitHead(values) {
+            // eslint-disable-next-line no-unused-vars
+            let { priority, deptName, ...res } = values
+            const psv = this.tempProcessStructureValue
+            this.submitDisabled = true
+            let ruleForm = this.ruleForm
+            ruleForm.priority = priority
+            let stateMap = {}
+            ruleForm.process = parseInt(this.processId)
+            ruleForm.classify = psv.process.classify
+            stateMap['id'] = this.selectTarget
+            ruleForm.source_state = psv.nodes[this.active].label
+            for (let v of psv.nodes) {
+                if (v.id === this.selectTarget) {
+                    if (v.assignType !== undefined) {
+                        stateMap['process_method'] = v.assignType
+                    }
+                    if (v.assignValue !== undefined) {
+                        stateMap['processor'] = Array.from(new Set(v.assignValue))
+                    }
+                    stateMap['label'] = v.label
+                    break
+                }
+            }
+            ruleForm.state = [stateMap]
+
+            ruleForm.tpls = {
+                'form_structure': [],
+                'form_data': []
+            }
+            // 绑定流程任务
+            ruleForm.tasks = psv.process.task === undefined ? [] : psv.process.task
+            // 追加节点任务
+            if (psv.nodes[this.active].task !== undefined && psv.nodes[this.active].task.length > 0) {
+                for (var task of psv.nodes[this.active].task) {
+                    if (ruleForm.tasks.indexOf(task) === -1) {
+                        ruleForm.tasks.push(task)
+                    }
+                }
+            }
+            // 不能一個流程用多个模板
+            for (let tpl of psv.tpls) {
+                tpl.form_structure.id = tpl.id
+                ruleForm.tpls.form_structure.push(tpl.form_structure)
+                if(this.currentNode.hideTpls === undefined || this.currentNode.hideTpls === null || this.currentNode.hideTpls.indexOf(tpl.id) === -1) {
+                    let fd = this.formatData(res)
+                    ruleForm.tpls.form_data.push(fd)
+                } else {
+                    let hideFd = this.getHideTplData(tpl.form_structure)
+                    ruleForm.tpls.form_data.push(hideFd)
+                }
+            }
+            ruleForm.source = psv.nodes[this.active].id
+            try {
+                // console.log(ruleForm)
+                const tplInfo = this.processStructureValue.tpls[0] // 默认只用第一个模板
+                const formData = ruleForm?.tpls?.form_data[0]
+                await checkCourseReturnFee({ tplInfoId: tplInfo.id, formData }) // 做校验
+                await createWorkOrder(ruleForm)
+                this.submitDisabled = false
+                this.$toast('申请成功')
+                setTimeout(() => {
+                    this.$router.push({
+                        path: '/myApproval',
+                        query: {
+                            classify: 2
+                        }
+                    })
+                }, 500)
+            } catch {
+                //
+                this.submitDisabled = false
+            }
+        },
+        formatData(json) {
+            let tempJson = {}
+            let subFormList = {}
+            for(let v in json) {
+                let nameList = v.split('.')
+                // 只要大于1说明是子表单
+                if(nameList.length > 1) {
+                    nameList.push(v)
+                    let delName = nameList.shift()
+                    if(subFormList[delName]) {
+                        subFormList[delName].push(nameList)
+                    } else {
+                        subFormList[delName] = []
+                        subFormList[delName].push(nameList)
+                    }
+                } else {
+                    tempJson[v] = json[v]
+                }
+            }
+
+            for(let sub in subFormList) {
+                let subList = subFormList[sub] || []
+                let subDown = []
+                subList.forEach(item => {
+                    if(subDown[item[1]]) {
+                        subDown[item[1]][item[0]] = json[item[2]]
+                    } else {
+                        subDown[item[1]] = {}
+                        subDown[item[1]][item[0]] = json[item[2]]
+                    }
+                })
+                tempJson[sub] = subDown
+            }
+            return tempJson
+        },
+        getHideTplData(tpl) {
+            // 獲取隱藏模板數據
+            let list = tpl.list
+            let data = {}
+            list.forEach(item => {
+                if(item.type == 'subform') {
+                    if(item.columns.length > 0) {
+                        data[item.model] = []
+                        let arr = []
+                        item.columns?.forEach(col => {
+                            if(col.list?.length > 0) {
+                                arr.push(...col.list)
+                            }
+                        })
+                        let tempSub = {}
+                        arr.forEach(a => {
+                            tempSub[a.model] = a.options.defaultValue
+                        })
+                        data[item.model].push(tempSub)
+                    } else {
+                        data[item.model] = ''
+                    }
+                } else {
+                    data[item.model] = item.options.defaultValue
+                }
+            })
+            return data
+        }
+    }
+}
+</script>
+<style lang='less' scoped>
+@import url("../../assets/commonLess/variable.less");
+.approvalApply {
+    margin-bottom: 85px;
+}
+
+/deep/.van-goods-action {
+    box-shadow: 0 4px 10px 0 #cecece;
+    z-index: 99;
+    justify-content: space-around;
+    height: 70px;
+    .van-goods-action-icon__icon {
+        font-size: 20px;
+        min-width: 55px;
+    }
+    .van-goods-action-button--last {
+        margin-right: .12rem;
+    }
+    .van-button--danger {
+        background: #fff;
+        border: 1px solid #01C1B5;
+        color: #01C1B5;
+    }
+}
+
+
+.style {
+    margin-bottom: .12rem;
+    padding: 12px 16px;
+    line-height: 1.3;
+    display: flex;
+    flex-direction: column;
+    font-size: 16px;
+    /deep/.van-field__label {
+        margin-bottom: 7px;
+        width: auto;
+        color: #111F2C;
+        font-size: 14px;
+    }
+    /deep/.van-field__value {
+        padding: .03rem 0;
+    }
+}
+</style>

+ 194 - 0
src/views/message/api.js

@@ -0,0 +1,194 @@
+import request from '@/helpers/request'
+const axios = require('@/common/axios').default
+// 上传图片或文件
+// export function uploadFile(data) {
+//     return request({
+//         url: '/api-web/uploadFile',
+//         method: 'post',
+//         noClean: true,
+//         data
+//     })
+// }
+
+
+// 上传文件
+export const uploadFile = (data) => {
+    return axios({
+        url: '/api-web/uploadFile',
+        method: 'post',
+        data: data
+    })
+}
+
+
+// 上传文件
+export const oaUploadFile = (data) => {
+    return axios({
+        url: '/api-web/import/oaUploadFile',
+        method: 'post',
+        data: data
+    })
+}
+
+// 获取流程列表
+export function classify(data) {
+    return request({
+        baseURL: "/api-oa",
+        url: '/api/v1/process/classify',
+        method: 'get',
+        params: data
+    })
+}
+
+// 获取我得待审批列表
+export function getWorkOrderList(data) {
+    return request({
+        baseURL: "/api-oa",
+        url: '/api/v1/work-order/list',
+        method: 'get',
+        hideLoading: true,
+        params: data
+    })
+}
+
+export function processStructure(data) {
+    return request({
+        baseURL: "/api-oa",
+        url: '/api/v1/work-order/process-structure',
+        method: 'get',
+        params: data
+    })
+}
+
+// 新建工单
+export function createWorkOrder(data) {
+    return request({
+        baseURL: "/api-oa",
+        noClean: true,
+        url: '/api/v1/work-order/create',
+        method: 'post',
+        data
+    })
+}
+
+// 处理工单
+export function handleWorkOrder(data) {
+    return request({
+        baseURL: "/api-oa",
+        noClean: true,
+        url: '/api/v1/work-order/handle',
+        method: 'post',
+        data
+    })
+}
+
+// 获取学校
+export function getOrganCooperation(data) {
+    return request({
+        url: '/eduOrganization/getOrganCooperation',
+        method: 'get',
+        params: data
+    })
+}
+
+export function sysUserList(data) {
+    return request({
+        baseURL: "/api-oa",
+        noClean: true,
+        url: '/api/v1/sysUserList',
+        method: 'get',
+        params: data
+    })
+}
+
+// 转交工单
+export function inversionWorkOrder(data) {
+    return request({
+        baseURL: "/api-oa",
+        url: '/api/v1/work-order/inversion',
+        method: 'post',
+        data
+    })
+}
+
+// 催办工单
+export function urgeWorkOrder(params) {
+    return request({
+        baseURL: '/api-oa',
+        url: '/api/v1/work-order/urge',
+        method: 'get',
+        params
+    })
+}
+
+export function getInfo() {
+    return request({
+        baseURL: '/api-oa',
+        url: '/api/v1/getinfo',
+        method: 'get'
+    })
+}
+
+export function dashboard() {
+    return request({
+        baseURL: '/api-oa',
+        url: '/api/v1/dashboard',
+        method: 'get'
+    })
+}
+
+export function todoCount() {
+    return request({
+        baseURL: '/api-oa',
+        url: '/api/v1/dashboard/todoCount',
+        method: 'get'
+    })
+}
+
+
+
+// 结束工单
+export function unityWorkOrder(params) {
+    return request({
+        baseURL: '/api-oa',
+        url: '/api/v1/work-order/unity',
+        method: 'get',
+        params
+    })
+}
+
+
+// 全部分部
+export function queryAllOrgan(params) {
+    return request({
+        url: '/eduOrganization/queryAllOrgan',
+        method: 'get',
+        params
+    })
+}
+
+//
+export function asyncPlayLog(data) {
+    return request({
+        url: '/oa/syncPayLog',
+        method: 'post',
+        requestType: 'form',
+        data
+    })
+}
+
+export function checkCourseReturnFee(data) {
+    return request({
+        url: '/oa/checkCourseReturnFee',
+        method: 'post',
+        data
+    })
+}
+
+// /sysMessage/queryCountOfUnread
+export function queryCountOfUnread() {
+    return request({
+        url: '/sysEduMessage/queryCountOfUnread',
+        method: 'get'
+    })
+}

+ 134 - 0
src/views/message/control/cascader.vue

@@ -0,0 +1,134 @@
+<template>
+    <div class="oCascader controller" :class="[preview ? '' : 'o-unit']" v-if="widget.type == 'cascader'">
+        <van-field
+            :name="widget.model"
+            v-model="values"
+            :label="widget.name || '级联选择'"
+            :required="fileCheck ? false : options.required || false"
+            :disabled="options.disabled || false"
+            clearable
+            readonly
+            :placeholder="options.placeholder || '请选择'"
+            :rules="rule"
+            @click="onClick"
+            v-if="!preview"
+        >
+            <template #right-icon>
+                <!-- <input type="hidden" v-model="dataModel"> -->
+                <i class="van-icon van-icon-arrow van-cell__right-icon"></i>
+            </template>
+        </van-field>
+
+        <van-cell v-else class="preview" :title="widget.name || '级联选择'" :value="previewValue"></van-cell>
+
+        <van-popup v-model="showCascader" position="bottom">
+            <!-- <van-picker
+                show-toolbar
+                :columns="columns"
+                value-key="label"
+                @confirm="onConfirm"
+                visible-item-count="4"
+                @cancel="showCascader = false"
+            /> -->
+            <van-cascader
+                :title="options.placeholder || '请选择'"
+                :options="columns"
+                active-color="#01C1B5"
+                @close="showCascader = false"
+                @finish="onConfirm"
+            />
+        </van-popup>
+    </div>
+</template>
+<script>
+
+export default {
+    name: 'oCascader',
+    props: ['widget', 'preview', 'value', 'fileCheck'],
+    data() {
+        return {
+            dataModel: this.widget.options?.defaultValue || [],
+            values: null,
+            showCascader: false,
+            columns: this.widget.options?.options || [],
+            previewValue: null,
+        }
+    },
+    mounted() {
+        // 初始化参数
+        const options = this.widget.options?.options || []
+        this.columns = this.filterOptions(options)
+        if(this.value) {
+            const widget = this.widget
+            const model = widget.originModel || widget.model
+            for(let v in this.value) {
+                if(v == model) {
+                    this.previewValue = this.value[v]
+                }
+            }
+        }
+    },
+    methods: {
+        filterOptions(options) {
+            let tempList = []
+            options.forEach(item => {
+                if(item.children && item.children.length > 0) {
+                    tempList.push({
+                        text: item.label,
+                        value: item.value,
+                        children: this.filterOptions(item.children)
+                    })
+                } else {
+                    tempList.push({
+                        text: item.label,
+                        value: item.value
+                    })
+                }
+            })
+            return tempList
+        },
+        onClick() {
+            this.showCascader = true
+        },
+        onConfirm({ selectedOptions  }) {
+            this.dataModel = selectedOptions.map((option) => option.text).join('/')
+            // console.log(selectedOptions)
+            this.values = selectedOptions.map((option) => option.text).join('/')
+            this.showCascader = false
+        },
+    },
+    computed: {
+        options() {
+            return this.widget.options || {}
+        },
+        rule() {
+            let rules = this.widget.rules || []
+            if(rules && rules.length > 0) {
+                rules.forEach(item => {
+                    if(item.pattern) {
+                        item.pattern = eval(item.pattern)
+                    }
+                    // 判断是否上传文件
+                    if(this.fileCheck) {
+                        item.required = false
+                    }
+                });
+            }
+            return rules
+        }
+    },
+    watch: {
+        // dataModel: {
+        //     deep: true,
+        //     handler(newValue) {
+        //         if (newValue !== undefined && newValue !== null) {
+        //         }
+        //     }
+        // },
+    }
+}
+</script>
+
+<style lang='less' scoped>
+@import url('./controlCommon.less');
+</style>

+ 98 - 0
src/views/message/control/checkbox.vue

@@ -0,0 +1,98 @@
+<template>
+    <div class="oCheckbox controller" :class="[preview ? '' : 'o-unit']" v-if="widget.type == 'checkbox'">
+        <van-field
+            :name="widget.model"
+            :label="widget.name || '复选框'"
+            :required="fileCheck ? false : options.required || false"
+            :disabled="options.disabled || false"
+            clearable
+            :rules="rule"
+            v-if="!preview"
+        >
+            <template #input>
+                <van-checkbox-group v-model="dataModel" :direction="options.inline ? 'horizontal' : 'vertical'">
+                    <van-checkbox shape="square" :name="item.value" v-for="item in options.options" :key="item.value">{{ item.value }}</van-checkbox>
+                </van-checkbox-group>
+            </template>
+        </van-field>
+        <van-field
+            v-else
+            :name="widget.model"
+            :label="widget.name || '复选框'"
+            class="preview"
+        >
+            <template #input>
+                <van-checkbox-group disabled v-model="dataModel" :direction="options.inline ? 'horizontal' : 'vertical'">
+                    <van-checkbox shape="square" :name="item.value" v-for="item in options.options" :key="item.value">{{ item.value }}</van-checkbox>
+                </van-checkbox-group>
+            </template>
+        </van-field>
+    </div>
+</template>
+<script>
+
+export default {
+    name: 'oCheckbox',
+    props: ['widget', 'preview', 'value', 'fileCheck'],
+    data() {
+        return {
+            dataModel: this.widget.options?.defaultValue || null,
+        }
+    },
+    mounted() {
+        // 初始化参数
+        if(this.value) {
+            const widget = this.widget
+            const model = widget.originModel || widget.model
+            for(let v in this.value) {
+                if(v == model) {
+                    this.dataModel = this.value[v]
+                }
+            }
+        }
+    },
+    methods: {
+    },
+    computed: {
+        options() {
+            return this.widget.options || {}
+        },
+        rule() {
+            let rules = this.widget.rules || []
+            if(rules && rules.length > 0) {
+                rules.forEach(item => {
+                    if(item.pattern) {
+                        item.pattern = eval(item.pattern)
+                    }
+                    // 判断是否上传文件
+                    if(this.fileCheck) {
+                        item.required = false
+                    }
+                });
+            }
+            return rules
+        }
+    },
+    watch: {
+        // dataModel: {
+        //     deep: true,
+        //     handler(newValue) {
+        //         if (newValue !== undefined && newValue !== null) {
+        //         }
+        //     }
+        // },
+    }
+}
+</script>
+
+<style lang='less' scoped>
+@import url('./controlCommon.less');
+.oCheckbox {
+    /deep/.van-checkbox  {
+        padding: .03rem 0;
+    }
+    /deep/.van-checkbox--horizontal {
+        margin-right: .15rem;
+    }
+}
+</style>

+ 40 - 0
src/views/message/control/controlCommon.less

@@ -0,0 +1,40 @@
+.controller {
+    .preview {
+        padding: 12px 16px 5px;
+        line-height: 1.3;
+        display: flex;
+        flex-direction: column;
+        font-size: 14px;
+        /deep/.van-cell__title {
+            margin-bottom: 7px;
+            width: auto;
+            color: #808080;
+        }
+        /deep/.van-cell__value {
+            text-align: left;
+            color: #1a1a1a;
+            font-size: 16px;
+        }
+    }
+
+    /deep/.van-field {
+        padding: 12px 16px;
+        line-height: 1.3;
+        display: flex;
+        flex-direction: column;
+        font-size: 16px;
+    }
+    /deep/.van-field__label {
+        margin-bottom: 7px;
+        width: auto;
+        color: #111F2C;
+        font-size: 14px;
+    }
+    /deep/.van-field__value {
+        padding: .03rem 0;
+    }
+}
+
+.o-unit {
+    margin-bottom: 12px;
+}

+ 133 - 0
src/views/message/control/date.vue

@@ -0,0 +1,133 @@
+<template>
+    <div class="oDate controller" :class="[preview ? '' : 'o-unit']" v-if="widget.type == 'date'">
+        <van-field
+            :name="widget.model"
+            v-model="dataModel"
+            :label="widget.name || '文本框'"
+            :required="fileCheck ? false : options.required || false"
+            :disabled="options.disabled || false"
+            readonly
+            clearable
+            :placeholder="options.placeholder || '请输入'"
+            :clickable="true"
+            :rules="rule"
+            @click="onClick"
+            v-if="!preview"
+        >
+            <template #right-icon>
+                <i class="van-icon van-icon-arrow van-cell__right-icon"></i>
+            </template>
+        </van-field>
+
+        <van-cell class="preview" :title="widget.name || '文本框'" :value="dataModel" v-else></van-cell>
+
+        <van-popup v-model="showDate" position="bottom">
+            <van-datetime-picker
+                v-model="currentDate"
+                :type="dateType"
+                :formatter="formatter"
+                @confirm="onConfirm"
+                @cancel="onCancel"
+            />
+        </van-popup>
+    </div>
+</template>
+<script>
+import dayjs from 'dayjs'
+export default {
+    name: 'oDate',
+    props: ['widget', 'preview', 'value', 'fileCheck'],
+    data() {
+        return {
+            dataModel: this.widget.options?.defaultValue || null,
+            showDate: false,
+            currentDate: new Date(),
+            dateType: 'date'
+        }
+    },
+    mounted() {
+        // 初始化参数
+        if(this.options?.type == 'dates' || this.options?.type == 'daterange') {
+            this.dateType = 'date'
+        } else if(this.options?.type == 'datetimerange') {
+            this.dateType = 'datetime'
+        } else {
+            this.dateType = this.options.type
+        }
+
+        if(this.value) {
+            const widget = this.widget
+            const model = widget.originModel || widget.model
+            for(let v in this.value) {
+                if(v == model) {
+                    this.dataModel = this.value[v]
+                }
+            }
+        }
+    },
+    methods: {
+        onClick() {
+            this.showDate = true
+        },
+        onConfirm(val) {
+            this.dataModel = dayjs(val).format('YYYY-MM-DD')
+            this.showDate = false
+        },
+        onCancel() {
+            this.showDate = false
+        },
+        formatter(type, val) {
+            if (type === 'year') {
+                return val + '年';
+            }
+            if (type === 'month') {
+                return val + '月';
+            }
+            if (type === 'day') {
+                return val + '日';
+            }
+            if(type == 'hour') {
+                return val + '时'
+            }
+            if(type == 'minute') {
+                return val + '分'
+            }
+            return val;
+        },
+    },
+    computed: {
+        options() {
+            return this.widget.options || {}
+        },
+        rule() {
+            let rules = this.widget.rules || []
+            if(rules && rules.length > 0) {
+                rules.forEach(item => {
+                    if(item.pattern) {
+                        item.pattern = eval(item.pattern)
+                    }
+                    // 判断是否上传文件
+                    if(this.fileCheck) {
+                        item.required = false
+                    }
+                });
+            }
+            return rules
+        }
+    },
+    watch: {
+        // dataModel: {
+        //     deep: true,
+        //     handler(newValue) {
+        //         if (newValue !== undefined && newValue !== null) {
+        //         }
+        //     }
+        // },
+    }
+}
+</script>
+
+<style lang='less' scoped>
+@import url('./controlCommon.less');
+
+</style>

+ 40 - 0
src/views/message/control/divider.vue

@@ -0,0 +1,40 @@
+<template>
+    <div class="oDivider" :class="[preview ? '' : 'o-unit']" v-if="widget.type == 'divider'">
+        <van-divider
+            :style="{ borderColor: '#c0c0c0', padding: '0 16px', margin: 0 }"
+            v-if="!preview"
+            :content-position="options.content_position">{{ dataModel }}</van-divider>
+        <van-divider
+            :style="{ borderColor: '#c0c0c0', margin: 0 }"
+            v-else
+            :content-position="options.content_position">{{ dataModel }}</van-divider>
+    </div>
+</template>
+<script>
+
+export default {
+    name: 'oDivider',
+    props: ['widget', 'preview'],
+    data() {
+        return {
+            dataModel: this.widget.options?.defaultValue || null,
+        }
+    },
+    mounted() {
+        // 初始化参数
+    },
+    methods: {
+    },
+    computed: {
+        options() {
+            return this.widget.options || {}
+        },
+    }
+}
+</script>
+
+<style lang='less' scoped>
+.o-unit {
+    margin-bottom: 12px;
+}
+</style>

+ 324 - 0
src/views/message/control/fileUpload.vue

@@ -0,0 +1,324 @@
+<template>
+    <div class="oFileUpload controller" :class="[preview ? '' : 'o-unit']" v-if="widget.type == 'file'">
+        <van-field
+            :name="widget.model"
+            :label="widget.name || '附件'"
+            :required="fileCheck ? false : options.required || false"
+            :rules="rule"
+            v-if="!preview"
+        >
+            <template #input>
+                <!-- image/*, *.xlsx, *.xls, application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document, *.txt, *.pdf -->
+                <van-uploader
+                    v-model="dataModel"
+                    :before-read="beforeRead"
+                    :before-delete="beforeDelete"
+                    :after-read="afterRead"
+                    :disabled="options.disabled || false"
+                    :max-count="options.length"
+                    accept="image/*, *.xlsx, *.xls,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document, *.txt, *.pdf"
+                    preview-size="0.6rem">
+                    <!-- <template v-slot:preview-cover="props">
+                        {{ props }}
+                    </template> -->
+                    <!-- <template #preview-cover>
+                        2323
+                    </template> -->
+                </van-uploader>
+            </template>
+        </van-field>
+        <van-field
+            v-else
+            :name="widget.model"
+            :label="widget.name || '附件'"
+            class="preview"
+        >
+            <template #input>
+                <div class="preview_file" v-for="(item, index) in dataModel" :key="index">
+                    <div class="preview_item">
+                        <i class="van-icon van-icon-description van-uploader__file-icon"></i>
+                        <span style="white-space: nowrap;overflow: hidden;text-overflow: ellipsis;width: 2rem;">{{ item.name || item.url }}</span>
+                    </div>
+                    <div class="preview_btn">
+                        <van-button @click="downLoadFile2(item.url)" type="info" size="mini">下载</van-button>
+                        <van-button :disabled="!checkFileSuffix(item.url)" v-if="checkFileSuffix(item.url)" @click="downLoadFile(item.url)" type="info" size="mini">预览</van-button>
+                    </div>
+                </div>
+                <!-- <div class="van-uploader__preview" v-for="(item, index) in dataModel" :key="index">
+                    <div class="van-uploader__file" style="width: 0.6rem; height: 0.6rem;" >
+                        <i class="van-icon van-icon-description van-uploader__file-icon"></i>
+                        <div class="van-uploader__file-name van-ellipsis">
+                            {{ item.name || item.url }}
+                        </div>
+                    </div>
+                    <div style="text-align: center">
+                        <van-button @click="downLoadFile2(item.url)" type="info" size="mini">下载</van-button>
+                        <van-button :disabled="!checkFileSuffix(item.url)" @click="downLoadFile(item.url)" type="info" size="mini">预览</van-button>
+                    </div>
+                </div> -->
+            </template>
+        </van-field>
+
+        <van-popup position="bottom" v-model="filePreview" style="height: 100%;border-radius: 0;">
+            <van-sticky>
+                <m-header :backUrl="backUrl" :isFixed="false" name="预览" />
+            </van-sticky>
+            <div id="previewIframe" style="height: calc(100vh - 0.44rem);" v-if="filePreview && (fileType == 'xls' || fileType == 'pdf')">
+            </div>
+            <div style="height: calc(100vh - 0.44rem);" v-if="filePreview && fileType == 'doc'">
+            </div>
+            <!-- <div ref="pdf" style="height: calc(100vh - 0.44rem);" v-if="filePreview && fileType == 'pdf'">
+            </div> -->
+        </van-popup>
+    </div>
+</template>
+<script>
+import MHeader from '@/components/MHeader'
+import setLoading from '@/common/loading'
+import { uploadFile, oaUploadFile } from '@/views/message/api'
+import { browser } from '@/common/common'
+import { postMessage } from '@/helpers/native-message'
+export default {
+    name: 'oFileUpload',
+    components: { MHeader },
+    props: ['widget', 'preview', 'value', 'fileCheck'],
+    data() {
+        // xls, doc, pdf
+        return {
+            dataModel: this.widget.options?.defaultValue || null,
+            filePreview: false,
+            fileType: 'xls',
+            previewUrl: '',
+            numPages: 1,
+            backUrl: {
+                status: true,
+                callBack: () => {
+                    this.filePreview = false;
+                },
+            },
+        }
+    },
+    mounted() {
+        // 初始化参数
+        if(this.value) {
+            const widget = this.widget
+            const model = widget.originModel || widget.model
+            for(let v in this.value) {
+                if(v == model) {
+                    this.dataModel = this.value[v]
+                }
+            }
+        }
+    },
+    methods: {
+        checkFileSuffix(url) {
+            let urlArr = url.split('.')
+            let suffix = urlArr[urlArr.length-1]
+            //  || suffix == 'doc' || suffix == 'docx'
+            if(suffix == 'xlsx' || suffix == 'xls' || suffix == 'pdf') {
+                return true
+            } else {
+                return false
+            }
+        },
+        getFileSuffix(url) {
+            let urlArr = url.split('.')
+            let suffix = urlArr[urlArr.length-1]
+            if(suffix == 'xlsx' || suffix == 'xls') {
+                return 'xls'
+            } else if(suffix == 'doc' || suffix == 'docx') {
+                return 'doc'
+            } else if(suffix == 'pdf') {
+                return 'pdf'
+            } else {
+                return ''
+            }
+        },
+        beforeRead(file) {
+            const isLt2M = file.size / 1024 / 1024 < 5
+            if (!isLt2M) {
+                this.$toast('上传文件大小不能超过 5MB')
+                return false
+            }
+            return true
+        },
+        beforeDelete(file, detail) {
+            this.dataModel.splice(detail.index, 1)
+            if(!this.dataModel || this.dataModel && this.dataModel.length <= 0) {
+                this.$emit('fileCheckRequired', false)
+            }
+            return true
+        },
+        async afterRead(file, detail) { // 上传头像
+            try {
+                setLoading(true)
+                file.status = 'uploading'
+                file.message = '上传中...'
+                let formData = new FormData()
+                formData.append('file', file.file)
+                let res = null
+                const query = this.$route.query
+                if(query.processId) {
+                    formData.append('processId', query.processId)
+                    res = await oaUploadFile(formData)
+                } else {
+                    res = await uploadFile(formData)
+                }
+                let result = res.data
+                if(result.code == 200) {
+                    file.status = 'done'
+                    this.dataModel[detail.index] = {
+                        url: result.data.url,
+                        name: file.file.name,
+                        file: {
+                            name: file.file.name
+                        }
+                    }
+                    // 目前只对 退费模板做处理(id: 19)
+                    if(query.processId == 19) {
+                        this.$emit('fileCheckRequired', true)
+                    }
+                } else {
+                    file.status = 'failed'
+                    file.message = '上传失败'
+                    this.$toast(result.msg)
+                    this.dataModel.splice(detail.index, 1)
+                    return false
+                }
+                setLoading(false)
+            } catch (err) {
+                setLoading(false)
+                file.status = 'failed'
+                file.message = '上传失败'
+                this.dataModel.splice(detail.index, 1)
+                return false
+            }
+        },
+        downLoadFile2(file) {
+            this.$toast.loading({
+                duration: 0, // 持续展示 toast
+                forbidClick: true,
+                message: '下载中...',
+            })
+            if(browser().isApp) {
+                postMessage({ api: 'downloadFile', content: { downloadUrl: file } }, () => {
+                    this.$toast.clear()
+                })
+            } else {
+                this.$toast.clear()
+                window.location.href = file
+            }
+        },
+        downLoadFile(file) {
+            // this.previewUrl = 'https://view.officeapps.live.com/op/view.aspx?src=' + file
+            this.filePreview = true
+            this.fileType = this.getFileSuffix(file)
+
+            if(this.fileType == 'xls' || this.fileType == 'pdf') {
+                this.$toast.loading({
+                    duration: 0, // 持续展示 toast
+                    forbidClick: true,
+                    message: '加载中...',
+                })
+                let _this = this
+                this.$nextTick(() => {
+                    let iframe = document.createElement("iframe");
+                    iframe.id = 'preview_iframe'
+                    iframe.style.width = '100%';
+                    iframe.style.height = '100%';
+                    iframe.style.border = 'none';
+                    if(this.fileType == 'xls') {
+                        if(browser().android) {
+                            iframe.src = 'https://api.idocv.com/view/url?url=' + encodeURIComponent(file + '?times=' + new Date().getTime())
+                        } else {
+                            iframe.src = 'https://view.officeapps.live.com/op/view.aspx?src=' + file
+                        }
+                    } else {
+                        iframe.src = window.location.origin + '/pdf/web/viewer.html?file=' + encodeURIComponent(file)
+                    }
+                    if (iframe.attachEvent){
+                        iframe.attachEvent("onload", function(){
+                            _this.$toast.clear()
+                        });
+                    } else {
+                        iframe.onload = function(){
+                            _this.$toast.clear()
+
+                            // setTimeout(() => {
+                            //     let dom =  document.querySelector('#preview_iframe').contentWindow.document
+                            //     let scripts = dom.querySelectorAll('script[src]')
+                            //     if(scripts)
+                            // }, 2000);
+                        };
+                    }
+                    document.querySelector('#previewIframe').appendChild(iframe);
+                })
+            } else if(this.fileType == 'doc') {
+            //     // this.previewUrl = 'https://view.officeapps.live.com/op/view.aspx?src=' + file
+
+            }
+        },
+    },
+    computed: {
+        options() {
+            return this.widget.options || {}
+        },
+        rule() {
+            let rules = this.widget.rules || []
+            if(rules && rules.length > 0) {
+                rules.forEach(item => {
+                    if(item.pattern) {
+                        item.pattern = eval(item.pattern)
+                    }
+                    // 判断是否上传文件
+                    if(this.fileCheck) {
+                        item.required = false
+                    }
+                });
+            }
+            return rules
+        }
+    },
+    watch: {
+        // dataModel: {
+        //     deep: true,
+        //     handler(newValue) {
+        //         if (newValue !== undefined && newValue !== null) {
+        //         }
+        //     }
+        // },
+    }
+}
+</script>
+
+<style lang='less' scoped>
+@import url('./controlCommon.less');
+.oFileUpload {
+    /deep/.van-uploader__wrapper--disabled {
+        opacity: 0.9;
+    }
+    /deep/.van-field__control--custom {
+        flex-wrap: wrap;
+    }
+
+    .preview_file {
+        width: 100%;
+        font-size: 14px;
+        display: flex;
+        justify-content: space-between;
+        background: #f7f7f7;
+        padding: .08rem;
+        margin-bottom: 0.08rem;
+        .preview_item {
+            display: flex;
+            padding-top: .03rem;
+            flex-basis: 70%;
+        }
+        .preview_btn {
+            text-align: right;
+            flex-basis: 30%;
+        }
+    }
+}
+
+</style>

+ 139 - 0
src/views/message/control/imgFile.vue

@@ -0,0 +1,139 @@
+<template>
+    <div class="oImgUpload controller" :class="[preview ? '' : 'o-unit']" v-if="widget.type == 'imgupload'">
+        <van-field
+            :name="widget.model"
+            :label="widget.name || '图片'"
+            :required="fileCheck ? false : options.required || false"
+            :rules="rule"
+            v-if="!preview"
+        >
+            <template #input>
+                <van-uploader
+                    v-model="dataModel"
+                    :before-read="beforeRead"
+                    :before-delete="beforeDelete"
+                    :after-read="afterRead"
+                    :max-count="options.length"
+                    preview-size="0.6rem">
+                </van-uploader>
+            </template>
+        </van-field>
+        <van-field
+            v-else
+            :name="widget.model"
+            :label="widget.name || '图片'"
+            class="preview"
+        >
+            <template #input>
+                <van-uploader
+                    v-model="dataModel"
+                    :disabled="true"
+                    :deletable="false"
+                    :max-count="dataModel.length || 0"
+                    preview-size="0.5rem">
+                </van-uploader>
+            </template>
+        </van-field>
+    </div>
+</template>
+<script>
+import setLoading from '@/common/loading'
+import { uploadFile } from '@/views/message/api'
+export default {
+    name: 'oImgUpload',
+    props: ['widget', 'preview', 'value', 'fileCheck'],
+    data() {
+        return {
+            dataModel: this.widget.options?.defaultValue || [],
+        }
+    },
+    mounted() {
+        // 初始化参数
+        if(this.value) {
+            const widget = this.widget
+            const model = widget.originModel || widget.model
+            for(let v in this.value) {
+                if(v == model) {
+                    this.dataModel = this.value[v]
+                }
+            }
+        }
+    },
+    methods: {
+        beforeRead(file) {
+            const isLt2M = file.size / 1024 / 1024 < 5
+            if (!isLt2M) {
+                this.$toast('上传图片大小不能超过 5MB')
+                return false
+            }
+            return true
+        },
+        beforeDelete(file, detail) {
+            this.dataModel.splice(detail.index, 1)
+            return true
+        },
+        async afterRead(file, detail) { // 上传头像
+            try {
+                setLoading(true)
+                file.status = 'uploading'
+                file.message = '上传中...'
+                let formData = new FormData()
+                formData.append('file', file.file)
+                let res = await uploadFile(formData)
+                let result = res.data
+                if(result.code == 200) {
+                    file.status = 'done'
+                    this.dataModel[detail.index] = {
+                        url: result.data.url
+                    }
+                } else {
+                    file.status = 'failed'
+                    file.message = '上传失败'
+                    this.$toast(result.msg)
+                    return false
+                }
+                setLoading(false)
+            } catch (err) {
+                setLoading(false)
+                return false
+            }
+        },
+    },
+    computed: {
+        options() {
+            return this.widget.options || {}
+        },
+        rule() {
+            let rules = this.widget.rules || []
+            if(rules && rules.length > 0) {
+                rules.forEach(item => {
+                    if(item.pattern) {
+                        item.pattern = eval(item.pattern)
+                    }
+                    // 判断是否上传文件
+                    if(this.fileCheck) {
+                        item.required = false
+                    }
+                });
+            }
+            return rules
+        }
+    },
+    watch: {
+        // dataModel: {
+        //     deep: true,
+        //     handler(newValue) {
+        //         if (newValue !== undefined && newValue !== null) {
+        //         }
+        //     }
+        // },
+    }
+}
+</script>
+
+<style lang='less' scoped>
+@import url('./controlCommon.less');
+/deep/.van-uploader__wrapper--disabled {
+    opacity: 0.9;
+}
+</style>

+ 86 - 0
src/views/message/control/input.vue

@@ -0,0 +1,86 @@
+<template>
+    <div class="oInput controller" :class="[preview ? '' : 'o-unit']" v-if="widget.type == 'input'">
+        <van-field
+            :name="widget.model"
+            v-model="dataModel"
+            :label="widget.name || '文本框'"
+            :type="inputType"
+            :required="fileCheck ? false : options.required || false"
+            :disabled="options.disabled || false"
+            clearable
+            :placeholder="options.placeholder || '请输入'"
+            autocomplete="off"
+            :rules="rule"
+            v-if="!preview"
+        />
+        <van-cell class="preview" :title="widget.name || '文本框'" :value="dataModel" v-else></van-cell>
+    </div>
+</template>
+<script>
+
+export default {
+    name: 'oInput',
+    props: ['widget', 'preview', 'value', 'fileCheck'],
+    data() {
+        return {
+            dataModel: this.widget.options?.defaultValue || null,
+            inputType: null
+        }
+    },
+    mounted() {
+        // 初始化参数
+        let type = null
+        if(this.options.dataType === 'integer') {
+            type = 'digit'
+        } else if(this.options.dataType === 'float' || this.options.dataType.number) {
+            type = 'number'
+        }
+        this.inputType = type
+
+        if(this.value) {
+            const widget = this.widget
+            const model = widget.originModel || widget.model
+            for(let v in this.value) {
+                if(v == model) {
+                    this.dataModel = this.value[v]
+                }
+            }
+        }
+    },
+    methods: {
+    },
+    computed: {
+        options() {
+            return this.widget.options || {}
+        },
+        rule() {
+            let rules = this.widget.rules || []
+            if(rules && rules.length > 0) {
+                rules.forEach(item => {
+                    if(item.pattern) {
+                        item.pattern = eval(item.pattern)
+                    }
+                    // 判断是否上传文件
+                    if(this.fileCheck) {
+                        item.required = false
+                    }
+                });
+            }
+            return rules
+        }
+    },
+    watch: {
+        // dataModel: {
+        //     deep: true,
+        //     handler(newValue) {
+        //         if (newValue !== undefined && newValue !== null) {
+        //         }
+        //     }
+        // },
+    }
+}
+</script>
+
+<style lang='less' scoped>
+@import url('./controlCommon.less');
+</style>

+ 76 - 0
src/views/message/control/number.vue

@@ -0,0 +1,76 @@
+<template>
+    <div class="oNumber controller" :class="[preview ? '' : 'o-unit']" v-if="widget.type == 'number'">
+        <van-field
+            :name="widget.model"
+            v-model="dataModel"
+            :label="widget.name || '文本框'"
+            :required="fileCheck ? false : options.required || false"
+            :disabled="options.disabled || false"
+            type="number"
+            clearable
+            :placeholder="options.placeholder || '请输入'"
+            :rules="rule"
+            v-if="!preview"
+        />
+        <van-cell class="preview" :title="widget.name || '文本框'" :value="dataModel" v-else></van-cell>
+    </div>
+</template>
+<script>
+
+export default {
+    name: 'oNumber',
+    props: ['widget', 'preview', 'value', 'fileCheck'],
+    data() {
+        return {
+            dataModel: this.widget.options?.defaultValue || null,
+        }
+    },
+    mounted() {
+        // 初始化参数
+        if(this.value) {
+            const widget = this.widget
+            const model = widget.originModel || widget.model
+            for(let v in this.value) {
+                if(v == model) {
+                    this.dataModel = this.value[v]
+                }
+            }
+        }
+    },
+    methods: {
+    },
+    computed: {
+        options() {
+            return this.widget.options || {}
+        },
+        rule() {
+            let rules = this.widget.rules || []
+            if(rules && rules.length > 0) {
+                rules.forEach(item => {
+                    if(item.pattern) {
+                        item.pattern = eval(item.pattern)
+                    }
+                    // 判断是否上传文件
+                    if(this.fileCheck) {
+                        item.required = false
+                    }
+                });
+            }
+            return rules
+        }
+    },
+    watch: {
+        // dataModel: {
+        //     deep: true,
+        //     handler(newValue) {
+        //         if (newValue !== undefined && newValue !== null) {
+        //         }
+        //     }
+        // },
+    }
+}
+</script>
+
+<style lang='less' scoped>
+@import url('./controlCommon.less');
+</style>

+ 157 - 0
src/views/message/control/organ.vue

@@ -0,0 +1,157 @@
+<template>
+    <div class="oOrgan controller" :class="[preview ? '' : 'o-unit']" v-if="widget.type == 'organ'">
+        <van-field
+            :name="widget.model"
+            v-model="dataModel"
+            :label="widget.name || '文本框'"
+            :required="fileCheck ? false : options.required || false"
+            :disabled="options.disabled || false"
+            clearable
+            readonly
+            :placeholder="options.placeholder || '请输入'"
+            :rules="rule"
+            clear-trigger="always"
+            @click="onClick"
+            v-if="!preview"
+        >
+            <template #right-icon>
+                <i class="van-icon van-icon-arrow van-cell__right-icon"></i>
+            </template>
+        </van-field>
+
+        <van-cell class="preview" :border="false" :title="widget.name || '文本框'" :value="dataModel" v-else></van-cell>
+
+        <!-- 处理显示问题 -->
+        <div class="valueShow" v-if="showDataModel && !preview">{{ showDataModel }}</div>
+        <div class="valueShow valuePreview" v-if="showDataModel && preview">
+            {{ showDataModel }}
+            <input type="hidden" :name="widget.model" :value="dataModel">
+        </div>
+
+        <van-popup v-model="showSelect" position="bottom">
+            <van-picker
+                show-toolbar
+                :columns="columns"
+                @confirm="onConfirm"
+                visible-item-count="4"
+                @cancel="onCancel"
+            />
+        </van-popup>
+    </div>
+</template>
+<script>
+// import { queryEmployeeOrgan } from '@/views/payAppeal/api'
+export default {
+    name: 'oOrgan',
+    props: ['widget', 'preview', 'value', 'fileCheck'],
+    data() {
+        return {
+            dataModel: this.widget.options?.defaultValue || null,
+            showDataModel: null,
+            showSelect: false,
+            columns: []
+        }
+    },
+    async mounted() {
+        // 初始化参数
+        await this.$store.dispatch('setAllBranch')
+        await this.getList()
+        if(this.value) {
+            const widget = this.widget
+            const model = widget.originModel || widget.model
+            for(let v in this.value) {
+                if(v == model) {
+                    this.dataModel = this.value[v]
+                }
+            }
+            // 反显分部
+            // console.log(this.dataModel)
+            // console.log(this.columns)
+            this.columns.forEach(item => {
+                if(this.dataModel == item.value) {
+                    this.showDataModel = item.text
+                }
+            })
+        }
+    },
+    methods: {
+        onClick() {
+            this.showSelect = true
+        },
+        onConfirm(val) {
+            this.dataModel = val.value
+            this.showDataModel = val.text
+            this.showSelect = false
+        },
+        onCancel() {
+            this.showSelect = false
+        },
+        async getList() {
+            // 获取所有分部
+            try {
+                this.columns = []
+                // let res = await queryEmployeeOrgan()
+                let res = this.selects.allBranch
+                // console.log(res)
+                let tempRes = res || []
+                tempRes.forEach(item => {
+                    this.columns.push({
+                        text: item.name,
+                        value: item.id
+                    })
+                })
+            } catch {
+                //
+            }
+        }
+    },
+    computed: {
+        options() {
+            return this.widget.options || {}
+        },
+        rule() {
+            let rules = this.widget.rules || []
+            if(rules && rules.length > 0) {
+                rules.forEach(item => {
+                    if(item.pattern) {
+                        item.pattern = eval(item.pattern)
+                    }
+                    // 判断是否上传文件
+                    if(this.fileCheck) {
+                        item.required = false
+                    }
+                });
+            }
+            return rules
+        }
+    },
+    watch: {
+        // dataModel: {
+        //     deep: true,
+        //     handler(newValue) {
+        //         if (newValue !== undefined && newValue !== null) {
+        //         }
+        //     }
+        // },
+    }
+}
+</script>
+
+<style lang='less' scoped>
+@import url('./controlCommon.less');
+.oOrgan {
+    position: relative;
+    .valueShow {
+        position: absolute;
+        bottom: 12px;
+        left: 16px;
+        line-height: 24px;
+        padding: .03rem 0;
+        background: #fff;
+        min-width: 120px;
+    }
+    .valuePreview {
+        bottom: 2px;
+    }
+}
+</style>

+ 88 - 0
src/views/message/control/radio.vue

@@ -0,0 +1,88 @@
+<template>
+    <div class="oRadio controller" :class="[preview ? '' : 'o-unit']" v-if="widget.type == 'radio'">
+        <van-field
+            :name="widget.model"
+            :label="widget.name || '单选框'"
+            :required="fileCheck ? false : options.required || false"
+            :disabled="options.disabled || false"
+            clearable
+            :placeholder="options.placeholder || '请选择'"
+            :rules="rule"
+             v-if="!preview"
+        >
+            <template #input>
+                <van-radio-group v-model="dataModel" :direction="options.inline ? 'horizontal' : 'vertical'">
+                    <van-radio :name="item.value" v-for="item in options.options" :key="item.value">{{ item.value }}</van-radio>
+                </van-radio-group>
+            </template>
+        </van-field>
+        <van-cell class="preview" :title="widget.name || '单选框'" :value="dataModel" v-else></van-cell>
+    </div>
+</template>
+<script>
+
+export default {
+    name: 'oRadio',
+    props: ['widget', 'preview', 'value', 'fileCheck'],
+    data() {
+        return {
+            dataModel: this.widget.options?.defaultValue || null,
+        }
+    },
+    mounted() {
+        // 初始化参数
+        if(this.value) {
+            const widget = this.widget
+            const model = widget.originModel || widget.model
+            for(let v in this.value) {
+                if(v == model) {
+                    this.dataModel = this.value[v]
+                }
+            }
+        }
+    },
+    methods: {
+    },
+    computed: {
+        options() {
+            return this.widget.options || {}
+        },
+        rule() {
+            let rules = this.widget.rules || []
+            if(rules && rules.length > 0) {
+                rules.forEach(item => {
+                    if(item.pattern) {
+                        item.pattern = eval(item.pattern)
+                    }
+                    // 判断是否上传文件
+                    if(this.fileCheck) {
+                        item.required = false
+                    }
+                });
+            }
+            return rules
+        }
+    },
+    watch: {
+        // dataModel: {
+        //     deep: true,
+        //     handler(newValue) {
+        //         if (newValue !== undefined && newValue !== null) {
+        //         }
+        //     }
+        // },
+    }
+}
+</script>
+
+<style lang='less' scoped>
+@import url('./controlCommon.less');
+.oRadio {
+    /deep/.van-radio  {
+        padding: .03rem 0;
+    }
+    /deep/.van-radio--horizontal {
+        margin-right: .15rem;
+    }
+}
+</style>

+ 107 - 0
src/views/message/control/rate.vue

@@ -0,0 +1,107 @@
+<template>
+    <div class="oRate controller" :class="[preview ? '' : 'o-unit']" v-if="widget.type == 'rate'">
+        <van-field
+            :name="widget.model"
+            :label="widget.name || '文本框'"
+            :required="fileCheck ? false : options.required || false"
+            clearable
+            :placeholder="options.placeholder || '请输入'"
+            :rules="rule"
+            v-if="!preview"
+        >
+            <template #input>
+                <van-rate
+                    v-model="dataModel"
+                    :size="25"
+                    :disabled="options.disabled || false"
+                    :count="options.max"
+                    color="#ffd21e"
+                    void-icon="star"
+                    void-color="#eee"
+                />
+            </template>
+        </van-field>
+        <van-field
+            v-else
+            :name="widget.model"
+            :label="widget.name || '文本框'"
+            class="preview"
+        >
+            <template #input>
+                <van-rate
+                    v-model="dataModel"
+                    :size="25"
+                    :readonly="true"
+                    :count="options.max"
+                    color="#ffd21e"
+                    void-icon="star"
+                    void-color="#eee"
+                />
+            </template>
+        </van-field>
+    </div>
+</template>
+<script>
+
+export default {
+    name: 'oRate',
+    props: ['widget', 'preview', 'value', 'fileCheck'],
+    data() {
+        return {
+            dataModel: this.widget.options?.defaultValue || null,
+        }
+    },
+    mounted() {
+        // 初始化参数
+        if(this.value) {
+            const widget = this.widget
+            const model = widget.originModel || widget.model
+            for(let v in this.value) {
+                if(v == model) {
+                    this.dataModel = this.value[v]
+                }
+            }
+        }
+    },
+    methods: {
+    },
+    computed: {
+        options() {
+            return this.widget.options || {}
+        },
+        rule() {
+            let rules = this.widget.rules || []
+            if(rules && rules.length > 0) {
+                rules.forEach(item => {
+                    if(item.pattern) {
+                        item.pattern = eval(item.pattern)
+                    }
+                    // 判断是否上传文件
+                    if(this.fileCheck) {
+                        item.required = false
+                    }
+                });
+            }
+            return rules
+        }
+    },
+    watch: {
+        // dataModel: {
+        //     deep: true,
+        //     handler(newValue) {
+        //         if (newValue !== undefined && newValue !== null) {
+        //         }
+        //     }
+        // },
+    }
+}
+</script>
+
+<style lang='less' scoped>
+@import url('./controlCommon.less');
+.oRate {
+    /deep/.van-rate {
+        flex-wrap: wrap;
+    }
+}
+</style>

+ 193 - 0
src/views/message/control/school.vue

@@ -0,0 +1,193 @@
+<template>
+    <div class="oSchool controller" :class="[preview ? '' : 'o-unit']" v-if="widget.type == 'school'">
+        <van-field
+            :name="widget.model"
+            v-model="dataModel"
+            :label="widget.name || '文本框'"
+            :required="fileCheck ? false : options.required || false"
+            :disabled="options.disabled || false"
+            clearable
+            readonly
+            clickable
+            :placeholder="options.placeholder || '请输入'"
+            :rules="rule"
+            clear-trigger="always"
+            @click="onClick"
+            v-if="!preview"
+        >
+            <template #right-icon>
+                <i class="van-icon van-icon-arrow van-cell__right-icon"></i>
+            </template>
+        </van-field>
+
+        <van-cell class="preview" :border="false" :title="widget.name || '文本框'" :value="dataModel" v-else></van-cell>
+
+        <!-- 处理显示问题 -->
+        <div class="valueShow" v-if="showDataModel && !preview">{{ showDataModel }}</div>
+        <div class="valueShow valuePreview" v-if="showDataModel && preview">{{ showDataModel }}</div>
+
+        <van-popup v-model="showSelect" position="bottom">
+            <van-picker
+                show-toolbar
+                :columns="columns"
+                @confirm="onConfirm"
+                visible-item-count="4"
+                @cancel="onCancel"
+            />
+        </van-popup>
+    </div>
+</template>
+<script>
+import { getOrganCooperation } from '../api'
+export default {
+    name: 'oSchool',
+    props: ['widget', 'preview', 'value', 'fileCheck'],
+    data() {
+        return {
+            dataModel: this.widget.options?.defaultValue || null,
+            showDataModel: null,
+            showSelect: false,
+            columns: [],
+            organId: null,
+        }
+    },
+    async mounted() {
+        // 初始化参数
+        this.options?.options?.forEach(item => {
+            this.columns.push(item.value)
+        })
+        // console.log(this.widget, this.value)
+        if(this.value) {
+            const widget = this.widget
+            const model = widget.originModel || widget.model
+            for(let v in this.value) {
+                if(v == model) {
+                    this.dataModel = this.value[v]
+                }
+            }
+            setTimeout(async () => {
+                let subForm = widget.model.indexOf('subform')
+                let name = widget.options.displayRelation
+                let relation = null
+                if(subForm > -1) {
+                    relation = widget.model.replace(widget.originModel, name)
+                }
+                // 判断,使用的是哪个分部为基本
+                let organElement = document.querySelector(`input[name="${name}"]`) || document.querySelector(`input[name="${relation}"]`)
+                const organId = organElement ? organElement.value : null
+                if(!organId) {
+                    return
+                }
+                this.organId = organId
+                await this.getList()
+                // 反显分部
+                this.columns.forEach(item => {
+                    if(this.dataModel == item.value) {
+                        this.showDataModel = item.text
+                    }
+                })
+            }, 200);
+        }
+    },
+    methods: {
+        async onClick() {
+            const widget = this.widget
+            let subForm = widget.model.indexOf('subform')
+            let name = widget.options.displayRelation
+            let relation = null
+            if(subForm > -1) {
+                relation = widget.model.replace(widget.originModel, name)
+            }
+            // 判断,使用的是哪个分部为基本
+            let organElement = document.querySelector(`input[name="${name}"]`) || document.querySelector(`input[name="${relation}"]`)
+            if(!name || !organElement) {
+                this.$toast('模板配置有误,请联系总部管理员')
+                return
+            }
+            const organId = organElement.value
+            if(!organId) {
+                this.$toast('请选择分部')
+                return
+            }
+            // 判断分部编号是否有修改或更新
+            if(organId != this.organId) {
+                this.organId = organId
+                await this.getList()
+            }
+
+            this.showSelect = true
+        },
+        async getList() {
+            // 获取所有分部
+            try {
+                this.columns = []
+                let res = await getOrganCooperation({ organId: this.organId })
+                let tempRes = res.data || []
+                tempRes.forEach(item => {
+                    this.columns.push({
+                        text: item.name,
+                        value: item.id
+                    })
+                })
+            } catch {
+                //
+            }
+        },
+        onConfirm(val) {
+            this.dataModel = val.value
+            this.showDataModel = val.text
+            this.showSelect = false
+        },
+        onCancel() {
+            this.showSelect = false
+        },
+    },
+    computed: {
+        options() {
+            return this.widget.options || {}
+        },
+        rule() {
+            let rules = this.widget.rules || []
+            if(rules && rules.length > 0) {
+                rules.forEach(item => {
+                    if(item.pattern) {
+                        item.pattern = eval(item.pattern)
+                    }
+                    // 判断是否上传文件
+                    if(this.fileCheck) {
+                        item.required = false
+                    }
+                });
+            }
+            return rules
+        }
+    },
+    watch: {
+        // dataModel: {
+        //     deep: true,
+        //     handler(newValue) {
+        //         if (newValue !== undefined && newValue !== null) {
+        //         }
+        //     }
+        // },
+    }
+}
+</script>
+
+<style lang='less' scoped>
+@import url('./controlCommon.less');
+.oSchool {
+    position: relative;
+     .valueShow {
+        position: absolute;
+        bottom: 12px;
+        left: 16px;
+        line-height: 24px;
+        padding: .03rem 0;
+        background: #fff;
+    }
+    .valuePreview {
+        bottom: 2px;
+    }
+}
+</style>

+ 166 - 0
src/views/message/control/select.vue

@@ -0,0 +1,166 @@
+<template>
+    <div class="oSelect controller" :class="[preview ? '' : 'o-unit']" v-if="widget.type == 'select'">
+        <van-field
+            :name="widget.model"
+            v-model="dataModel"
+            :label="widget.name || '文本框'"
+            :required="fileCheck ? false : options.required || false"
+            :disabled="options.disabled || false"
+            clearable
+            readonly
+            clickable
+            :placeholder="options.placeholder || '请输入'"
+            :rules="rule"
+            clear-trigger="always"
+            @click="onClick"
+            v-if="!preview"
+        >
+            <template #right-icon>
+                <i class="van-icon van-icon-arrow van-cell__right-icon"></i>
+            </template>
+        </van-field>
+
+        <van-cell class="preview" :title="widget.name || '文本框'" :value="dataModel" v-else></van-cell>
+
+        <van-popup v-model="showSelect" position="bottom" round style="height: 45%">
+            <van-cell title=" " class="select-popup-header">
+                <template #icon>
+                    <van-icon name="arrow-down" size="22px" @click="showSelect = false" />
+                </template>
+                <span v-if="multiple" type="info" class="btnSure" style="padding: 0 .18rem;" @click="onPopupSubmit">确定</span>
+            </van-cell>
+            <van-checkbox-group v-if="multiple" class="select-list" v-model="selectValue">
+                <van-checkbox class="select-item" v-for="(item, index) in columns" :key="index" :name="item">{{ item }}</van-checkbox>
+            </van-checkbox-group>
+            <van-radio-group v-model="radio" class="select-list" v-else>
+                <van-radio class="select-item" v-for="(item, index) in columns" @click="onRadio" :key="index" :name="item">{{ item }}</van-radio>
+            </van-radio-group>
+        </van-popup>
+    </div>
+</template>
+<script>
+
+export default {
+    name: 'oSelect',
+    props: ['widget', 'preview', 'value', 'fileCheck'],
+    data() {
+        return {
+            dataModel: this.widget.options?.defaultValue || null,
+            showSelect: false,
+            multiple: false, // 是否可以多选
+            columns: [],
+            selectValue: [], // 多选存值
+            radio: null, // 单选的值
+        }
+    },
+    mounted() {
+        // 初始化参数
+        this.options?.options?.forEach(item => {
+            this.columns.push(item.value)
+        })
+        // 是否多选
+        this.multiple = this.options?.multiple || false
+        if(this.value) {
+            const widget = this.widget
+            // console.log(this.value, widget, 'select')
+            const model = widget.originModel || widget.model
+            for(let v in this.value) {
+                if(v == model) {
+                    this.dataModel = this.value[v]
+                }
+            }
+        }
+    },
+    methods: {
+        onClick() {
+            this.showSelect = true
+        },
+        onPopupSubmit() {
+            this.dataModel = this.selectValue.join(',')
+            this.showSelect = false
+            let relationArray = []
+            this.options?.options?.forEach(item => {
+                if(this.selectValue.includes(item.value)) {
+                    let tempRelation = item.relationOptions || []
+                    relationArray.push(...tempRelation)
+                }
+            })
+            let uniqueArr = [...new Set(relationArray)]
+            if(uniqueArr.length > 0) {
+                this.$emit('relationFormChange', uniqueArr)
+            }
+        },
+        onRadio() {
+            this.dataModel = this.radio
+            this.showSelect = false
+            let relationArray = []
+            this.options?.options?.forEach(item => {
+                if(this.radio == item.value) {
+                    let tempRelation = item.relationOptions || []
+                    relationArray.push(...tempRelation)
+                }
+            })
+            if(relationArray.length > 0) {
+                this.$emit('relationFormChange', relationArray)
+            }
+        }
+    },
+    computed: {
+        options() {
+            return this.widget.options || {}
+        },
+        rule() {
+            let rules = this.widget.rules || []
+            if(rules && rules.length > 0) {
+                rules.forEach(item => {
+                    if(item.pattern) {
+                        item.pattern = eval(item.pattern)
+                    }
+                    // 判断是否上传文件
+                    if(this.fileCheck) {
+                        item.required = false
+                    }
+                });
+            }
+            return rules
+        }
+    },
+    watch: {
+        // dataModel: {
+        //     deep: true,
+        //     handler(newValue) {
+        //         if (newValue !== undefined && newValue !== null) {
+        //         }
+        //     }
+        // },
+    }
+}
+</script>
+
+<style lang='less' scoped>
+@import url('./controlCommon.less');
+.select-popup-header {
+    align-items: center;
+    position: fixed;
+    background: #fff;
+    z-index: 99;
+    border-radius: 16px 16px 0 0;
+}
+.select-list {
+    padding-top: 60px;
+    padding-bottom: .2rem;
+    .select-item {
+        padding: 12px 16px;
+    }
+}
+.btnSure {
+    padding: 0 18px;
+    background-color: #01C1B5;
+    font-size: 12px;
+    height: 32px;
+    border-radius: 32px;
+    line-height: 32px;
+    display: inline-block;
+    color: #fff;
+}
+</style>

+ 269 - 0
src/views/message/control/subform.vue

@@ -0,0 +1,269 @@
+<template>
+    <div class="oSubform" :class="[preview ? '' : 'o-unit']" v-if="widget.type == 'subform'">
+        <!-- <input type="text" :name="widget.model" v-model="dataModel"> -->
+        <!-- <van-field type="hidden" :name="widget.model" v-model="dataModel" /> -->
+        <van-cell v-if="!preview" style="padding-bottom: 5px;" :title="(widget.name || '子表单')" :border="false"></van-cell>
+        <div v-for="(child, iChild) in columns" :key="iChild" :class="preview ? 'previewSection' : null">
+            <van-cell v-if="preview" class="previewTitle" :title="(widget.name || '子表单') + `${iChild + 1}`" :border="false"></van-cell>
+            <span v-if="columns.length > 1 && !preview" class="delete">
+                <span @click="onDelete(child, iChild)">删除</span>
+            </span>
+            <div v-if="child.list && child.list.length > 0" :class="[preview ? null : 'van-hairline--bottom']">
+                <div v-for="(item, index) in child.list" :key="'o' + index">
+                    <template v-if="item.type == 'input' && !item.hidden">
+                        <o-input :widget="item" :fileCheck="fileCheck" :preview="preview" :value="defaultValue ? defaultValue[iChild] : null" />
+                    </template>
+                    <template v-if="item.type == 'textarea' && !item.hidden">
+                        <o-textarea :widget="item" :fileCheck="fileCheck" :preview="preview" :value="defaultValue ? defaultValue[iChild] : null" />
+                    </template>
+                    <template v-if="item.type == 'number' && !item.hidden">
+                        <o-number :widget="item" :fileCheck="fileCheck" :preview="preview" :value="defaultValue ? defaultValue[iChild] : null" />
+                    </template>
+                    <template v-if="item.type == 'radio' && !item.hidden">
+                        <o-radio :widget="item" :fileCheck="fileCheck" :preview="preview" :value="defaultValue ? defaultValue[iChild] : null" />
+                    </template>
+                    <template v-if="item.type == 'checkbox' && !item.hidden">
+                        <o-checkbox :widget="item" :fileCheck="fileCheck" :preview="preview" :value="defaultValue ? defaultValue[iChild] : null" />
+                    </template>
+                    <template v-if="item.type == 'date' && !item.hidden">
+                        <o-date :widget="item" :fileCheck="fileCheck" :preview="preview" :value="defaultValue ? defaultValue[iChild] : null" />
+                    </template>
+                    <template v-if="item.type == 'rate' && !item.hidden">
+                        <o-rate :widget="item" :fileCheck="fileCheck" :preview="preview" :value="defaultValue ? defaultValue[iChild] : null" />
+                    </template>
+                    <template v-if="item.type == 'select' && !item.hidden">
+                        <o-select :widget="item" :fileCheck="fileCheck" @relationFormChange="relationFormChange" :preview="preview" :value="defaultValue ? defaultValue[iChild] : null" />
+                    </template>
+                    <template v-if="item.type == 'organ' && !item.hidden">
+                        <o-organ :widget="item" :fileCheck="fileCheck" :preview="preview" :value="defaultValue ? defaultValue[iChild] : null" />
+                    </template>
+                    <template v-if="item.type == 'school' && !item.hidden">
+                        <o-school :widget="item" :fileCheck="fileCheck" :preview="preview" :value="defaultValue ? defaultValue[iChild] : null" />
+                    </template>
+                    <template v-if="item.type == 'text' && !item.hidden">
+                        <o-text :widget="item" :fileCheck="fileCheck" :preview="preview" :value="defaultValue ? defaultValue[iChild] : null" />
+                    </template>
+                    <template v-if="item.type == 'imgupload' && !item.hidden">
+                        <o-img-upload :widget="item" :fileCheck="fileCheck" :preview="preview" :value="defaultValue ? defaultValue[iChild] : null" />
+                    </template>
+                    <template v-if="item.type == 'file' && !item.hidden">
+                        <o-file-upload :widget="item" :fileCheck="fileCheck" @fileCheckRequired="fileCheckRequired" :preview="preview" :value="defaultValue ? defaultValue[iChild] : null" />
+                    </template>
+                    <template v-if="item.type == 'divider' && !item.hidden">
+                        <o-divider :widget="item" :fileCheck="fileCheck" :preview="preview" :value="defaultValue ? defaultValue[iChild] : null" />
+                    </template>
+                    <template v-if="item.type == 'cascader' && !item.hidden">
+                        <o-cascader :widget="item" :fileCheck="fileCheck" :preview="preview" :value="defaultValue ? defaultValue[iChild] : null" />
+                    </template>
+                    <!-- <template v-if="item.type == 'subform'">
+                        <o-subform :widget="item" />
+                    </template> -->
+                </div>
+            </div>
+        </div>
+        <div class="addItem" @click="addItem" v-if="!preview">
+            <van-icon name="plus" /> 添加
+        </div>
+    </div>
+</template>
+<script>
+import OInput from './input'
+import OTextarea from './textarea'
+import ONumber from './number'
+import ORadio from './radio'
+import OCheckbox from './checkbox'
+import ODate from './date'
+import ORate from './rate'
+import OSelect from './select'
+import OOrgan from './organ'
+import OSchool from './school'
+import OText from './text'
+import OImgUpload from './imgFile'
+import OFileUpload from './fileUpload'
+import ODivider from './divider'
+import OCascader from './cascader'
+export default {
+    name: 'oSubform',
+    components: {OInput, OTextarea, ONumber, ORadio, OCheckbox, ODate, ORate, OSelect, OOrgan, OSchool, OText, OImgUpload, OFileUpload, ODivider, OCascader },
+    props: ['widget', 'value', 'preview', 'fileCheck'],
+    data() {
+        return {
+            columns: [],
+            tempColumns: [],
+            models: {},
+            defaultValue: null,
+        }
+    },
+    mounted() {
+        this.__init()
+    },
+    methods: {
+        __init() {
+            // 初始化参数
+            this.columns = []
+            this.tempColumns = []
+            const columns = this.widget.columns || []
+            let arr = {
+                list: []
+            }
+            columns.forEach(item => {
+                if(item.list?.length > 0) {
+                    item.list.forEach(child => {
+                        child.originModel = child.model
+                        child.model = this.widget.model + '.' + child.model + '.0'
+                    })
+                    arr.list.push(...item.list)
+                }
+            });
+            this.columns.push(arr)
+            this.tempColumns.push(arr)
+            if(this.value) {
+                const widget = this.widget
+                const model = widget.originModel || widget.model
+                for(let v in this.value) {
+                    if(v == model) {
+                        this.defaultValue = this.value[v]
+                    }
+                }
+            }
+            // 对比值跟表单对比
+            if(this.defaultValue) {
+                let arrDiff = this.defaultValue.length - this.columns?.length
+                if(arrDiff > 0) {
+                    for(let i = 0; i < arrDiff; i++) {
+                        this.addItem()
+                    }
+                }
+            }
+        },
+        reSetFormData() {
+            this.columns = []
+            const columns = this.widget.columns || []
+            let arr = {
+                list: []
+            }
+            columns.forEach(item => {
+                if(item.list?.length > 0) {
+                    arr.list.push(...item.list)
+                }
+            });
+            this.columns.push(arr)
+        },
+        addItem() {
+            // 添加组件
+            // console.log(this.tempColumns)
+            let tempColumns = JSON.parse(JSON.stringify(this.tempColumns))
+            tempColumns.forEach(item => {
+                if(item.list?.length > 0) {
+                    item.list.forEach(child => {
+                        let model = child.model.split('.')
+                        model.pop()
+                        model = model.join('.')
+                        child.model = model + '.' + this.columns.length
+                    })
+                }
+            })
+            this.columns.push(...tempColumns)
+        },
+        onDelete(child, index) {
+            this.$dialog.confirm({
+                message: `你确定要删除吗?`,
+            }).then(() => {
+                // on confirm
+				if (index !== -1) {
+					this.columns.splice(index, 1)
+				}
+            })
+        },
+        relationFormChange(value) { // 是否隐藏对应表单
+            this.$emit('relationFormChange', value)
+        },
+        fileCheckRequired(value) { // 退费模板上传附件
+            this.$emit('fileCheckRequired', value)
+        },
+    },
+    computed: {
+        options() {
+            return this.widget.options || {}
+        },
+        // columns() {
+        //     return this.widget.columns || []
+        // }
+    },
+    watch: {
+        // dataModel: {
+        //     deep: true,
+        //     handler(newValue) {
+        //         if (newValue !== undefined && newValue !== null) {
+        //         }
+        //     }
+        // },
+        // value: {
+        //     deep: true,
+        //     handler(val) {
+        //         this.models = { ...this.models, ...val }
+        //     }
+        // }
+    }
+}
+</script>
+
+<style lang='less' scoped>
+.o-unit {
+    margin-bottom: 12px;
+}
+.oSubform {
+    .delete {
+        color: #01C1B5;
+        font-size: 0.14rem;
+        padding: 0.05rem 0.1rem 0;
+        display: block;
+        text-align: right;
+    }
+    .o-unit {
+        margin-bottom: 0;
+        position: relative;
+        &::after {
+            position: absolute;
+            box-sizing: border-box;
+            content: ' ';
+            pointer-events: none;
+            right: 16px;
+            bottom: 0;
+            left: 16px;
+            border-bottom: 1px solid #ebedf0;
+            -webkit-transform: scaleY(0.5);
+            transform: scaleY(0.5);
+        }
+    }
+    /deep/.van-field {
+        padding: 12px 16px;
+        line-height: 1.3;
+        display: flex;
+        font-size: 16px;
+    }
+
+    .addItem {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        background: #fff;
+        padding: .1rem;
+        color: #01C1B5;
+        /deep/.van-icon {
+            font-size: .16rem;
+            padding-right: .03rem;
+        }
+    }
+
+    .previewSection {
+        padding: 0 .16rem;
+        .previewTitle {
+            padding: 0 .1rem;
+            margin-top: .1rem;
+            background: #f5f5f5;
+            color: #808080;
+        }
+    }
+}
+</style>

+ 52 - 0
src/views/message/control/text.vue

@@ -0,0 +1,52 @@
+<template>
+    <div class="oText" v-if="widget.type == 'text'">
+        <p class="text-content" :class="[preview ? 'textMarginTop' : null]">{{ dataModel }}</p>
+    </div>
+</template>
+<script>
+
+export default {
+    name: 'oText',
+    props: ['widget', 'preview'],
+    data() {
+        return {
+            dataModel: this.widget.options?.defaultValue || null,
+        }
+    },
+    mounted() {
+        // 初始化参数
+    },
+    methods: {
+    },
+    computed: {
+        options() {
+            return this.widget.options || {}
+        },
+    },
+    watch: {
+        // dataModel: {
+        //     deep: true,
+        //     handler(newValue) {
+        //         if (newValue !== undefined && newValue !== null) {
+        //         }
+        //     }
+        // },
+    }
+}
+</script>
+
+<style lang='less' scoped>
+.oText {
+    // margin-bottom: 5px;
+    padding: 12px 0 5px;
+    .text-content {
+        font-size: .12rem;
+        color: #777777;
+        padding: 0 .16rem;
+        &.textMarginTop {
+            margin-top: 5px;
+            // padding: 0;
+        }
+    }
+}
+</style>

+ 79 - 0
src/views/message/control/textarea.vue

@@ -0,0 +1,79 @@
+<template>
+    <div class="oTextarea controller" :class="[preview ? '' : 'o-unit']" v-if="widget.type == 'textarea'">
+        <van-field
+            :name="widget.model"
+            v-model="dataModel"
+            :label="widget.name || '文本框'"
+            :required="fileCheck ? false : options.required || false"
+            :disabled="options.disabled || false"
+            :show-error="false"
+            :scroll-to-error="true"
+            :placeholder="options.placeholder || '请输入'"
+            type="textarea"
+            clearable
+            :autosize=" { maxHeight: 120, minHeight: 60 }"
+            :rules="rule"
+            v-if="!preview"
+        />
+        <van-cell class="preview" :title="widget.name || '文本框'" :value="dataModel" v-else></van-cell>
+    </div>
+</template>
+<script>
+
+export default {
+    name: 'oTextarea',
+    props: ['widget', 'preview', 'value', 'fileCheck'],
+    data() {
+        return {
+            dataModel: this.widget.options?.defaultValue || null,
+        }
+    },
+    mounted() {
+        // 初始化参数
+        if(this.value) {
+            const widget = this.widget
+            const model = widget.originModel || widget.model
+            for(let v in this.value) {
+                if(v == model) {
+                    this.dataModel = this.value[v]
+                }
+            }
+        }
+    },
+    methods: {
+    },
+    computed: {
+        options() {
+            return this.widget.options || {}
+        },
+        rule() {
+            let rules = this.widget.rules || []
+            if(rules && rules.length > 0) {
+                rules.forEach(item => {
+                    if(item.pattern) {
+                        item.pattern = eval(item.pattern)
+                    }
+                    // 判断是否上传文件
+                    if(this.fileCheck) {
+                        item.required = false
+                    }
+                });
+            }
+            return rules
+        }
+    },
+    watch: {
+        // dataModel: {
+        //     deep: true,
+        //     handler(newValue) {
+        //         if (newValue !== undefined && newValue !== null) {
+        //         }
+        //     }
+        // },
+    }
+}
+</script>
+
+<style lang='less' scoped>
+@import url('./controlCommon.less');
+</style>

BIN
src/views/message/images/a_1.png


BIN
src/views/message/images/a_2.png


BIN
src/views/message/images/a_3.png


BIN
src/views/message/images/default_icon.png


BIN
src/views/message/images/icon_close.png


BIN
src/views/message/images/icon_close_round.png


BIN
src/views/message/images/icon_ding.png


BIN
src/views/message/images/icon_edit.png


BIN
src/views/message/images/icon_reject.png


BIN
src/views/message/images/icon_resolve.png


BIN
src/views/message/images/icon_share.png


BIN
src/views/message/images/icon_transfer.png


BIN
src/views/message/images/music_icon.png


BIN
src/views/message/images/pay_icon.png


BIN
src/views/message/images/student_icon.png


BIN
src/views/message/images/system-complete-default.png


BIN
src/views/message/images/system-complete.png


BIN
src/views/message/images/system-reject.png


BIN
src/views/message/images/system-transfer.png


BIN
src/views/message/images/system-wait.png


BIN
src/views/message/images/work_icon.png


+ 118 - 0
src/views/message/modal/applyModal.vue

@@ -0,0 +1,118 @@
+<template>
+  <div class="workflow">
+    <van-collapse v-model="activeNames" :border="false">
+      <template v-for="(item, index) in applyList">
+        <van-collapse-item :name="index" :key="index" v-if="item.process_list2 && item.process_list2.length > 0">
+          <template #title>
+              <h2 class="title">{{ item.name }}</h2>
+          </template>
+          <div class="grid">
+            <div
+              class="grid-item"
+              v-for="process in item.process_list2"
+              :key="process.id"
+              @click="onHref(process)"
+            >
+              <e-icon class="icon" :icon-name="process.icon" />
+              <p>{{ process.name }}</p>
+            </div>
+          </div>
+        </van-collapse-item>
+      </template>
+    </van-collapse>
+ </div>
+</template>
+
+<script>
+export default {
+  props: {
+    applyList: {
+      type: [Array, Object],
+      default() {
+        return [];
+      },
+    },
+  },
+  data() {
+    return {
+      activeNames: [0],
+    };
+  },
+  methods: {
+    onHref(process) {
+      this.$router.push({
+        path: "/approvalApply",
+        query: {
+          processId: process.id,
+          name: process.name
+        },
+      });
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.workflow {
+  padding: 0 0.16rem .12rem;
+  .icon {
+    // border: 1px solid #666;
+    // padding: 0.1rem;
+    // border-radius: 50%;
+    // width: .4rem;
+    // height: .4rem;
+    font-size: .4rem;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-bottom: 0.03rem;
+  }
+  .title {
+    display: flex;
+    align-items: center;
+    font-size: 0.16rem;
+    line-height: 0.28rem;
+    font-weight: 500;
+    color: #333333;
+
+    &::before {
+      content: " ";
+      width: 0.04rem;
+      height: 0.17rem;
+      background: #01c1b5;
+      display: inline-block;
+      margin-right: 0.07rem;
+      border-radius: 8px;
+    }
+  }
+
+  .grid {
+    display: flex;
+    flex-wrap: wrap;
+    align-items: flex-start;
+    margin-top: 0.08rem;
+    .grid-item {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-items: center;
+      justify-content: center;
+      padding: 0 0 0.15rem;
+      width: 25%;
+      color: #666;
+      p {
+        width: 96%;
+        text-align: center;
+        padding: 0.05rem 2% 0;
+        font-size: 12px;
+      }
+    }
+  }
+
+  /deep/.van-collapse-item {
+    margin-bottom: .1rem;
+    border-radius: 0.05rem;
+    overflow: hidden;
+  }
+}
+</style>

+ 206 - 0
src/views/message/modal/formModal.vue

@@ -0,0 +1,206 @@
+<template>
+    <div>
+    <!-- <van-form ref="generateForm" validate-first @submit="onSubmit" :scroll-to-error="true" :show-error="false"> -->
+        <template v-if="formData && formData.list">
+            <div v-for="(item, index) in formData.list" :key="index">
+                <template v-if="item.type == 'input' && !item.hidden">
+                    <o-input :widget="item" :preview="preview" :value="value" />
+                </template>
+                <template v-if="item.type == 'textarea' && !item.hidden">
+                    <o-textarea :widget="item" :preview="preview" :value="value" />
+                </template>
+                <template v-if="item.type == 'number' && !item.hidden">
+                    <o-number :widget="item" :preview="preview" :value="value" />
+                </template>
+                <template v-if="item.type == 'radio' && !item.hidden">
+                    <o-radio :widget="item" :preview="preview" :value="value" />
+                </template>
+                <template v-if="item.type == 'checkbox' && !item.hidden">
+                    <o-checkbox :widget="item" :preview="preview" :value="value" />
+                </template>
+                <template v-if="item.type == 'date' && !item.hidden">
+                    <o-date :widget="item" :preview="preview" :value="value" />
+                </template>
+                <template v-if="item.type == 'rate' && !item.hidden">
+                    <o-rate :widget="item" :preview="preview" :value="value" />
+                </template>
+                <template v-if="item.type == 'select' && !item.hidden">
+                    <o-select :widget="item" @relationFormChange="relationFormChange" :preview="preview" :value="value" />
+                </template>
+                <template v-if="item.type == 'organ' && !item.hidden">
+                    <o-organ :widget="item" :preview="preview" :value="value" />
+                </template>
+                <template v-if="item.type == 'school' && !item.hidden">
+                    <o-school :widget="item" :preview="preview" :value="value" />
+                </template>
+                <template v-if="item.type == 'text' && !item.hidden">
+                    <o-text :widget="item" :preview="preview" :value="value" />
+                </template>
+                <template v-if="item.type == 'imgupload' && !item.hidden">
+                    <o-img-upload :widget="item" :preview="preview" :value="value" />
+                </template>
+                <template v-if="item.type == 'file' && !item.hidden">
+                    <o-file-upload :widget="item" @fileCheckRequired="fileCheckRequired" :preview="preview" :value="value" />
+                </template>
+                <template v-if="item.type == 'divider' && !item.hidden">
+                    <o-divider :widget="item" :preview="preview" :value="value" />
+                </template>
+                <template v-if="item.type == 'cascader' && !item.hidden">
+                    <o-cascader :widget="item" :preview="preview" :value="value" />
+                </template>
+                <template v-if="item.type == 'subform'">
+                    <div v-show="!item.hidden">
+                        <o-subform ref="subform" :widget="item" :fileCheck="fileCheck" @relationFormChange="relationFormChange"  @fileCheckRequired="fileCheckRequired" :preview="preview" :value="value" />
+                    </div>
+                </template>
+            </div>
+        </template>
+        <!-- <van-button round block type="info" native-type="submit">提交</van-button> -->
+    <!-- </van-form> -->
+    </div>
+</template>
+<script>
+import OInput from '../control/input'
+import OTextarea from '../control/textarea'
+import ONumber from '../control/number'
+import ORadio from '../control/radio'
+import OCheckbox from '../control/checkbox'
+import ODate from '../control/date'
+import ORate from '../control/rate'
+import OSelect from '../control/select'
+import OOrgan from '../control/organ'
+import OSchool from '../control/school'
+import OText from '../control/text'
+import OImgUpload from '../control/imgFile'
+import OFileUpload from '../control/fileUpload'
+import ODivider from '../control/divider'
+import OCascader from '../control/cascader'
+import OSubform from '../control/subform'
+export default {
+    name: 'formModal',
+    components: { OInput, OTextarea, ONumber, ORadio, OCheckbox, ODate, ORate, OSelect, OOrgan, OSchool, OText, OImgUpload, OFileUpload, ODivider, OCascader, OSubform },
+    props: {
+        value: {
+            type: Object,
+            default() {
+                return {}
+            }
+        },
+        formData: {
+            type: Object,
+            default() {
+                return {}
+            }
+        },
+        preview: { // 是否是预览
+            type: Boolean,
+            default() {
+                return false
+            }
+        }
+    },
+    data() {
+        return {
+            formStatus: false,
+            // 如果是乐团退费且上传文件,则子表单不做校验(true 为已上传文件,且是退费模板)
+            fileCheck: false
+        }
+    },
+    mounted() {
+        if(!this.preview) {
+            let tempData = this.formData
+            // 获取对应模板中,需要隐藏的字段
+            let hiddenFormList = this.getSelectValueObject(tempData)
+            this.formStatus = hiddenFormList
+            this.relationFormChange()
+        }
+    },
+    methods: {
+        getSelectValueObject(tpls) {
+            let tempList = tpls.list || []
+            let status = false
+            tempList.forEach(list => {
+                if(list.type == 'select' && list.options.relationStatus) {
+                   status = true
+                }
+                if(list.type == 'subform') {
+                    let childList = list.columns || []
+                    childList.forEach(child => {
+                        let childList = child.list || []
+                        childList.forEach(c => {
+                            if(c.type == 'select' && list.options.relationStatus) {
+                                status = true
+                            }
+                        })
+                    });
+                }
+            })
+            return status
+        },
+        relationFormChange(value) {
+            // false 为显示,true 为隐藏
+            let temp = value || []
+            this.formData.list?.forEach(item => {
+                // console.log(!item.options.relationStatus, 'relationStatus')
+                if(this.formStatus) {
+                    if(item.type != 'text' && item.type != 'subform' && !item.options.relationStatus) {
+                        item.hidden = true
+                    } else {
+                        item.hidden = false
+                    }
+                    if(temp.includes(item.model)) {
+                        item.hidden = false
+                    }
+                } else {
+                    item.hidden = false
+                }
+                // 子表单  不支持子表单,根据不同的类型显示不同
+                if(item.type == 'subform') {
+                    let childList = item.columns || []
+                    childList.forEach(child => {
+                        if(child.list?.length > 0) {
+                            child.list.forEach(c => {
+                                // c.hidden = true
+                                if(this.formStatus) {
+                                    if(c.type != 'text' && c.type != 'subform' && !c.options.relationStatus) {
+                                        c.hidden = true
+                                    } else {
+                                        c.hidden = false
+                                    }
+                                    if(temp.includes(c.originModel)) {
+                                        c.hidden = false
+                                    }
+                                } else {
+                                    c.hidden = false
+                                }
+                            })
+                        }
+                    });
+                    // 重置数据
+                    let subForm = this.$refs.subform
+                    subForm.forEach(item => {
+                        item.reSetFormData()
+                    })
+                    let subFormStatus = true
+                    childList.forEach(child => {
+                        if(child.list?.length > 0) {
+                            child.list.forEach(c => {
+                                if(!c.hidden) {
+                                    subFormStatus = false
+                                }
+                            })
+                        }
+                    })
+                    item.hidden = subFormStatus
+                }
+            })
+            this.$forceUpdate()
+        },
+        // 处理是否需要校验
+        fileCheckRequired(state) {
+            this.fileCheck = state
+            this.$forceUpdate()
+        }
+    }
+}
+</script>

+ 240 - 0
src/views/message/modal/transferModal.vue

@@ -0,0 +1,240 @@
+<template>
+    <van-form validate-first @submit="onSubmit" :show-error="false" class="popup-transfer controller">
+        <van-field
+            :label="'节点'"
+            clickable
+            :placeholder="'请选择节点'"
+            clear-trigger="always"
+            v-model="node_txt"
+            readonly
+            name="node_id"
+            required
+            @click="onSelect('node')"
+            :rules="[{ required: true, message: '请选择节点', trigger: 'onSubmit' }]"
+        >
+            <template #right-icon>
+                <i class="van-icon van-icon-arrow van-cell__right-icon"></i>
+            </template>
+        </van-field>
+        <van-field
+            :label="'用户'"
+            clickable
+            readonly
+            :placeholder="'请选择用户'"
+            clear-trigger="always"
+            v-model="user_txt"
+            name="user_id"
+            required
+            @click="showSelectUser = true"
+            :rules="[{ required: true, message: '请选择用户', trigger: 'onSubmit' }]"
+        >
+            <template #right-icon>
+                <i class="van-icon van-icon-arrow van-cell__right-icon"></i>
+            </template>
+        </van-field>
+        <van-field
+            label="备注"
+            name='remarks'
+            v-model="ruleForm.remarks"
+            type="textarea"
+            clearable
+            row="5"
+            :autosize=" { maxHeight: 120, minHeight: 80 }"
+            :placeholder="'请输入备注'"
+        />
+        <van-button type="info" round native-type="submit">确认</van-button>
+
+        <van-popup v-model="showSelect" position="bottom">
+            <van-picker
+                show-toolbar
+                :columns="popupList"
+                @confirm="onConfirm"
+                :default-index="defaultIndex"
+                visible-item-count="4"
+                @cancel="showSelect = false"
+            />
+        </van-popup>
+
+        <van-popup v-model="showSelectUser" position="bottom" style="height: 60%">
+            <search class="transferSearch" @onSearch="onSearch" placeholder="请输入用户名" />
+            <div class="userList">
+                <template v-for="(item, index) in columnList">
+                    <div class="userItem" v-show="!item.hidden" :key="index" @click="onTeacherSelect(item)">
+                        <div class="userItem-title">
+                            <img v-if="item.avatar" class="logo" :src="item.avatar" fit="cover" />
+                            <img
+                                v-else
+                                class="logo"
+                                src="@/assets/images/icon_teacher.png"
+                                alt=""
+                            />
+                        </div>
+                        <div class="userItem-content">
+                            <div class="userItem-content-name">
+                                <span>{{item.text}}</span>
+                            </div>
+                            <div class="userItem-content-desc">
+                                <span>{{item.phone}}</span>
+                            </div>
+                        </div>
+                    </div>
+                </template>
+            </div>
+            <!-- <van-picker
+                show-toolbar
+                :columns="popupList"
+                @confirm="onConfirm"
+                :default-index="defaultIndex"
+                visible-item-count="4"
+                @cancel="showSelect = false"
+            /> -->
+        </van-popup>
+    </van-form>
+</template>
+<script>
+import search from '@/components/Search.vue'
+import { inversionWorkOrder } from '../api'
+export default {
+    name: 'transfer',
+    props: ['popupForm', 'columns', 'nodeList'],
+    components: { search },
+    data() {
+        return {
+            node_txt: null,
+            nodeIndex: 0,
+            user_txt: null,
+            userIndex: 0,
+            ruleForm: {
+                work_order_id: '',
+                node_id: '',
+                user_id: '',
+                remarks: ''
+            },
+            showSelect: false,
+            showSelectUser: false,
+            popupList: [],
+            popupType: null,
+            defaultIndex: 0,
+            columnList: []
+        }
+    },
+    async mounted() {
+        this.columnList = this.columns
+        this.ruleForm = this.popupForm
+        if(this.ruleForm.node_id) {
+            this.nodeList.forEach(item => {
+                if(item.id == this.ruleForm.node_id) {
+                    this.node_txt = item.text
+                }
+            })
+        }
+    },
+    methods: {
+        onSearch(val) {
+            this.columnList.forEach(item => {
+                if(val) {
+                    if(item.text.indexOf(val) != -1) {
+                        item.hidden = false
+                    } else {
+                        item.hidden = true
+                    }
+                } else {
+                    item.hidden = false
+                }
+            })
+            this.$forceUpdate()
+        },
+        async onSubmit() {
+            try {
+                await inversionWorkOrder({ ...this.ruleForm })
+                this.$toast('转交成功')
+                this.$router.replace({
+                    path: '/myApproval',
+                    query: {
+                        classify: this.$route.query.classify || 2
+                    }
+                })
+            } catch {
+                //
+            }
+        },
+        onTeacherSelect(item) {
+            this.ruleForm.user_id = item.id
+            this.user_txt = item.text
+            this.showSelectUser = false
+        },
+        onConfirm(value, index) {
+            if(this.popupType == 'users') {
+                this.userIndex = index
+                this.ruleForm.user_id = value.id
+                this.user_txt = value.text
+            } else if(this.popupType == 'node') {
+                this.nodeIndex = index
+                this.ruleForm.node_id = value.id
+                this.node_txt = value.text
+            }
+            this.showSelect = false
+        },
+        onSelect(type) {
+            this.popupType = type
+            if(type == 'users') {
+                this.popupList = this.columns
+                this.defaultIndex = this.userIndex
+            } else if(type == 'node') {
+                this.popupList = this.nodeList
+                this.defaultIndex = this.nodeIndex
+            }
+            this.showSelect = true
+        }
+    }
+}
+</script>
+
+<style lang="less" scoped>
+@import url('../control/controlCommon.less');
+.popup-transfer {
+    /deep/.van-cell {
+        font-size: .14rem;
+    }
+    /deep/.van-button {
+        width: 90%;
+        margin-top: .12rem;
+        margin-left: 5%;
+        font-size: .16rem;
+    }
+
+    .transferSearch {
+        /deep/.van-field {
+            flex-direction: row;
+            padding: 5px 8px 5px 0;
+        }
+        /deep/.van-field__value {
+            padding: 0;
+        }
+    }
+
+    .userList {
+        height: calc(100% - 70px);
+        overflow-y: auto;
+        overflow-x: hidden;
+
+        .logo {
+            width: 0.42rem;
+            height: 0.42rem;
+            margin-right: 0.12rem;
+            border-radius: 100%;
+        }
+        .userItem {
+            padding: 0.08rem 0.15rem 0;
+            display: flex;
+            align-items: center;
+        }
+        .userItem-content-name {
+            font-size: .15rem;
+        }
+        .userItem-content-desc {
+            font-size: .12rem;
+        }
+    }
+}
+</style>

+ 376 - 0
src/views/message/myApproval.vue

@@ -0,0 +1,376 @@
+<template>
+  <div class="myApproval">
+    <van-sticky>
+        <!-- <m-header :backUrl="backUrl" :name="name" :isNative="false" :isFixed="false" /> -->
+        <van-dropdown-menu active-color="#01C1B5" :close-on-click-outside="false">
+            <van-dropdown-item :title="value0" ref="treeSelect">
+              <van-tree-select
+                :items="items"
+                :active-id.sync="activeId"
+                :main-active-index.sync="activeIndex"
+                @click-item="clickItem"
+              />
+            </van-dropdown-item>
+            <van-dropdown-item v-model="value1" @change="onSearch" :options="option1" />
+            <van-dropdown-item v-model="value2" @change="onSearch" :options="option2" />
+            <van-dropdown-item title="创建时间" ref="item">
+                <van-cell title="开始时间" is-link @click="onChangeDate('showStart')" :value="searchList.startTime"></van-cell>
+                <van-cell title="结束时间" is-link @click="onChangeDate('showEnd')" :value="searchList.endTime"></van-cell>
+                <div class="btnWrap">
+                    <div class="cancelBtn" @click="cancelBtn">重置</div>
+                    <div class="okBtn" @click="okBtn">确定</div>
+                </div>
+            </van-dropdown-item>
+        </van-dropdown-menu>
+
+        <search @onSearch="onSearchChange"></search>
+
+        <van-popup v-model="dataForm.status" position="bottom" round :style="{ height: '40%' }">
+            <van-datetime-picker
+                v-model="dataForm.currentDate"
+                :min-date="dataForm.minDate"
+                :max-date="dataForm.maxDate"
+                :formatter="formatter"
+                @cancel="dataForm.status = false"
+                type="date"
+                @confirm="chioseDate"
+            />
+        </van-popup>
+        <!-- <van-search placeholder="请输入工单标题" background="#ffffff" @search="onSearch" shape="round" show-action v-model="searchList.title">
+            <div slot="action" @click="onSearch">搜索</div>
+        </van-search> -->
+    </van-sticky>
+
+    <!-- 列表 -->
+    <div>
+      <van-list
+        v-model="loading"
+        v-if="dataShow"
+        key="data"
+        :finished="finished"
+        style="margin-top: .1rem;"
+        :immediate-check="false"
+        finished-text="- 没有更多内容 -"
+        @load="getList"
+      >
+        <van-cell-group class="app-item" v-for="(item, index) in dataList" :key="index" @click="onClick(item)">
+          <van-cell class="appItem-title" :border="false" style="padding-bottom: 0" :title="item.title" :value="dayjs(item.create_time).format('YYYY-MM-DD HH:mm')"></van-cell>
+          <van-cell>
+            <template #title>
+              <!-- <p>标题:{{ item.title }}</p> -->
+              <p>优先级:
+                <van-tag size="small" v-if="item.priority == 2" style="background: #fff9f6;vertical-align: text-bottom;" plain type="warning">紧急</van-tag>
+                <van-tag size="small" v-if="item.priority == 3" style="background: #fff6f7;vertical-align: text-bottom;" plain type="danger">非常紧急</van-tag>
+                <van-tag size="small" v-if="item.priority == 1" style="background: #edfff6;vertical-align: text-bottom;" plain type="success">一般</van-tag>
+                <!-- {{ item.priority | statusFilter }} -->
+              </p>
+              <div class="info van-hairline--top">
+                <span>由 <span style="color: #01C1B5">{{ item.creator_name }}</span> 提交</span>
+                 <template v-if="item.is_end">
+                  <template v-if="item.is_cancel">
+                    <van-tag color="#999999" size="medium" plain>已关闭</van-tag>
+                  </template>
+                  <template v-else>
+                    <van-tag color="#FF4444" style="background: #FFFBFB;" size="medium" v-if="item.is_denied" plain>审批拒绝</van-tag>
+                    <van-tag color="#00C988" style="background: #F1FCF9" size="medium" v-else plain>审批通过</van-tag>
+                  </template>
+                </template>
+                <template v-else>
+                  <p class="orangeStatus" style="display: flex;" v-if="item.principals">
+                    <span style="width: 100px;white-space: nowrap;overflow: hidden;display: inline-block;text-overflow: ellipsis;text-align:right;">{{ item.principals }}</span>处理中
+                  </p>
+                </template>
+              </div>
+            </template>
+          </van-cell>
+        </van-cell-group>
+      </van-list>
+      <m-empty v-else key="data" />
+    </div>
+  </div>
+</template>
+<script>
+import MHeader from "@/components/MHeader";
+import Search from '@/components/Search'
+import { getWorkOrderList, classify } from "./api";
+import MEmpty from "@/components/MEmpty";
+import dayjs from "dayjs";
+import setLoading from '@/common/loading'
+export default {
+  components: { MHeader, MEmpty, Search },
+  data() {
+    let query = this.$route.query
+    return {
+      dayjs,
+      backUrl: {
+        status: true,
+        path: '/approval'
+      },
+      classify: query.classify,
+      value1: 0,
+      value2: -1,
+      option1: [
+        { text: '优先级', value: 0 },
+        { text: '一般', value: 1 },
+        { text: '紧急', value: 2 },
+        { text: '非常紧急', value: 3 },
+      ],
+      option2: [
+          { text: '是否结束', value: -1 },
+          { text: '是', value: 1 },
+          { text: '否', value: 0 }
+      ],
+      dataForm: {
+          // 时间下拉框
+          status: false,
+          minDate: new Date(2000, 0, 1),
+          maxDate: new Date(2025, 10, 1),
+          currentDate: new Date(),
+      },
+      items: [],
+      activeIndex: 0,
+      value0: '全部',
+      activeId: 0,
+      searchList: {
+        search: null,
+        startTime: null,
+        endTime: null,
+        page: 1,
+        per_page: 10,
+        classify: query.classify,
+        title: null,
+      },
+      loading: false,
+      finished: false,
+      dataShow: true,
+      dataList: [],
+      name:''
+    };
+  },
+  async mounted() {
+    let classifyStr = this.classify;
+    if (classifyStr == 1) {
+      this.name =  "需我审批"
+    } else if (classifyStr == 2) {
+         this.name = "我发起的"
+    } else if (classifyStr == 3) {
+         this.name =  "我相关的"
+    }
+    document.title = this.name
+    try {
+      let res = await classify()
+      let tempList = res.data || []
+      let lastArr = [{ text: '全部', children: [{text: '全部', id: 0}] }]
+      tempList.forEach((item, index) => {
+        lastArr[index + 1] = {
+          text: item.name,
+          children: []
+        }
+        if(item.process_list && item.process_list.length > 0) {
+          item.process_list.forEach(child => {
+            lastArr[index + 1].children.push({
+              text: child.name,
+              id: child.id
+            })
+          })
+        }
+      });
+      this.items = lastArr
+    } catch(e) {
+        //
+    }
+    setLoading(true)
+    await this.getList();
+    setLoading(false)
+  },
+  methods: {
+    clickItem(val) {
+      this.value0 = val.text;
+      this.$refs.treeSelect.toggle();
+      this.onSearch()
+    },
+    onSearchChange(val) {
+      this.searchList.search = val
+      this.onSearch()
+    },
+    onSearch() {
+      this.dataList = [] // 重置数据
+      this.dataShow = true
+      this.loading = true
+      this.searchList.page = 1
+      this.finished = false
+      this.getList()
+    },
+    onClick(item) {
+      this.$router.push({
+        path: '/ApprovalAction',
+        query: {
+          // classify: this.classify,
+          workOrderId: item.id,
+          processId: item.process,
+          // title: `${item.creator_name}提交的${item.process_name}`,
+          // principals: item.principals + '处理中'
+        }
+      })
+    },
+    async getList() {
+      let { ...params } = this.searchList;
+      params.endTime = params.endTime ? dayjs(params.endTime).format('YYYY-MM-DD') + ' 23:59:59' : null
+      params.priority = this.value1 ? this.value1 : null
+      params.isEnd = this.value2 != -1 ? this.value2 : null
+      params.processId = this.activeId ? this.activeId : null // 分类id
+      const userId = sessionStorage.getItem('userId')
+      params.userId = userId
+
+      try {
+        const res = await getWorkOrderList(params)
+        let result = res.data
+        this.loading = false
+        // 重点这句,判断是不是重复请求了
+        if (this.dataList.length > 0 && result.total_page == 1) {
+            return;
+        }
+        let data = result.data || []
+        this.dataList.push(...data)
+        if(params.page >= Math.ceil(result.total_count / params.per_page)) {
+            this.finished = true
+        }
+        this.searchList.page++
+        if(this.dataList.length <= 0) {
+            this.dataShow = false
+        }
+      } catch {
+        //
+        this.finished = true
+      }
+    },
+    cancelBtn() {
+        this.searchList.startTime = null;
+        this.searchList.endTime = null;
+        this.onSearch()
+        this.$refs.item.toggle();
+    },
+    okBtn() {
+        if(this.searchList.startTime || this.searchList.endTime) {
+            this.onSearch()
+        }
+        this.$refs.item.toggle();
+    },
+    onChangeDate(type) {
+        let dataForm = this.dataForm
+        let searchList = this.searchList
+        if(type == 'showEnd') {
+            if(searchList.startTime) {
+            dataForm.minDate = new Date(dayjs(searchList.startTime))
+            }
+            setTimeout(() => {
+                dataForm.currentDate = searchList.endTime ? new Date(dayjs(searchList.endTime)) : new Date()
+            }, 500)
+        } else if(type == 'showStart') {
+            dataForm.minDate = new Date(2000, 0, 1)
+            setTimeout(() => {
+                dataForm.currentDate = searchList.startTime ? new Date(dayjs(searchList.startTime)) : new Date()
+            }, 500)
+        }
+        dataForm.status = true
+        dataForm.type = type
+    },
+    chioseDate(value) {
+        let dataForm = this.dataForm
+        if(dataForm.type == 'showStart') {
+            this.searchList.startTime = dayjs(value).format('YYYY-MM-DD')
+            if(this.searchList.endTime && dayjs(value).unix() > dayjs(this.searchList.endTime).unix()) {
+            this.searchList.endTime = null
+            }
+        } else if(dataForm.type == 'showEnd') {
+            this.searchList.endTime = dayjs(value).format('YYYY-MM-DD')
+        }
+        dataForm.status = false
+    },
+    formatter(type, val) {
+      if (type === "year") {
+        return `${val}年`;
+      } else if (type === "month") {
+        return `${val}月`;
+      } else if (type == "day") {
+        return `${val}日`;
+      }
+      return val;
+    }
+  },
+  filters: {
+    statusFilter(item) {
+      const obj = {
+        1: "一般",
+        2: "紧急",
+        3: "非常紧急",
+      };
+      return obj[item];
+    },
+  },
+};
+</script>
+<style lang="less" scoped>
+@import url("../../assets/commonLess/variable.less");
+.myApproval {
+  min-height: 100vh;
+}
+.btnWrap {
+  display: flex;
+  flex-direction: row;
+  .cancelBtn {
+    height: 48px;
+    line-height: 48px;
+    background: #e1f6f4;
+    color: @mColor;
+    text-align: center;
+    width: 100%;
+  }
+  .okBtn {
+    width: 100%;
+    height: 48px;
+    line-height: 48px;
+    background: @mColor;
+    color: #fff;
+    text-align: center;
+  }
+}
+/deep/.van-dropdown-menu__title {
+  font-size: .15rem;
+}
+/deep/.van-tree-select__item--active {
+  color: #01c1b5;
+}
+/deep/.van-sidebar-item--select::before {
+  background-color: #01c1b5;
+}
+
+.app-item {
+  margin: 0 0.12rem 0.1rem;
+  background: @whiteColor;
+  border-radius: 0.1rem;
+  font-size: 0.13rem;
+  .appItem-title {
+    /deep/.van-cell__title {
+      flex-basis: 65%;
+      color: #333;
+      font-size: .16rem;
+    }
+    /deep/.van-cell__value {
+      flex-basis: 35%;
+    }
+  }
+  .info {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    color: #333;
+    font-size: .14rem;
+    padding-top: .1rem;
+    margin-top: .1rem;
+  }
+  /deep/.van-cell {
+    color: #666666;
+  }
+}
+</style>

+ 323 - 323
src/views/teacher/privacy.vue

@@ -1,324 +1,324 @@
-<template>
-    <div class="privacy">
-        <div class="container">
-            <h1 v-if="headerStatus">用户隐私协议</h1>
-            <strong>发布日期:2019-11-22</strong><br />
-            <strong>最新更新日期:2021-05-24</strong><br />
-
-            管乐迷(下称“管乐迷”或“本公司”)尤其重视用户(下称“用户”或“您”)的隐私和个人信息保护。《隐私保护政策》(下称“本政策”)旨在向用户说明本公司如何收集、使用、处理、存储用户个人信息以及为用户提供访问、删除、更正等处理事宜。<br />
-
-            <strong class="line">在使用本公司提供的软件及相关服务前,请您务必仔细阅读并充分理解本政策,尤其是以加粗、下划线等标识的条款。当您使用本公司提供的软件及相关服务时,即表示您已经同意本公司按照本政策来收集、使用、共享和保护您的相关个人信息;用户不同意本政策的任何条款或内容的,请立即停止使用本公司提供的软件及相关服务。</strong><br />
-
-            <strong class="line">【针对未成年用户及其监护人的特别提示】</strong><br />
-            <strong class="line">如果您是未满18周岁的未成年人,应在您的监护人的陪同下阅读本政策,并在监护人的解释和指导下理解本政策,在使用本公司提供的软件及服务或者提交信息之前获得您的监护人的指导并获得同意。未成年用户或者未成年用户的监护人点击同意本政策,或者未成年用户使用本公司提供的软件及服务,或者提交个人信息的,即表示未成年用户已经获得其监护人的同意,即监护人同意本公司按照本政策收集、使用、分享、转让、披露和存储未成年用户的个人信息。如果未成年用户或其监护人不同意本政策的任何条款或内容的,请立即停止使用本公司提供的软件及相关服务。</strong><br />
-            <!-- <strong class="line">【针对未成年用户及其监护人的特别提示】</strong><br /> -->
-
-            <!-- <strong class="line">向儿童用户的特别提示:</strong><br /> -->
-            <strong class="line">如果您是未满14周岁的儿童用户,请务必通知您的监护人并共同阅读本公司的《儿童个人信息保护规则》,并务必在使用本公司提供的软件及服务或者提交信息之前获得监护人的指导并获得同意。</strong><br />
-
-            本公司收集、使用、共享和保护用户的个人信息,是在遵守国家法律法规规定的前提下,仅出于本政策所述的以下目的,遵循正当必要、知情同意、目的明确、安全保障、依法利用的原则收集、存储、使用、转移、披露儿童个人信息。<br />
-            如果您对本政策有任何疑问的,请通过本政策中公布的联系方式与本公司联系。<br />
-            <!-- 本公司联系方式:【】联系人:【】<br /> -->
-
-            <h2>一、本公司收集和使用儿童用户个人信息</h2>
-            个人信息是指以电子或者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息。<br />
-            1. 用户账户信息:<br />
-            用户使用本公司提供的软件及相关服务前,需要在本公司提供的软件注册账户,此时我们需要您主动填写和提供注册信息,包括姓名、性别、出生日期、所在学校、所在班级、手机号码、家庭住址、创建的用户名(ID)等信息,这些信息是本公司为用户提供考勤、教学、排课、陪练、打分等服务所必要的,如果您不提供给本公司,您可能无法使用本公司提供的软件或相关服务。<br />
-
-            2. 用户使用本公司提供的软件及相关服务信息:<br />
-            用户通过电脑、平板电脑或手机等智能设备访问本公司提供的软件及其相关服务时,服务器会自动记录用户的登录IP地址、网络服务商、接入网络的方式、类型及状态、网络质量数据、使用的语言、设备类别、设备型号、设备识别码、操作系统、分辨率、访问日期及时间长度等信息,以及用户登录和使用本公司提供的软件及相关服务时,用户使用的设备所在的地理位置信息。<br />
-            用户使用本公司提供的软件及相关服务参加直播教学、录播教学等课程时,我们会收集用户提交的信息以及系统自动记录和存储的信息,包括但不限于视频、录像、音频、照片、截图以及这些信息产生时对应的日期、时间、地点等。<br />
-
-            3. 交易信息和付款信息:<br />
-            用户使用本公司提供的软件及相关服务的过程中,购买课程或其他服务需要向本公司进行支付,<strong class="line">本公司建议由未成年用户的监护人完成支付交易的整个过程,如果支付交易是由未成年用户进行,则应当在向本公司提供支付信息之前,获得其监护人的同意、授权和指导。</strong>本公司会收集支付交易时使用的支付工具、支付账户、银行卡号、开户银行、支付状态信息,支付日期等信息。<strong class="line">如果您进行支付交易时直接跳转至第三方支付页面的,该第三方将会直接收集您的支付信息,此时您的支付信息的收集、使用、存储等规则将遵循该第三方的隐私政策,请您注意事先仔细查阅。</strong>如果您符合本公司相关服务协议里约定的退费情形,并要求退费时,本公司需要您提供姓名、银行卡号、开户银行等信息。<br />
-
-            4. 用户因请假、打卡签到或其他原因通过电话、电子邮件或其他线上或线下渠道与本公司取得联系时,本公司会收集和处理该用户向本公司提供或者反馈的信息,用户的姓名、联系方式,以及本公司为了处理该用户的请求而必须向其确认的事项,并可能会保留与用户的通话、通信记录,以便本公司及时与该用户联系和沟通并处理。<br />
-
-            5. 本公司还可能为了改进本公司服务,提高教学和服务质量等需要,用户联系和沟通,并在此过程中收集沟通时提供的相关信息。<br />
-
-            6. 本公司在教学和服务过程中必要时向用户发出与教学或服务有关的通知或推送。<br />
-
-            7. 检测和防止欺诈和IT安全威胁所需的信息:本公司收集检测,调查和预防欺诈,作弊、IT安全威胁、网络漏洞、黑客攻击、计算机病毒和其他违反用户协议和适用法律的行为所需的某些信息。此数据仅用于检测,调查,预防以及在适用的情况下对此类行为进行处理,并且仅在此目的所需的最短时间内存储。如果披露信息会影响本公司检测、调查、防止此类行为的机制,则可能无法向您披露为此目的而存储的特定信息。<br />
-
-            8. 如本公司要向用户的个人信息用于本政策未载明的其他用途时,将事先获得用户的同意;如本公司将基于特定目的而收集的用户个人信息用于其他目的时,将事先获得用户的同意。<br />
-
-            9. 为了保障您的账号安全、交易安全以及系统运行安全,满足法律法规和我们协议规则的相关要求,在您使用我们的产品/服务过程中,经您授权我们会获取您的设备信息,包括您使用的设备属性、连接和状态信息,例如设备型号、设备标识符(如IMEI/androidID/IDFA/OPENUDID/GUID/OAID、SIM卡IMSI、ICCID信息等)、设备MAC地址、软件列表、电信运营商等软硬件特征信息。<br />
-
-            10. 我们可能间接收集的个人信息<br />
-            您可以选择授权我们收集和使用个人信息的场景,为向您提供个性化的服务,您可以选择使用我们提供的拓展功能,我们会在符合法律规定并根据您具体授权的情况下收集并使用如下信息。这类信息将在您选择的具体功能和业务场景中进行收集,如果您不提供这些信息,不会影响您使用管乐迷的基本功能。<br />
-            10.1 您需要授权我们收集和使用个人信息的场景<br />
-            10.1.1 基于相机授权的拓展功能<br />
-            您可以选择开启系统的相机权限,通过使用拍照、录视频等功能授权管乐迷访问您的相机,以便于您通过拍摄照片或录制视频等方式发布内容,如果您需要录制并发布有声视频时,您还需开启麦克风权限。我们会收集您上传发布的上述信息,此项功能您可以在系统权限中关闭,一旦关闭您将可能无法通过拍摄图片、视频等方式进行更换头像、上传作业等,但不会影响您享受管乐迷的基本功能。<br />
-            10.1.2 基于相册授权的拓展功能<br />
-            您可以选择开启系统的相册权限,通过主动上传图片、视频等方式授权我们访问您的相册,以便于您通过上传照片或上传视频等方式发布内容。我们会收集您选择上传发布的上述信息,此项功能您可以在系统权限中关闭,一旦关闭您将可能无法通过上传图片、视频等方式进行更换头像、上传作业等,但不会影响您享受管乐迷的基本功能。<br />
-            10.1.3 基于麦克风授权的拓展功能<br />
-            您可以选择开启系统的麦克风权限,使用语音技术来实现录制音频作业等语音功能。我们会收集您在使用录音功能中录入的语音信息用于老师查看音频作业。此项功能您可以在系统权限中关闭,一旦关闭您将可能无法使用音频作业功能,但不会影响您享受管乐迷的基本功能。<br />
-            10.2 其他<br />
-            如果我们将信息用于本政策未载明的其他用途,或者将基于特定目的收集而来的信息用于其他目的时,会单独征求您的授权同意。<br />
-            10.3 征得同意的例外<br />
-            请您知悉,以下情形中,我们收集、使用个人信息无需征得您的授权同意:<br />
-            10.3.1 与国家安全、国防安全有关的;<br />
-            10.3.2 与公共安全、公共卫生、重大公共利益有关的;<br />
-            10.3.3 与犯罪侦查、起诉、审判和判决执行等有关的;<br />
-            10.3.4 出于维护个人信息主体或其他个人的生命、财产等重大合法权益但又很难得到本人同意的;<br />
-            10.3.5 所收集的个人信息是个人信息主体自行向社会公众公开的;<br />
-            10.3.6 从合法公开披露的信息中收集的您的个人信息的,如合法的新闻报道、政府信息公开等渠道;但是您明确拒绝或者处理该信息侵害您重大利益的除外。<br />
-            10.3.7 根据您的要求签订合同所必需的;<br />
-            10.3.8 用于维护所提供的产品与/或服务的安全稳定运行所必需的,例如发现、处置产品与/或服务的故障;<br />
-            10.3.9 为合法的新闻报道所必需的;<br />
-            10.3.10 学术研究机构基于公共利益开展统计或学术研究所必要,且对外提供学术研究或描述的结果时,对结果中所包含的个人信息进行去标识化处理的;<br />
-            10.3.11 法律法规规定的其他情形。<br />
-            请注意,单独或与其他信息相结合无法识别您的身份或者与您直接建立联系的信息,不属于个人信息。如果我们将单独无法与任何特定个人建立联系的信息与其他信息结合用于识别自然人个人身份,或者将其与个人信息结合使用,则在结合使用期间,此类信息将被视为个人信息。<br />
-            10.4 设备权限调用<br />
-            为确保相关业务功能的正常实现,我们需要根据具体的使用场景调用对应的必要权限,并在调用前向您弹窗询问,具体的权限调用说明请见下表:<br />
-            <table border="1">
-                <tr>
-                    <th>设备权限</th>
-                    <th>使用目的</th>
-                    <th>是否询问</th>
-                    <th>是否可关闭</th>
-                </tr>
-                <tr>
-                    <td>相机</td>
-                    <td>用于拍照、上传图片、录制视屏</td>
-                    <td>用户主动拍照、上传图片、录制视屏时询问</td>
-                    <td>是</td>
-                </tr>
-                <tr>
-                    <td>麦克风</td>
-                    <td>用户语音消息、音频作业、视屏作业</td>
-                    <td>用户发起语音输入前询问</td>
-                    <td>是</td>
-                </tr>
-                <tr>
-                    <td>照片</td>
-                    <td>用于写入和读取用户相册信息</td>
-                    <td>用户主动上传前询问</td>
-                    <td>是</td>
-                </tr>
-            </table>
-            您可以在设备的设置中选择关闭部分或者全部权限,这可能导致对应的业务功能无法实现或者无法达到预期效果。<br />
-
-            <strong class="line">本公司收集用户的个人信息后,可能会通过技术手段对已经收集的用户个人信息进行匿名化处理,使得用户个人信息主体无法被识别,且处理后的信息不能被复原,此时经过匿名化处理后的信息不再属于用户个人信息。本公司可能会将匿名化处理信息用于测试本公司的IT系统,研究,数据分析,改进服务,以及开发新服务、新产品和功能,以及向第三方分享、转让、披露,此时无需另行获得用户的授权同意。</strong><br />
-
-            <h2>二、本公司共享、转让、披露用户个人信息</h2>
-            1. 共享:<br />
-            1.1在获取用户的明确同意或授权的情况共享;<br />
-            1.2根据法律法规规定,或者按照政府主管部门的强制性要求;<br />
-            1.3为了向用户提供教学或服务,本公司可能会与关联方分享,但是本公司只会分享必要的个人信息,且此时受用户服务协议或本隐私政策中所声明的收集和使用之目的的约束,本公司的关联方如要改变个人信息的处理目的,将再次征求用户的授权同意;<br />
-            1.4本公司还可以与提供与本公司提供的软件及相关服务相关的必要功能和支持服务的第三方(例如,提供或处理支付业务、网络定位服务、推送通知、电子邮件服务、税务和会计服务、分析服务)共享用户的个人信息数据,此时本公司仅会处于合法、正当、必要、特定、明确的目的共享用户的个人信息,并且只会共享提供服务所必要的信息,且此时本公司会与这些第三方主体签订保密协议,要求这些第三方主体按照相关法律法规规定、本政策及其他任何相关的保密和安全措施保护用户的个人信息。您可以通过以下链接查看第三方的数据使用和保护规则。<br />
-            管乐迷接入第三方SDK目录<br />
-            <table border="1">
-                <tr>
-                    <th style="width: .9rem;">使用目的</th>
-                    <th>第三方名称</th>
-                    <th>涉及个人信息</th>
-                    <th>第三方官网链接</th>
-                </tr>
-                <tr>
-                    <td rowspan="5">根据用户机型,为用户提供通知信息推送功能</td>
-                    <td>极光</td>
-                    <td>设备信息</td>
-                    <td style="word-break: break-all;">https://www.jiguang.cn/license/privacy</td>
-                </tr>
-                <tr>
-                    <td>华为</td>
-                    <td>设备信息</td>
-                    <td style="word-break: break-all;">https://developer.huawei.com/consumer/cn/service/hms/pushservice.html</td>
-                </tr>
-                <tr>
-                    <td>小米</td>
-                    <td>设备信息</td>
-                    <td style="word-break: break-all;">https://dev.mi.com/console/doc/detail?pId=1822</td>
-                </tr>
-                <tr>
-                    <td>VIVO</td>
-                    <td>设备信息</td>
-                    <td style="word-break: break-all;">https://www.umeng.com/page/policy</td>
-                </tr>
-                <tr>
-                    <td>OPPO</td>
-                    <td>设备信息</td>
-                    <td style="word-break: break-all;">https://open.oppomobile.com/wiki/doc#id=10194</td>
-                </tr>
-                <tr>
-                    <td>统计分析</td>
-                    <td>友盟SDK</td>
-                    <td>设备信息</td>
-                    <td style="word-break: break-all;">https://open.oppomobile.com/wiki/doc#id=10194</td>
-                </tr>
-            </table>
-            2. 转让:<br />
-            2.1在获取用户的明确同意的情况下转让;<br />
-            2.2本公司发生合并、收购、资产转让或类似交易或者破产清算时,用户信息(包括用户的个人信息)可能会被转让给相关方,此时本公司会要求这些受让方继续遵守本政策的约束,如果这些受让方变更用户信息的使用目的时,应重新获得用户的同意和授权;<br />
-            本公司向第三方转让用户个人信息时,将自行或委托第三方机构进行安全评估。<br />
-            3. 披露:<br />
-            3.1在获取用户的要求或明确同意的情况下披露;<br />
-            3.2根据法律法规规定、法院或政府主管部门的强制性要求时;<br />
-            3.3本公司发生资本市场活动(例如IPO等)时,可能需要接受其他主体的尽职调查,此时本公司可能会根据尽职调查的需要将用户的个人信息提供给必要的主体,但本公司会与实施调查的主体签订保密协议,要求这些第三方主体按照相关法律法规规定、本政策及其他任何相关的保密和安全措施来保护用户的个人信息。<br />
-            4. 本公司在下列情形时,无需获得用户的授权同意,即可分享、转让、披露用户的信息:<br />
-            4.1与国家安全、国防安全相关的;<br />
-            4.2与公共安全、公共卫生、重大公共利益相关的;<br />
-            4.3与犯罪侦查、起诉、审判和判决执行等司法或行政执法相关的;<br />
-            4.4为了维护用户或其他个人的生命和财产等重大合法权利但又很难得到同意的情况下;<br />
-            4.5用户自行向社会公开的信息;<br />
-            4.6从合法公开渠道获得的信息,如新闻报道、政府信息公开等渠道。<br />
-
-            <h2>三、用户个人信息的保护、存储、跨境传输</h2>
-            1. 本公司会努力采取各种符合业界标准的物理、电子和管理方面的安全措施来保护用户的个人信息,防止用户的个人信息遭到未经授权访问、公开披露、使用、修改、损坏或丢失。本公司会采取一切合理可行的措施,保护用户的个人信息。<br />
-            2. 本公司会采取加密等措施存储用户的个人信息,确保信息安全,并以最小授权为原则对本公司工作人员严格设置信息访问权限,控制用户个人信息知悉范围。工作人员需要访问用户个人信息的,应当经过本公司用户个人信息保护负责人或者其授权的管理人员审批,并且记录访问情况,采取技术措施,避免违法复制、下载用户个人信息。<br />
-            3. 本公司将会采取一切合理可行的措施,确保未收集无关的用户个人信息,并只会在达成本政策所述目的所需的期限内存储和保留用户的个人信息,不会超过本政策所述目的所必需的期限,除非需要延长保留期或受到法律的允许。<br />
-            4. 互联网并非绝对安全的环境,而且电子邮件、即时通讯、社交软件或其他服务软件等与其他用户交流的方式无法确定是否完全加密,因此本公司建议用户使用此类工具时注意保护信息安全,并使用复杂密码,协助本公司保证用户的账户安全。<br />
-            5. 互联网环境并非百分之白安全,本公司将尽力确保或担保用户发送给本公司的任何信息的安全性。如果本公司的物理、技术,或管理防护设施遭到破坏,导致信息被非授权访问、公开披露、篡改,或破坏,导致您的合法权益受损,本公司将承担相应的法律责任。<br />
-            6. 本公司发现用户个人信息发生或可能发生泄露、毁损、灭失的,本公司将将立即启动应急预案,采取补救措施;造成或者可能造成严重后果的,会立即向有关主管部门报告,并将事件相关情况以邮件、信函、电话、推送通知等方式告知受影响的用户,难以逐一告知的,将会采取合理、有效的方式发布相关警示信息。包括但不限于安全事件的基本情况和可能的影响、本公司已采取或将要采取的处置措施、用户可自主防范和降低风险的建议、对用户的补救措施等。同时,本公司还将按照监管部门要求,主动上报个人信息安全事件的处置情况。<br />
-            7. 本公司将用户的个人信息存储于中华人民共和国境内。如需跨境传输,本公司将依法征得用户的授权同意。<br />
-            8. 用户提供的上述信息,将在该用户使用本服务期间持续授权本公司使用。本公司停止运营软件或服务的,将会立即停止收集用户个人信息,并删除持有的用户个人信息,并将停止运营的通知及时通知用户;当该用户注销账号时,本公司将停止使用并删除上述信息。<br />
-
-            <h2>四、用户的权利及其实现的途径和方法</h2>
-            1. 更正<br />
-            用户或一旦发现本公司收集、存储、使用、披露的用户个人信息有错误的,有权要求本公司予以更正,本公司将会及时采取措施予以更正。<br />
-            2. 删除<br />
-            用户要求本公司删除其收集、存储、使用、披露的用户个人信息时,本公司将会及时采取措施予以删除,包括但不限于以下情形:<br />
-            2.1本公司违反法律、行政法规的规定或者双方的约定收集、存储、使用、转移、披露用户个人信息的;<br />
-            2.2本公司超出目的范围或者必要期限收集、存储、使用、转移、披露用户个人信息的;<br />
-            2.3用户撤回授权同意的;<br />
-            2.4用户通过注销等方式终止使用本公司提供的软件及其相关服务的。<br />
-            当本公司决定相应用户的删除请求时,本公司还将同时通知从本公司获得用户个人信息的实体,并要求其及时删除,除非法律法规另有规定,或这些实体获得了用户的独立授权。<br />
-            当用户从本公司的软件中删除信息后,本公司可能不会立即备份系统中删除相应的信息,但会在备份更新时删除这些信息。
-            3. 访问<br />
-            用户有权访问该用户的个人信息,法律法规规定的例外情况除外。如果用户及其简化人希望访问、编辑、更改该用户的账户中的个人资料信息、更改密码、或关闭用户的账户等,均可以通过访问本公司提供的软件执行此类操作。如果用户无法通过本公司提供的软件访问这些个人信息,可以随时联系本公司。<br />
-            4. 在响应用户的上述请求时,为了保障安全,本公司可能要求提供书面请求,或以其他方式证明身份,本公司会验证用户身份后再处理该请求,并在30日内作出答复。在下列情形时,本公司无法响应用户的请求:<br />
-            4.1与国家安全、国防安全直接相关的;<br />
-            4.2与公共安全、公共卫生、重大公共利益直接相关的;<br />
-            4.3与犯罪侦查、起诉、审判、执行等直接相关的;<br />
-            4.4有充分的证据表明用户存在主观恶意或滥用权利的;<br />
-            4.5响应用户的请求可能导致用户或者其他个人、组织的合法权益受到严重损害的;<br />
-            4.6涉及商业秘密的。<br />
-
-            <h2>五、关于Cookie及同类技术</h2>
-            为了确保本公司提供的软件及相关服务的正常运转,本公司会在用户的电脑或移动设备上存储名为Cookie的小数据文件。Cookie通常包含标识符、站点名称以及一些号码和字符。本公司不会将Cookie用于本政策所述目的之外的任何用途,用户可根据自己的偏好管理或删除Cookie,有关详情请参见AboutCookies.org。用户可以通过更改设置清除电脑或移动设备上保存的所有Cookie。<br />
-
-            <h2>六、第三方链接及服务</h2>
-            <strong>本政策仅适用于本公司收集的用户个人信息,本政策不适用于用户通过本公司提供的软件或服务而接入到第三方链接或服务时,该第三方收集和使用用户个人信息的行为及其结果,此时应适用该第三方链接或服务的隐私政策。</strong><br />
-
-            <h2>七、本政策的更新</h2>
-            本公司可能会根据软件及其服务或教学的需要或者根据法律法规的规定修改本政策的全部或部分。如该等更新造成用户在本政策下权利的实质减少或重大变更的,本公司将在更新生效前通过网站公告、推送通知、弹窗提示或其他方式进行通知。用户如果不同意该等更新或变更的,可以选择停止使用本公司的软件及相关服务;如果用户继续使用本公司的软件及相关服务的,即表示用户已充分阅读、理解并同意受该等更新或变更后的本政策的约束。<br />
-
-            <h2>八、本公司的联系方式</h2>
-            用户如有任何问题、意见或者建议,除本政策页首所述的联系方式以外,还可以通过以下方式与本公司联系。<br />
-            电子邮箱:lexiaoyaVIP@163.com<br />
-            联系电话:027-87718176<br />
-            开发者:武汉乐小雅网络科技有限公司
-        </div>
-    </div>
-</template>
-
-<script>
-import { browser } from '@/common/common'
-export default {
-    name: 'smallprotocol',
-    props: ['proto'],
-    data() {
-        return {
-            headerStatus: false
-        }
-    },
-    mounted() {
-        document.title = '用户隐私协议'
-        if (!browser().android && !browser().iPhone) {
-            this.headerStatus = true
-        }
-    },
-    methods: {
-        goBack() {
-            this.$emit('popupClose', false)
-        },
-    }
-}
-</script>
-
-<style lang="less" scoped>
-    .privacy {
-        background: #fff;
-    }
-    header {
-        height: .40rem;
-        line-height: .40rem;
-        color: #000;
-        font-size: .17rem;
-        background: #fff;
-        box-shadow: 0px 1px 8px 0px rgba(0,0,0,0.07);
-        text-align: center;
-
-        .back {
-            width: .2rem;
-            height: .2rem;
-            position: absolute;
-            left: .12rem;
-            top: .1rem;
-        }
-    }
-    .container {
-        padding: .22rem .2rem .3rem;
-        font-size: .14rem;
-        h1 {
-            font-size: .16rem;
-            text-align: center;
-        }
-
-        h2 {
-            font-size: .16rem;
-            font-weight: bold;
-            padding-top: .15rem;
-        }
-        h3 {
-            font-size: .14rem;
-            font-weight: bold;
-        }
-
-        .signature {
-            display: flex;
-            padding-top: .5rem;
-            .sign {
-                flex: 1;
-                position: relative;
-            }
-            span {
-                display: block;
-                padding-left: .2rem;
-            }
-
-            .cachet {
-                position: absolute;
-                top: -.6rem;
-                left: 0;
-                width: 1.5rem;
-                height: 1.5rem;
-            }
-        }
-    }
-
-    .iInfo {
-        display: flex;
-        span {
-            flex: 1;
-        }
-    }
-
-    .btnback {
-        display: inline-block;
-        font-size: 0.18rem;
-        color: #fff;
-        background: #F1111B;
-        border-radius: 0.04rem;
-        -webkit-box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.19);
-        box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.19);
-        padding: 0.08rem 0;
-        margin-top: .8rem;
-        width: 100%;
-        text-align: center;
-    }
-    .line {
-        border-bottom: 1px solid #000;
-    }
+<template>
+    <div class="privacy">
+        <div class="container">
+            <h1 v-if="headerStatus">用户隐私协议</h1>
+            <strong>发布日期:2019-11-22</strong><br />
+            <strong>最新更新日期:2021-05-24</strong><br />
+
+            管乐迷(下称“管乐迷”或“本公司”)尤其重视用户(下称“用户”或“您”)的隐私和个人信息保护。《隐私保护政策》(下称“本政策”)旨在向用户说明本公司如何收集、使用、处理、存储用户个人信息以及为用户提供访问、删除、更正等处理事宜。<br />
+
+            <strong class="line">在使用本公司提供的软件及相关服务前,请您务必仔细阅读并充分理解本政策,尤其是以加粗、下划线等标识的条款。当您使用本公司提供的软件及相关服务时,即表示您已经同意本公司按照本政策来收集、使用、共享和保护您的相关个人信息;用户不同意本政策的任何条款或内容的,请立即停止使用本公司提供的软件及相关服务。</strong><br />
+
+            <strong class="line">【针对未成年用户及其监护人的特别提示】</strong><br />
+            <strong class="line">如果您是未满18周岁的未成年人,应在您的监护人的陪同下阅读本政策,并在监护人的解释和指导下理解本政策,在使用本公司提供的软件及服务或者提交信息之前获得您的监护人的指导并获得同意。未成年用户或者未成年用户的监护人点击同意本政策,或者未成年用户使用本公司提供的软件及服务,或者提交个人信息的,即表示未成年用户已经获得其监护人的同意,即监护人同意本公司按照本政策收集、使用、分享、转让、披露和存储未成年用户的个人信息。如果未成年用户或其监护人不同意本政策的任何条款或内容的,请立即停止使用本公司提供的软件及相关服务。</strong><br />
+            <!-- <strong class="line">【针对未成年用户及其监护人的特别提示】</strong><br /> -->
+
+            <!-- <strong class="line">向儿童用户的特别提示:</strong><br /> -->
+            <strong class="line">如果您是未满14周岁的儿童用户,请务必通知您的监护人并共同阅读本公司的《儿童个人信息保护规则》,并务必在使用本公司提供的软件及服务或者提交信息之前获得监护人的指导并获得同意。</strong><br />
+
+            本公司收集、使用、共享和保护用户的个人信息,是在遵守国家法律法规规定的前提下,仅出于本政策所述的以下目的,遵循正当必要、知情同意、目的明确、安全保障、依法利用的原则收集、存储、使用、转移、披露儿童个人信息。<br />
+            如果您对本政策有任何疑问的,请通过本政策中公布的联系方式与本公司联系。<br />
+            <!-- 本公司联系方式:【】联系人:【】<br /> -->
+
+            <h2>一、本公司收集和使用儿童用户个人信息</h2>
+            个人信息是指以电子或者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息。<br />
+            1. 用户账户信息:<br />
+            用户使用本公司提供的软件及相关服务前,需要在本公司提供的软件注册账户,此时我们需要您主动填写和提供注册信息,包括姓名、性别、出生日期、所在学校、所在班级、手机号码、家庭住址、创建的用户名(ID)等信息,这些信息是本公司为用户提供考勤、教学、排课、陪练、打分等服务所必要的,如果您不提供给本公司,您可能无法使用本公司提供的软件或相关服务。<br />
+
+            2. 用户使用本公司提供的软件及相关服务信息:<br />
+            用户通过电脑、平板电脑或手机等智能设备访问本公司提供的软件及其相关服务时,服务器会自动记录用户的登录IP地址、网络服务商、接入网络的方式、类型及状态、网络质量数据、使用的语言、设备类别、设备型号、设备识别码、操作系统、分辨率、访问日期及时间长度等信息,以及用户登录和使用本公司提供的软件及相关服务时,用户使用的设备所在的地理位置信息。<br />
+            用户使用本公司提供的软件及相关服务参加直播教学、录播教学等课程时,我们会收集用户提交的信息以及系统自动记录和存储的信息,包括但不限于视频、录像、音频、照片、截图以及这些信息产生时对应的日期、时间、地点等。<br />
+
+            3. 交易信息和付款信息:<br />
+            用户使用本公司提供的软件及相关服务的过程中,购买课程或其他服务需要向本公司进行支付,<strong class="line">本公司建议由未成年用户的监护人完成支付交易的整个过程,如果支付交易是由未成年用户进行,则应当在向本公司提供支付信息之前,获得其监护人的同意、授权和指导。</strong>本公司会收集支付交易时使用的支付工具、支付账户、银行卡号、开户银行、支付状态信息,支付日期等信息。<strong class="line">如果您进行支付交易时直接跳转至第三方支付页面的,该第三方将会直接收集您的支付信息,此时您的支付信息的收集、使用、存储等规则将遵循该第三方的隐私政策,请您注意事先仔细查阅。</strong>如果您符合本公司相关服务协议里约定的退费情形,并要求退费时,本公司需要您提供姓名、银行卡号、开户银行等信息。<br />
+
+            4. 用户因请假、打卡签到或其他原因通过电话、电子邮件或其他线上或线下渠道与本公司取得联系时,本公司会收集和处理该用户向本公司提供或者反馈的信息,用户的姓名、联系方式,以及本公司为了处理该用户的请求而必须向其确认的事项,并可能会保留与用户的通话、通信记录,以便本公司及时与该用户联系和沟通并处理。<br />
+
+            5. 本公司还可能为了改进本公司服务,提高教学和服务质量等需要,用户联系和沟通,并在此过程中收集沟通时提供的相关信息。<br />
+
+            6. 本公司在教学和服务过程中必要时向用户发出与教学或服务有关的通知或推送。<br />
+
+            7. 检测和防止欺诈和IT安全威胁所需的信息:本公司收集检测,调查和预防欺诈,作弊、IT安全威胁、网络漏洞、黑客攻击、计算机病毒和其他违反用户协议和适用法律的行为所需的某些信息。此数据仅用于检测,调查,预防以及在适用的情况下对此类行为进行处理,并且仅在此目的所需的最短时间内存储。如果披露信息会影响本公司检测、调查、防止此类行为的机制,则可能无法向您披露为此目的而存储的特定信息。<br />
+
+            8. 如本公司要向用户的个人信息用于本政策未载明的其他用途时,将事先获得用户的同意;如本公司将基于特定目的而收集的用户个人信息用于其他目的时,将事先获得用户的同意。<br />
+
+            9. 为了保障您的账号安全、交易安全以及系统运行安全,满足法律法规和我们协议规则的相关要求,在您使用我们的产品/服务过程中,经您授权我们会获取您的设备信息,包括您使用的设备属性、连接和状态信息,例如设备型号、设备标识符(如IMEI/androidID/IDFA/OPENUDID/GUID/OAID、SIM卡IMSI、ICCID信息等)、设备MAC地址、软件列表、电信运营商等软硬件特征信息。<br />
+
+            10. 我们可能间接收集的个人信息<br />
+            您可以选择授权我们收集和使用个人信息的场景,为向您提供个性化的服务,您可以选择使用我们提供的拓展功能,我们会在符合法律规定并根据您具体授权的情况下收集并使用如下信息。这类信息将在您选择的具体功能和业务场景中进行收集,如果您不提供这些信息,不会影响您使用管乐迷的基本功能。<br />
+            10.1 您需要授权我们收集和使用个人信息的场景<br />
+            10.1.1 基于相机授权的拓展功能<br />
+            您可以选择开启系统的相机权限,通过使用拍照、录视频等功能授权管乐迷访问您的相机,以便于您通过拍摄照片或录制视频等方式发布内容,如果您需要录制并发布有声视频时,您还需开启麦克风权限。我们会收集您上传发布的上述信息,此项功能您可以在系统权限中关闭,一旦关闭您将可能无法通过拍摄图片、视频等方式进行更换头像、上传作业等,但不会影响您享受管乐迷的基本功能。<br />
+            10.1.2 基于相册授权的拓展功能<br />
+            您可以选择开启系统的相册权限,通过主动上传图片、视频等方式授权我们访问您的相册,以便于您通过上传照片或上传视频等方式发布内容。我们会收集您选择上传发布的上述信息,此项功能您可以在系统权限中关闭,一旦关闭您将可能无法通过上传图片、视频等方式进行更换头像、上传作业等,但不会影响您享受管乐迷的基本功能。<br />
+            10.1.3 基于麦克风授权的拓展功能<br />
+            您可以选择开启系统的麦克风权限,使用语音技术来实现录制音频作业等语音功能。我们会收集您在使用录音功能中录入的语音信息用于老师查看音频作业。此项功能您可以在系统权限中关闭,一旦关闭您将可能无法使用音频作业功能,但不会影响您享受管乐迷的基本功能。<br />
+            10.2 其他<br />
+            如果我们将信息用于本政策未载明的其他用途,或者将基于特定目的收集而来的信息用于其他目的时,会单独征求您的授权同意。<br />
+            10.3 征得同意的例外<br />
+            请您知悉,以下情形中,我们收集、使用个人信息无需征得您的授权同意:<br />
+            10.3.1 与国家安全、国防安全有关的;<br />
+            10.3.2 与公共安全、公共卫生、重大公共利益有关的;<br />
+            10.3.3 与犯罪侦查、起诉、审判和判决执行等有关的;<br />
+            10.3.4 出于维护个人信息主体或其他个人的生命、财产等重大合法权益但又很难得到本人同意的;<br />
+            10.3.5 所收集的个人信息是个人信息主体自行向社会公众公开的;<br />
+            10.3.6 从合法公开披露的信息中收集的您的个人信息的,如合法的新闻报道、政府信息公开等渠道;但是您明确拒绝或者处理该信息侵害您重大利益的除外。<br />
+            10.3.7 根据您的要求签订合同所必需的;<br />
+            10.3.8 用于维护所提供的产品与/或服务的安全稳定运行所必需的,例如发现、处置产品与/或服务的故障;<br />
+            10.3.9 为合法的新闻报道所必需的;<br />
+            10.3.10 学术研究机构基于公共利益开展统计或学术研究所必要,且对外提供学术研究或描述的结果时,对结果中所包含的个人信息进行去标识化处理的;<br />
+            10.3.11 法律法规规定的其他情形。<br />
+            请注意,单独或与其他信息相结合无法识别您的身份或者与您直接建立联系的信息,不属于个人信息。如果我们将单独无法与任何特定个人建立联系的信息与其他信息结合用于识别自然人个人身份,或者将其与个人信息结合使用,则在结合使用期间,此类信息将被视为个人信息。<br />
+            10.4 设备权限调用<br />
+            为确保相关业务功能的正常实现,我们需要根据具体的使用场景调用对应的必要权限,并在调用前向您弹窗询问,具体的权限调用说明请见下表:<br />
+            <table border="1">
+                <tr>
+                    <th>设备权限</th>
+                    <th>使用目的</th>
+                    <th>是否询问</th>
+                    <th>是否可关闭</th>
+                </tr>
+                <tr>
+                    <td>相机</td>
+                    <td>用于拍照、上传图片、录制视频</td>
+                    <td>用户主动拍照、上传图片、录制视频时询问</td>
+                    <td>是</td>
+                </tr>
+                <tr>
+                    <td>麦克风</td>
+                    <td>用户语音消息、音频作业、视频作业</td>
+                    <td>用户发起语音输入前询问</td>
+                    <td>是</td>
+                </tr>
+                <tr>
+                    <td>照片</td>
+                    <td>用于写入和读取用户相册信息</td>
+                    <td>用户主动上传前询问</td>
+                    <td>是</td>
+                </tr>
+            </table>
+            您可以在设备的设置中选择关闭部分或者全部权限,这可能导致对应的业务功能无法实现或者无法达到预期效果。<br />
+
+            <strong class="line">本公司收集用户的个人信息后,可能会通过技术手段对已经收集的用户个人信息进行匿名化处理,使得用户个人信息主体无法被识别,且处理后的信息不能被复原,此时经过匿名化处理后的信息不再属于用户个人信息。本公司可能会将匿名化处理信息用于测试本公司的IT系统,研究,数据分析,改进服务,以及开发新服务、新产品和功能,以及向第三方分享、转让、披露,此时无需另行获得用户的授权同意。</strong><br />
+
+            <h2>二、本公司共享、转让、披露用户个人信息</h2>
+            1. 共享:<br />
+            1.1在获取用户的明确同意或授权的情况共享;<br />
+            1.2根据法律法规规定,或者按照政府主管部门的强制性要求;<br />
+            1.3为了向用户提供教学或服务,本公司可能会与关联方分享,但是本公司只会分享必要的个人信息,且此时受用户服务协议或本隐私政策中所声明的收集和使用之目的的约束,本公司的关联方如要改变个人信息的处理目的,将再次征求用户的授权同意;<br />
+            1.4本公司还可以与提供与本公司提供的软件及相关服务相关的必要功能和支持服务的第三方(例如,提供或处理支付业务、网络定位服务、推送通知、电子邮件服务、税务和会计服务、分析服务)共享用户的个人信息数据,此时本公司仅会处于合法、正当、必要、特定、明确的目的共享用户的个人信息,并且只会共享提供服务所必要的信息,且此时本公司会与这些第三方主体签订保密协议,要求这些第三方主体按照相关法律法规规定、本政策及其他任何相关的保密和安全措施保护用户的个人信息。您可以通过以下链接查看第三方的数据使用和保护规则。<br />
+            管乐迷接入第三方SDK目录<br />
+            <table border="1">
+                <tr>
+                    <th style="width: .9rem;">使用目的</th>
+                    <th>第三方名称</th>
+                    <th>涉及个人信息</th>
+                    <th>第三方官网链接</th>
+                </tr>
+                <tr>
+                    <td rowspan="5">根据用户机型,为用户提供通知信息推送功能</td>
+                    <td>极光</td>
+                    <td>设备信息</td>
+                    <td style="word-break: break-all;">https://www.jiguang.cn/license/privacy</td>
+                </tr>
+                <tr>
+                    <td>华为</td>
+                    <td>设备信息</td>
+                    <td style="word-break: break-all;">https://developer.huawei.com/consumer/cn/service/hms/pushservice.html</td>
+                </tr>
+                <tr>
+                    <td>小米</td>
+                    <td>设备信息</td>
+                    <td style="word-break: break-all;">https://dev.mi.com/console/doc/detail?pId=1822</td>
+                </tr>
+                <tr>
+                    <td>VIVO</td>
+                    <td>设备信息</td>
+                    <td style="word-break: break-all;">https://www.umeng.com/page/policy</td>
+                </tr>
+                <tr>
+                    <td>OPPO</td>
+                    <td>设备信息</td>
+                    <td style="word-break: break-all;">https://open.oppomobile.com/wiki/doc#id=10194</td>
+                </tr>
+                <tr>
+                    <td>统计分析</td>
+                    <td>友盟SDK</td>
+                    <td>设备信息</td>
+                    <td style="word-break: break-all;">https://open.oppomobile.com/wiki/doc#id=10194</td>
+                </tr>
+            </table>
+            2. 转让:<br />
+            2.1在获取用户的明确同意的情况下转让;<br />
+            2.2本公司发生合并、收购、资产转让或类似交易或者破产清算时,用户信息(包括用户的个人信息)可能会被转让给相关方,此时本公司会要求这些受让方继续遵守本政策的约束,如果这些受让方变更用户信息的使用目的时,应重新获得用户的同意和授权;<br />
+            本公司向第三方转让用户个人信息时,将自行或委托第三方机构进行安全评估。<br />
+            3. 披露:<br />
+            3.1在获取用户的要求或明确同意的情况下披露;<br />
+            3.2根据法律法规规定、法院或政府主管部门的强制性要求时;<br />
+            3.3本公司发生资本市场活动(例如IPO等)时,可能需要接受其他主体的尽职调查,此时本公司可能会根据尽职调查的需要将用户的个人信息提供给必要的主体,但本公司会与实施调查的主体签订保密协议,要求这些第三方主体按照相关法律法规规定、本政策及其他任何相关的保密和安全措施来保护用户的个人信息。<br />
+            4. 本公司在下列情形时,无需获得用户的授权同意,即可分享、转让、披露用户的信息:<br />
+            4.1与国家安全、国防安全相关的;<br />
+            4.2与公共安全、公共卫生、重大公共利益相关的;<br />
+            4.3与犯罪侦查、起诉、审判和判决执行等司法或行政执法相关的;<br />
+            4.4为了维护用户或其他个人的生命和财产等重大合法权利但又很难得到同意的情况下;<br />
+            4.5用户自行向社会公开的信息;<br />
+            4.6从合法公开渠道获得的信息,如新闻报道、政府信息公开等渠道。<br />
+
+            <h2>三、用户个人信息的保护、存储、跨境传输</h2>
+            1. 本公司会努力采取各种符合业界标准的物理、电子和管理方面的安全措施来保护用户的个人信息,防止用户的个人信息遭到未经授权访问、公开披露、使用、修改、损坏或丢失。本公司会采取一切合理可行的措施,保护用户的个人信息。<br />
+            2. 本公司会采取加密等措施存储用户的个人信息,确保信息安全,并以最小授权为原则对本公司工作人员严格设置信息访问权限,控制用户个人信息知悉范围。工作人员需要访问用户个人信息的,应当经过本公司用户个人信息保护负责人或者其授权的管理人员审批,并且记录访问情况,采取技术措施,避免违法复制、下载用户个人信息。<br />
+            3. 本公司将会采取一切合理可行的措施,确保未收集无关的用户个人信息,并只会在达成本政策所述目的所需的期限内存储和保留用户的个人信息,不会超过本政策所述目的所必需的期限,除非需要延长保留期或受到法律的允许。<br />
+            4. 互联网并非绝对安全的环境,而且电子邮件、即时通讯、社交软件或其他服务软件等与其他用户交流的方式无法确定是否完全加密,因此本公司建议用户使用此类工具时注意保护信息安全,并使用复杂密码,协助本公司保证用户的账户安全。<br />
+            5. 互联网环境并非百分之白安全,本公司将尽力确保或担保用户发送给本公司的任何信息的安全性。如果本公司的物理、技术,或管理防护设施遭到破坏,导致信息被非授权访问、公开披露、篡改,或破坏,导致您的合法权益受损,本公司将承担相应的法律责任。<br />
+            6. 本公司发现用户个人信息发生或可能发生泄露、毁损、灭失的,本公司将将立即启动应急预案,采取补救措施;造成或者可能造成严重后果的,会立即向有关主管部门报告,并将事件相关情况以邮件、信函、电话、推送通知等方式告知受影响的用户,难以逐一告知的,将会采取合理、有效的方式发布相关警示信息。包括但不限于安全事件的基本情况和可能的影响、本公司已采取或将要采取的处置措施、用户可自主防范和降低风险的建议、对用户的补救措施等。同时,本公司还将按照监管部门要求,主动上报个人信息安全事件的处置情况。<br />
+            7. 本公司将用户的个人信息存储于中华人民共和国境内。如需跨境传输,本公司将依法征得用户的授权同意。<br />
+            8. 用户提供的上述信息,将在该用户使用本服务期间持续授权本公司使用。本公司停止运营软件或服务的,将会立即停止收集用户个人信息,并删除持有的用户个人信息,并将停止运营的通知及时通知用户;当该用户注销账号时,本公司将停止使用并删除上述信息。<br />
+
+            <h2>四、用户的权利及其实现的途径和方法</h2>
+            1. 更正<br />
+            用户或一旦发现本公司收集、存储、使用、披露的用户个人信息有错误的,有权要求本公司予以更正,本公司将会及时采取措施予以更正。<br />
+            2. 删除<br />
+            用户要求本公司删除其收集、存储、使用、披露的用户个人信息时,本公司将会及时采取措施予以删除,包括但不限于以下情形:<br />
+            2.1本公司违反法律、行政法规的规定或者双方的约定收集、存储、使用、转移、披露用户个人信息的;<br />
+            2.2本公司超出目的范围或者必要期限收集、存储、使用、转移、披露用户个人信息的;<br />
+            2.3用户撤回授权同意的;<br />
+            2.4用户通过注销等方式终止使用本公司提供的软件及其相关服务的。<br />
+            当本公司决定相应用户的删除请求时,本公司还将同时通知从本公司获得用户个人信息的实体,并要求其及时删除,除非法律法规另有规定,或这些实体获得了用户的独立授权。<br />
+            当用户从本公司的软件中删除信息后,本公司可能不会立即备份系统中删除相应的信息,但会在备份更新时删除这些信息。
+            3. 访问<br />
+            用户有权访问该用户的个人信息,法律法规规定的例外情况除外。如果用户及其简化人希望访问、编辑、更改该用户的账户中的个人资料信息、更改密码、或关闭用户的账户等,均可以通过访问本公司提供的软件执行此类操作。如果用户无法通过本公司提供的软件访问这些个人信息,可以随时联系本公司。<br />
+            4. 在响应用户的上述请求时,为了保障安全,本公司可能要求提供书面请求,或以其他方式证明身份,本公司会验证用户身份后再处理该请求,并在30日内作出答复。在下列情形时,本公司无法响应用户的请求:<br />
+            4.1与国家安全、国防安全直接相关的;<br />
+            4.2与公共安全、公共卫生、重大公共利益直接相关的;<br />
+            4.3与犯罪侦查、起诉、审判、执行等直接相关的;<br />
+            4.4有充分的证据表明用户存在主观恶意或滥用权利的;<br />
+            4.5响应用户的请求可能导致用户或者其他个人、组织的合法权益受到严重损害的;<br />
+            4.6涉及商业秘密的。<br />
+
+            <h2>五、关于Cookie及同类技术</h2>
+            为了确保本公司提供的软件及相关服务的正常运转,本公司会在用户的电脑或移动设备上存储名为Cookie的小数据文件。Cookie通常包含标识符、站点名称以及一些号码和字符。本公司不会将Cookie用于本政策所述目的之外的任何用途,用户可根据自己的偏好管理或删除Cookie,有关详情请参见AboutCookies.org。用户可以通过更改设置清除电脑或移动设备上保存的所有Cookie。<br />
+
+            <h2>六、第三方链接及服务</h2>
+            <strong>本政策仅适用于本公司收集的用户个人信息,本政策不适用于用户通过本公司提供的软件或服务而接入到第三方链接或服务时,该第三方收集和使用用户个人信息的行为及其结果,此时应适用该第三方链接或服务的隐私政策。</strong><br />
+
+            <h2>七、本政策的更新</h2>
+            本公司可能会根据软件及其服务或教学的需要或者根据法律法规的规定修改本政策的全部或部分。如该等更新造成用户在本政策下权利的实质减少或重大变更的,本公司将在更新生效前通过网站公告、推送通知、弹窗提示或其他方式进行通知。用户如果不同意该等更新或变更的,可以选择停止使用本公司的软件及相关服务;如果用户继续使用本公司的软件及相关服务的,即表示用户已充分阅读、理解并同意受该等更新或变更后的本政策的约束。<br />
+
+            <h2>八、本公司的联系方式</h2>
+            用户如有任何问题、意见或者建议,除本政策页首所述的联系方式以外,还可以通过以下方式与本公司联系。<br />
+            电子邮箱:lexiaoyaVIP@163.com<br />
+            联系电话:027-87718176<br />
+            开发者:武汉乐小雅网络科技有限公司
+        </div>
+    </div>
+</template>
+
+<script>
+import { browser } from '@/common/common'
+export default {
+    name: 'smallprotocol',
+    props: ['proto'],
+    data() {
+        return {
+            headerStatus: false
+        }
+    },
+    mounted() {
+        document.title = '用户隐私协议'
+        if (!browser().android && !browser().iPhone) {
+            this.headerStatus = true
+        }
+    },
+    methods: {
+        goBack() {
+            this.$emit('popupClose', false)
+        },
+    }
+}
+</script>
+
+<style lang="less" scoped>
+    .privacy {
+        background: #fff;
+    }
+    header {
+        height: .40rem;
+        line-height: .40rem;
+        color: #000;
+        font-size: .17rem;
+        background: #fff;
+        box-shadow: 0px 1px 8px 0px rgba(0,0,0,0.07);
+        text-align: center;
+
+        .back {
+            width: .2rem;
+            height: .2rem;
+            position: absolute;
+            left: .12rem;
+            top: .1rem;
+        }
+    }
+    .container {
+        padding: .22rem .2rem .3rem;
+        font-size: .14rem;
+        h1 {
+            font-size: .16rem;
+            text-align: center;
+        }
+
+        h2 {
+            font-size: .16rem;
+            font-weight: bold;
+            padding-top: .15rem;
+        }
+        h3 {
+            font-size: .14rem;
+            font-weight: bold;
+        }
+
+        .signature {
+            display: flex;
+            padding-top: .5rem;
+            .sign {
+                flex: 1;
+                position: relative;
+            }
+            span {
+                display: block;
+                padding-left: .2rem;
+            }
+
+            .cachet {
+                position: absolute;
+                top: -.6rem;
+                left: 0;
+                width: 1.5rem;
+                height: 1.5rem;
+            }
+        }
+    }
+
+    .iInfo {
+        display: flex;
+        span {
+            flex: 1;
+        }
+    }
+
+    .btnback {
+        display: inline-block;
+        font-size: 0.18rem;
+        color: #fff;
+        background: #F1111B;
+        border-radius: 0.04rem;
+        -webkit-box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.19);
+        box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.19);
+        padding: 0.08rem 0;
+        margin-top: .8rem;
+        width: 100%;
+        text-align: center;
+    }
+    .line {
+        border-bottom: 1px solid #000;
+    }
 </style>

+ 108 - 101
vue.config.js

@@ -1,102 +1,109 @@
-let targetUrl = 'https://mteatest.dayaedu.com'
-// let targetUrl = 'http://192.168.3.20:8000'
-// let targetUrl = 'https://online.dayaedu.com'
-// let targetUrl = 'http://dev.dayaedu.com/'
-// let targetUrl = 'http://192.168.3.124:8000'
-module.exports = {
-  chainWebpack: config => {
-    config.devtool('inline-source-map')
-    config.output.filename('[name].[hash].js').end();
-    // // chunkHash
-    // config.output.filename(`js/[name].[chunkhash].${Version}.js`).end();
-    // config.output.chunkFilename(`js/[id].[chunkhash].${Version}.js`).end();
-    // config.output.filename(`js/[name].[chunkhash].js`).end();
-    // config.output.chunkFilename(`js/[id].[chunkhash].js`).end();
-    // config.entry.app = ['babel-polyfill', './src/main.js']
-
-    // config.resolve.symlinks(true)
-    // config.plugin('html').tap(args => {
-    //   args[0].chunksSortMode = 'none'
-    //   return args
-    // })
-    config.plugin('html').tap(args => {
-      args[0].minify = {
-        removeAttributeQuotes: false
-      }
-      return args
-    })
-  },
-  // eslint-loader 是否在保存的时候检查
-  lintOnSave: true,
-
-  // 是否使用包含运行时编译器的Vue核心的构建
-  runtimeCompiler: false,
-  // 生产环境 sourceMap
-  productionSourceMap: false,
-  configureWebpack: () => { },
-  // 配置 webpack-dev-server 行为。
-  devServer: {
-    open: process.platform === 'darwin',
-    host: '0.0.0.0',
-    port: 9999,
-    // https: true,
-    hotOnly: false,
-    // 查阅 https://github.com/vuejs/vue-doc-zh-cn/vue-cli/cli-service.md#配置代理
-    proxy: {
-      '/contracts': {
-        target: targetUrl,
-        changeOrigin: true,
-        ws: true,
-        '^/contracts': '/contracts',
-        xfwd: true
-      },
-      '/api-student': {
-        target: targetUrl,
-        changeOrigin: true,
-        ws: true,
-        '^/api-student': '/api-student',
-        xfwd: true
-      },
-      '/api-cms': {
-        target: targetUrl,
-        changeOrigin: true,
-        ws: true,
-        '^/api-cms': '/api-cms',
-        xfwd: true
-      },
-      '/api-teacher': {
-        target: targetUrl,
-        changeOrigin: true,
-        ws: true,
-        '^/api-teacher': '/api-teacher',
-        xfwd: true
-      },
-      '/api-web': {
-        target: targetUrl,
-        changeOrigin: true,
-        ws: true,
-        '^/api-web': '/api-web',
-        xfwd: true
-      },
-      '/api-auth': {
-        target: targetUrl,
-        changeOrigin: true,
-        ws: true,
-        '^/api-auth': '/api-auth',
-        xfwd: true
-      }
-    }, // string | Object
-  },
-  css: {
-    loaderOptions: {
-      less: {
-        modifyVars: {
-          // red: '#03a9f4',
-          blue: '#01C1B5',
-          // orange: '#f08d49',
-          // 'text-color': '#111' 
-        }
-      }
-    }
-  },
+let targetUrl = 'https://mteatest.dayaedu.com'
+// let targetUrl = 'http://192.168.3.20:8000'
+// let targetUrl = 'https://online.dayaedu.com'
+// let targetUrl = 'http://dev.dayaedu.com/'
+// let targetUrl = 'http://192.168.3.124:8000'
+module.exports = {
+  chainWebpack: config => {
+    config.devtool('inline-source-map')
+    config.output.filename('[name].[hash].js').end();
+    // // chunkHash
+    // config.output.filename(`js/[name].[chunkhash].${Version}.js`).end();
+    // config.output.chunkFilename(`js/[id].[chunkhash].${Version}.js`).end();
+    // config.output.filename(`js/[name].[chunkhash].js`).end();
+    // config.output.chunkFilename(`js/[id].[chunkhash].js`).end();
+    // config.entry.app = ['babel-polyfill', './src/main.js']
+
+    // config.resolve.symlinks(true)
+    // config.plugin('html').tap(args => {
+    //   args[0].chunksSortMode = 'none'
+    //   return args
+    // })
+    config.plugin('html').tap(args => {
+      args[0].minify = {
+        removeAttributeQuotes: false
+      }
+      return args
+    })
+  },
+  // eslint-loader 是否在保存的时候检查
+  lintOnSave: true,
+
+  // 是否使用包含运行时编译器的Vue核心的构建
+  runtimeCompiler: false,
+  // 生产环境 sourceMap
+  productionSourceMap: false,
+  configureWebpack: () => { },
+  // 配置 webpack-dev-server 行为。
+  devServer: {
+    open: process.platform === 'darwin',
+    host: '0.0.0.0',
+    port: 9999,
+    // https: true,
+    hotOnly: false,
+    // 查阅 https://github.com/vuejs/vue-doc-zh-cn/vue-cli/cli-service.md#配置代理
+    proxy: {
+      '/contracts': {
+        target: targetUrl,
+        changeOrigin: true,
+        ws: true,
+        '^/contracts': '/contracts',
+        xfwd: true
+      },
+      '/api-student': {
+        target: targetUrl,
+        changeOrigin: true,
+        ws: true,
+        '^/api-student': '/api-student',
+        xfwd: true
+      },
+      '/api-cms': {
+        target: targetUrl,
+        changeOrigin: true,
+        ws: true,
+        '^/api-cms': '/api-cms',
+        xfwd: true
+      },
+      '/api-teacher': {
+        target: targetUrl,
+        changeOrigin: true,
+        ws: true,
+        '^/api-teacher': '/api-teacher',
+        xfwd: true
+      },
+      '/api-web': {
+        target: targetUrl,
+        changeOrigin: true,
+        ws: true,
+        '^/api-web': '/api-web',
+        xfwd: true
+      },
+      '/api-auth': {
+        target: targetUrl,
+        changeOrigin: true,
+        ws: true,
+        '^/api-auth': '/api-auth',
+        xfwd: true
+      },
+      '/api-oa': {
+        target: targetUrl,
+        changeOrigin: true,
+        ws: true,
+        '^/api-oa': '/api-oa',
+        xfwd: true
+      }
+    }, // string | Object
+  },
+  css: {
+    loaderOptions: {
+      less: {
+        modifyVars: {
+          // red: '#03a9f4',
+          blue: '#01C1B5',
+          // orange: '#f08d49',
+          // 'text-color': '#111' 
+        }
+      }
+    }
+  },
 }

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