Forráskód Böngészése

Merge branch '0601Action' into 03/03GRADE

mo 3 éve
szülő
commit
f3fe3dbffd

+ 1 - 0
package.json

@@ -31,6 +31,7 @@
     "js-cookie": "2.2.0",
     "linq": "^3.2.2",
     "lodash": "^4.17.20",
+    "mammoth": "^1.4.19",
     "moment": "^2.29.1",
     "node-sass": "^4.14.1",
     "normalize.css": "7.0.0",

BIN
public/systemMaintain/bg.png


+ 144 - 0
public/systemMaintain/index.html

@@ -0,0 +1,144 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <link rel="icon" href="./favicon.ico" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>管乐迷</title>
+  <style>
+    .wscn-http404-container {
+      transform: translate(-50%, -50%);
+      position: absolute;
+      top: 40%;
+      left: 50%;
+    }
+
+    .wscn-http404 {
+      position: relative;
+      width: 1000px;
+      padding: 0 50px;
+      overflow: hidden;
+    }
+
+    .pic-404 {
+      position: relative;
+      float: left;
+      width: 475px;
+      overflow: hidden;
+    }
+
+    .pic-404__parent {
+      width: 100%;
+      width: 475px;
+      height: 430px;
+    }
+
+    .pic-404__child {
+      position: absolute;
+      width: 475px;
+      height: 430px;
+    }
+
+
+    .bullshit {
+      position: relative;
+      float: left;
+      width: 365px;
+      padding: 120px 0 30px;
+      margin-left: 100px;
+      overflow: hidden;
+    }
+
+    .bullshit__oops {
+      font-size: 32px;
+      font-weight: bold;
+      line-height: 40px;
+      color: #000;
+      opacity: 0;
+      margin-bottom: 20px;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-fill-mode: forwards;
+    }
+
+    .bullshit__headline {
+      font-size: 20px;
+      line-height: 24px;
+      color: #222;
+      font-weight: bold;
+      opacity: 0;
+      margin-bottom: 10px;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-delay: 0.1s;
+      animation-fill-mode: forwards;
+    }
+
+    .bullshit__info {
+      font-size: 18px;
+      line-height: 31px;
+      color: #666;
+      opacity: 0;
+      margin-bottom: 30px;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-delay: 0.2s;
+      animation-fill-mode: forwards;
+    }
+
+    .bullshit__return-home {
+      display: block;
+      float: left;
+      width: 110px;
+      height: 36px;
+      background: #1482f0;
+      border-radius: 100px;
+      text-align: center;
+      color: #ffffff;
+      opacity: 0;
+      font-size: 14px;
+      line-height: 36px;
+      cursor: pointer;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-delay: 0.3s;
+      animation-fill-mode: forwards;
+    }
+
+    @keyframes slideUp {
+      0% {
+        transform: translateY(60px);
+        opacity: 0;
+      }
+
+      100% {
+        transform: translateY(0);
+        opacity: 1;
+      }
+    }
+
+  </style>
+</head>
+
+<body>
+  <div class="wscn-http404-container">
+    <div class="wscn-http404">
+      <div class="pic-404">
+        <img class="pic-404__parent" src="./bg.png" alt="bg">
+      </div>
+      <div class="bullshit">
+        <div class="bullshit__oops">系统升级中</div>
+        <div class="bullshit__info">我们正在对系统进行升级,升级期间暂时无法访问。预计结束时间:2022年2月10日 22:00
+          给您带来的不便,敬请谅解!</div>
+        <!-- <a href=""
+           class="bullshit__return-home">返回首页</a> -->
+      </div>
+    </div>
+  </div>
+  <!-- <div id="app"></div>
+  <img id="loading" class="show" src="./bg.png" alt="loading"/>
+  系统维护中 -->
+</body>
+
+</html>

BIN
src/assets/404_images/permission_bg.png


+ 1 - 1
src/components/Tooltip/index.vue

@@ -13,7 +13,7 @@
     props: {
       content: {
         required: true,
-        type: String
+        type: [String, Object]
       },
 
     },

+ 14 - 3
src/components/singe-file-upload/index.vue

@@ -1,4 +1,5 @@
 <template>
+    <!-- :before-upload="beforeUpload" -->
   <el-upload
     action="/api-web/uploadFile"
     :headers="headers"
@@ -8,7 +9,7 @@
     :on-error="error"
     :file-list="filelist"
     :accept="accept">
-    <el-button  type="primary" plain >点击上传</el-button>
+    <el-button  type="primary" plain >{{ buttonText }}</el-button>
     <div slot="tip" v-if="tips" class="el-upload__tip">{{tips}}</div>
     <div slot="file" slot-scope="{file}">
       <div style="display: flex; align-items: center;flex: 1 auto;justify-content: space-between;">
@@ -29,6 +30,10 @@ import load from '@/utils/loading'
 export default {
   name: 'singe-file-upload',
   props: {
+    buttonText: {
+      type: String,
+      default: '点击上传',
+    },
     tips: {
       type: String,
       default: ''
@@ -75,10 +80,15 @@ export default {
       this.remove()
       load.endLoading()
     },
-    progress() {
+    // beforeUpload(file) {
+    //   console.log(file)
+
+    //   return false
+    // },
+    progress(file) {
       load.startLoading()
     },
-    success(res) {
+    success(res, file) {
       load.endLoading()
       if (res.code == 200) {
         this.filelist = [{
@@ -87,6 +97,7 @@ export default {
         }]
         this.$emit('update:value', res.data.url)
         this.$emit('input', res.data.url)
+        this.$emit('inputFile', file)
       } else {
         this.remove()
         this.$message.error(res.msg || '上传失败')

+ 11 - 3
src/permission.js

@@ -105,6 +105,8 @@ router.beforeEach(async (to, from, next) => {
           await store.dispatch('user/getInfo')
           // 请求接口 生成可访问路由
           const accessRoutes = await store.dispatch('permission/generateRoutes')
+          // console.log(accessRoutes, 'accessRoutes')
+          const isMenu = accessRoutes && accessRoutes.length > 0 ? true : false
           accessRoutes.push({ path: '*', redirect: '/404', hidden: true })
           // 动态添加可访问的路由
           router.addRoutes(accessRoutes)
@@ -113,10 +115,16 @@ router.beforeEach(async (to, from, next) => {
           // 设置replace:true,这样导航就不会留下历史记录。
           let firstMenu = getFirstMenu(accessRoutes)
           localStorage.setItem('firstMenuUrl', firstMenu)
-          if(to.path == '/main/main') {
-            next({ path: firstMenu, replace: true })
+          // console.log(firstMenu, 'firstMenu', isMenu)
+          // 判断是否有菜单
+          if(isMenu) {
+            if(to.path == '/main/main') {
+              next({ path: firstMenu, replace: true })
+            } else {
+              next({ ...to, replace: true })
+            }
           } else {
-            next({ ...to, replace: true })
+            next({ path: '/noPermission', replace: true })
           }
         } catch (error) {
           // remove token and go to login page to re-login

+ 12 - 0
src/router/index.js

@@ -115,6 +115,16 @@ export const constantRoutes = [
     component: () => import('@/views/403'),
     hidden: true
   },
+  {
+    path: '/noPermission',
+    component: () => import('@/views/noPermission'),
+    hidden: true
+  },
+  {
+    path: '/systemMaintain',
+    component: () => import('@/views/systemMaintain'),
+    hidden: true
+  },
   // { path: '*', redirect: '/404', hidden: true }
 ]
 // export const constantRoutes = [{
@@ -504,6 +514,8 @@ export const asyncRoutes = {
   specialDeault:() => import('@/views/baseRulesManager/special.vue'),
   // 机构设置
   tenantInfoSetting:() => import('@/views/tenantSetting/tenantInfo.vue'),
+  // 机构协议管理
+  tenantInfoProtocol:() => import('@/views/tenantSetting/tenantInfoProtocol.vue'),
   // 机构交易管理
   tenantTradeManager:() => import('@/views/tenantSetting/tenantTradeManager.vue'),
   // 团练宝激活记录

+ 5 - 1
src/styles/iconfont/iconfont.css

@@ -1,6 +1,6 @@
 @font-face {
   font-family: "iconfont"; /* Project id  */
-  src: url('iconfont.ttf?t=1642679515371') format('truetype');
+  src: url('iconfont.ttf?t=1644398529497') format('truetype');
 }
 
 .iconfont {
@@ -411,3 +411,7 @@
   content: "\e67f";
 }
 
+.icon-jigouxieyiguanli:before {
+  content: "\e680";
+}
+

BIN
src/styles/iconfont/iconfont.ttf


+ 19 - 1
src/views/categroyManager/insideSetting/staffManager.vue

@@ -79,6 +79,13 @@
         v-permission="'employee/add'"
         >添加</el-button
       >
+
+      <el-button
+        type="primary"
+        style="margin-bottom: 20px"
+        @click="onCreateQRCode"
+        >管理端APP下载</el-button
+      >
       <!-- 列表 -->
       <div class="tableWrap">
         <el-table
@@ -588,6 +595,8 @@
         <el-button @click="submitEducation" type="primary">确 定</el-button>
       </span>
     </el-dialog>
+
+    <qr-code v-model="qrCodeStatus" title="管理端APP下载链接" :codeUrl="codeUrl" />
   </div>
 </template>
 <script>
@@ -613,6 +622,8 @@ import deepClone from "@/helpers/deep-clone";
 import Tooltip from "@/components/Tooltip/index";
 import { isvalidPhone } from "@/utils/validate";
 import handover from "./modals/handover";
+import QrCode from "@/components/QrCode/index";
+import { vaildTeachingUrl } from "@/utils/validate";
 let validPhone = (rule, value, callback) => {
   if (!value) {
     callback(new Error("请输入电话号码"));
@@ -624,7 +635,7 @@ let validPhone = (rule, value, callback) => {
 };
 export default {
   name: "staffManager",
-  components: { pagination, Tooltip, handover },
+  components: { pagination, Tooltip, handover, QrCode },
   data() {
     return {
       roleResetList: [],
@@ -633,6 +644,8 @@ export default {
       deptList: [],
       deptSmallList: [],
       postList: [],
+      qrCodeStatus: false,
+      codeUrl: null,
       educationForm: {
         targetUserId: "",
       },
@@ -747,6 +760,11 @@ export default {
       this.detail = row;
       this.handoverVisible = true;
     },
+    onCreateQRCode() {
+      // 生成报名二维码
+      this.qrCodeStatus = true;
+      this.codeUrl = vaildTeachingUrl() + "/#/manageDownload";
+    },
     onBranchCheckAll() {
       this.form.organIdLists = [];
 

+ 242 - 0
src/views/noPermission.vue

@@ -0,0 +1,242 @@
+<template>
+  <div style="background: #fff;height: 100%;">
+    <div class="wscn-http404-container">
+      <div class="wscn-http404">
+        <div class="pic-404">
+          <img
+            class="pic-404__parent"
+            src="@/assets/404_images/permission_bg.png"
+            alt="permission"
+          />
+        </div>
+        <div class="bullshit">
+          <div class="bullshit__oops">暂无权限</div>
+          <!-- <div class="bullshit__info">All rights reserved
+          <a style="color:#20a0ff"
+             href="https://wallstreetcn.com"
+             target="_blank"> </a>
+        </div> -->
+          <div class="bullshit__info">
+            您的账号尚未配置角色权限,<br />请联系机构管理员配置角色权限
+          </div>
+          <!-- <a href="" class="bullshit__return-home">切换账号</a> -->
+          <el-button type="primary" @click="onChange">切换账号</el-button>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "Page404",
+  computed: {
+    message() {
+      return "很抱歉,你访问的页面不存在";
+    }
+  },
+  methods: {
+    onChange() {
+      this.$store.dispatch('user/resetToken').then(() => {
+        location.reload()
+      })
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.wscn-http404-container {
+  transform: translate(-50%, -50%);
+  position: absolute;
+  top: 40%;
+  left: 50%;
+}
+.wscn-http404 {
+  position: relative;
+  width: 1200px;
+  padding: 0 50px;
+  overflow: hidden;
+  .pic-404 {
+    position: relative;
+    float: left;
+    width: 600px;
+    overflow: hidden;
+    &__parent {
+      width: 100%;
+    }
+    &__child {
+      position: absolute;
+      &.left {
+        width: 80px;
+        top: 17px;
+        left: 220px;
+        opacity: 0;
+        animation-name: cloudLeft;
+        animation-duration: 2s;
+        animation-timing-function: linear;
+        animation-fill-mode: forwards;
+        animation-delay: 1s;
+      }
+      &.mid {
+        width: 46px;
+        top: 10px;
+        left: 420px;
+        opacity: 0;
+        animation-name: cloudMid;
+        animation-duration: 2s;
+        animation-timing-function: linear;
+        animation-fill-mode: forwards;
+        animation-delay: 1.2s;
+      }
+      &.right {
+        width: 62px;
+        top: 100px;
+        left: 500px;
+        opacity: 0;
+        animation-name: cloudRight;
+        animation-duration: 2s;
+        animation-timing-function: linear;
+        animation-fill-mode: forwards;
+        animation-delay: 1s;
+      }
+      @keyframes cloudLeft {
+        0% {
+          top: 17px;
+          left: 220px;
+          opacity: 0;
+        }
+        20% {
+          top: 33px;
+          left: 188px;
+          opacity: 1;
+        }
+        80% {
+          top: 81px;
+          left: 92px;
+          opacity: 1;
+        }
+        100% {
+          top: 97px;
+          left: 60px;
+          opacity: 0;
+        }
+      }
+      @keyframes cloudMid {
+        0% {
+          top: 10px;
+          left: 420px;
+          opacity: 0;
+        }
+        20% {
+          top: 40px;
+          left: 360px;
+          opacity: 1;
+        }
+        70% {
+          top: 130px;
+          left: 180px;
+          opacity: 1;
+        }
+        100% {
+          top: 160px;
+          left: 120px;
+          opacity: 0;
+        }
+      }
+      @keyframes cloudRight {
+        0% {
+          top: 100px;
+          left: 500px;
+          opacity: 0;
+        }
+        20% {
+          top: 120px;
+          left: 460px;
+          opacity: 1;
+        }
+        80% {
+          top: 180px;
+          left: 340px;
+          opacity: 1;
+        }
+        100% {
+          top: 200px;
+          left: 300px;
+          opacity: 0;
+        }
+      }
+    }
+  }
+  .bullshit {
+    position: relative;
+    float: left;
+    width: 365px;
+    padding: 50px 0 30px;
+    margin-left: 70px;
+    overflow: hidden;
+    &__oops {
+      font-size: 32px;
+      font-weight: bold;
+      line-height: 40px;
+      color: #000;
+      opacity: 0;
+      margin-bottom: 20px;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-fill-mode: forwards;
+    }
+    &__headline {
+      font-size: 20px;
+      line-height: 24px;
+      color: #222;
+      font-weight: bold;
+      opacity: 0;
+      margin-bottom: 10px;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-delay: 0.1s;
+      animation-fill-mode: forwards;
+    }
+    &__info {
+      font-size: 18px;
+      line-height: 31px;
+      color: #666666;
+      opacity: 0;
+      margin-bottom: 30px;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-delay: 0.2s;
+      animation-fill-mode: forwards;
+    }
+    &__return-home {
+      display: block;
+      float: left;
+      width: 110px;
+      height: 36px;
+      background: #1482f0;
+      border-radius: 100px;
+      text-align: center;
+      color: #ffffff;
+      opacity: 0;
+      font-size: 14px;
+      line-height: 36px;
+      cursor: pointer;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-delay: 0.3s;
+      animation-fill-mode: forwards;
+    }
+    @keyframes slideUp {
+      0% {
+        transform: translateY(60px);
+        opacity: 0;
+      }
+      100% {
+        transform: translateY(0);
+        opacity: 1;
+      }
+    }
+  }
+}
+</style>

+ 7 - 0
src/views/organManager/api.js

@@ -46,4 +46,11 @@ export const checkStudentPhone = (data) => request2({
   method: 'get',
   params: data,
   hideLoading: true
+})
+
+// 获取机构协议列表
+export const tenantContractRecordList = (data) => request2({
+  url: '/api-web/tenantContractRecord/queryPage',
+  method: 'post',
+  data
 })

+ 25 - 17
src/views/organManager/components/organInfo.vue

@@ -41,13 +41,13 @@
               <img v-if="form.logo" :src="form.logo" class="avatar" />
               <i v-else class="el-icon-plus avatar-uploader-icon"></i>
             </el-upload> -->
-            <image-cropper :options="cropperOptions" :disabled="isDisabled" :imgSize="2" accept=".png" :acceptArray="['image/png']" :imageUrl="form.logo" @crop-upload-close="cropClose" @crop-upload-success="cropSuccess" />
+            <image-cropper :options="cropperOptions" :disabled="isDisabled" :imgSize="5" accept=".png" :acceptArray="['image/png']" :imageUrl="form.logo" @crop-upload-success="cropSuccess" />
             <div
               class="tips"
               v-show="!isDisabled"
               style="line-height: 1.3;color: red;"
             >
-              仅支持图片格式:png,大小:2MB;
+              仅支持图片格式:png,大小:5MB;
             </div>
           </div>
         </el-form-item>
@@ -75,12 +75,12 @@
           <div style="width: 300px !important" class="ant-upload-preview">
             <el-upload
               class="avatar-uploader"
-              :class="[isDisabled || tenantInfo == 'SETTING' ? 'uploadDisabled' : null]"
+              :class="[isDisabled ? 'uploadDisabled' : null]"
               action="/api-web/uploadFile"
               accept=".png"
               :headers="headers"
               :show-file-list="false"
-              :disabled="isDisabled || tenantInfo == 'SETTING'"
+              :disabled="isDisabled"
               :on-success="handleAvatarSuccess"
               :before-upload="beforeAvatarUpload"
             >
@@ -96,7 +96,7 @@
               style="line-height: 1.3;color: red;"
             >
               请上传透明背景PNG格式公章图片;<br/>
-              仅支持图片格式:png,大小:2MB;
+              仅支持图片格式:png,大小:5MB;
             </div>
           </div>
         </el-form-item>
@@ -114,12 +114,12 @@
           <div style="width: 300px !important" class="ant-upload-preview">
             <el-upload
               class="avatar-uploader"
-              :class="[isDisabled || tenantInfo == 'SETTING' ? 'uploadDisabled' : null]"
+              :class="[isDisabled ? 'uploadDisabled' : null]"
               action="/api-web/uploadFile"
               accept=".png"
               :headers="headers"
               :show-file-list="false"
-              :disabled="isDisabled || tenantInfo == 'SETTING'"
+              :disabled="isDisabled"
               :on-success="handleAvatarSuccess2"
               :before-upload="beforeAvatarUpload"
             >
@@ -135,7 +135,7 @@
               style="line-height: 1.3;color: red;"
             >
               请上传透明背景PNG格式公章图片;<br/>
-              仅支持图片格式:png,大小:2MB;
+              仅支持图片格式:png,大小:5MB;
             </div>
           </div>
         </el-form-item>
@@ -187,7 +187,7 @@
       >
         <el-input
           v-model.trim="form.tsignName"
-          :disabled="isDisabled || tenantInfo == 'SETTING'"
+          :disabled="isDisabled"
           placeholder="请输入机构全称"
         ></el-input>
       </el-form-item>
@@ -269,7 +269,7 @@
       >
         <el-input
           v-model.trim="form.tsignCode"
-          :disabled="isDisabled || tenantInfo == 'SETTING'"
+          :disabled="isDisabled"
           placeholder="请输入营业执照编号"
         ></el-input>
       </el-form-item>
@@ -416,6 +416,7 @@ export default {
         corporateChops: null,
         corporateFinanceChops: null,
       },
+      originPhone: null, // 是否已存在手机号
       errorPhone: null, // 手机号存在错误信息
       payState: null,
       imageWidthM: 72,
@@ -435,8 +436,14 @@ export default {
   async mounted() {
     await this.getAreaList();
     if (this.data) {
-      this.form = { ...this.data };
+      this.form = Object.assign(this.form, this.data);
       this.payState = this.data.payState;
+
+      const { phone } = this.data
+      // 判断是否已存在手机号
+      if(phone) {
+        this.originPhone = phone
+      }
     }
     // 选中默认颜色
     this.themeList.forEach(theme => {
@@ -475,10 +482,6 @@ export default {
     }
   },
   methods: {
-    //上传操作结束
-    cropClose() {
-      console.log('上传操作结束')
-    },
     //上传图片成功
     cropSuccess(data) {
       // this.imgUrl = data.data.avatar
@@ -513,6 +516,11 @@ export default {
     },
     async checkPhone() {
       const phone = this.form.phone;
+      // 判断是否是自己之前注册的手机号
+      if(phone == this.originPhone && this.originPhone) {
+        this.errorPhone = null
+        return
+      }
       try {
         const regu = /^1[3456789]\d{9}$/;
         console.log(phone, regu);
@@ -587,13 +595,13 @@ export default {
         "image/png": true
       };
       const isImage = imageType[file.type];
-      const isLt2M = file.size / 1024 / 1024 < 2;
+      const isLt2M = file.size / 1024 / 1024 < 5;
       if (!isImage) {
         this.$message.error("只能上传图片格式!");
         return false;
       }
       if (!isLt2M) {
-        this.$message.error("上传头像图片大小不能超过 2MB!");
+        this.$message.error("上传头像图片大小不能超过 5MB!");
         return false;
       }
       return isImage && isLt2M;

+ 26 - 2
src/views/organManager/index.vue

@@ -122,6 +122,11 @@
                 @click="onQrCode(scope.row)"
                 type="text"
                 >缴费二维码</el-button>
+              <el-button
+                v-if="scope.row.payState === 1 && $helpers.permission('tenantContractRecord/queryPage')"
+                 @click="onDownloadProtocol(scope.row)"
+                type="text"
+                >下载协议</el-button>
             </template>
           </el-table-column>
         </el-table>
@@ -137,12 +142,17 @@
       </div>
     </div>
     <qr-code v-model="qrcodeStatus" title="机构缴费二维码" :codeUrl="codeUrl" />
+
+    <el-dialog title="协议下载" :visible.sync="protocolVisible" width="650px">
+      <protocol-model v-if="protocolVisible" @close="protocolVisible = false" :protocolVersions="protocolVersions" />
+    </el-dialog>
   </div>
 </template>
 <script>
 import pagination from "@/components/Pagination/index";
 import QrCode from "@/components/QrCode/index";
-import { tenantInfoQueryPage , tenantInfoOpsState } from "./api";
+import protocolModel from '@/views/studentManager/modals/protocolModel';
+import { tenantInfoQueryPage , tenantInfoOpsState, tenantContractRecordList } from "./api";
 import { getTimes } from "@/utils";
 import { vaildTeachingUrl } from "@/utils/validate";
 const initSearch = {
@@ -153,7 +163,7 @@ const initSearch = {
   createdName: null,
 };
 export default {
-  components: { pagination, QrCode },
+  components: { pagination, QrCode, protocolModel },
   data() {
     return {
       tableList: [],
@@ -167,6 +177,8 @@ export default {
       searchForm: { ...initSearch },
       qrcodeStatus: false,
       codeUrl: null,
+      protocolVisible: false,
+      protocolVersions: [],
     };
   },
   mounted() {
@@ -196,6 +208,18 @@ export default {
       this.searchForm = { ...initSearch };
       this.search();
     },
+    async onDownloadProtocol(item) {
+      try {
+        const res = await tenantContractRecordList({ tenantId: item.id })
+        console.log(res)
+        this.protocolVersions = res.data?.rows || [];
+        this.protocolVisible = true;
+      } catch(e) {
+        //
+        console.log(e)
+      }
+      // window.location.href = item.url;
+    },
     onQrCode(row) {
       // 生成报名二维码
       this.qrcodeStatus = true;

+ 7 - 0
src/views/productService/api.js

@@ -27,3 +27,10 @@ export const tenantInfoRePay = (data) => request2({
   url: '/api-web/tenantInfo/renew/' + data.id + '?val=' + data.val,
   method: 'get',
 })
+
+// 获取协议
+export const getContract = (data) => request2({
+  url: `/api-web/tenantInfo/getContract/${data.id}`,
+  method: 'get',
+  params: data
+})

+ 86 - 38
src/views/productService/model/serviceModel.vue

@@ -13,43 +13,50 @@
           <el-option label="微信支付" value="wx_pub"></el-option>
         </el-select>
       </el-form-item>
-    </el-form>
-    <el-table
-      style="width: 100%"
-      :header-cell-style="{ background: '#EDEEF0', color: '#444' }"
-      :data="tableList"
-    >
-      <el-table-column align="center" prop="platformServeName" label="产品名称">
-      </el-table-column>
-      <el-table-column align="center" label="学员上限">
-        <template slot-scope="scope">
-          {{ scope.row.studentUpLimit | numberFormat }}人
-        </template>
-      </el-table-column>
-      <el-table-column align="center" label="付费模式">
-        <template slot-scope="scope">
-          {{ scope.row.expiryUnit | memberEnumType }}
-        </template>
-      </el-table-column>
-      <el-table-column align="center" prop="num" label="数量">
-      </el-table-column>
-      <el-table-column
-        align="center"
-        label="原价(元)"
-      >
-        <template slot-scope="scope">
-          {{ scope.row.rechargeAmount | hasMoneyFormat }}
-        </template>
-      </el-table-column>
-      <el-table-column
-        align="center"
-        label="支付价格(元)"
+      <el-table
+        style="width: 100%; margin-bottom: 15px;"
+        :header-cell-style="{ background: '#EDEEF0', color: '#444' }"
+        :data="tableList"
       >
-        <template slot-scope="scope">
-          <span style="color: red;">{{ scope.row.value | hasMoneyFormat }}</span>
-        </template>
-      </el-table-column>
-    </el-table>
+        <el-table-column align="center" prop="platformServeName" label="产品名称">
+        </el-table-column>
+        <el-table-column align="center" label="学员上限">
+          <template slot-scope="scope">
+            {{ scope.row.studentUpLimit | numberFormat }}人
+          </template>
+        </el-table-column>
+        <el-table-column align="center" label="付费模式">
+          <template slot-scope="scope">
+            {{ scope.row.expiryUnit | memberEnumType }}
+          </template>
+        </el-table-column>
+        <el-table-column align="center" prop="num" label="数量">
+        </el-table-column>
+        <el-table-column
+          align="center"
+          label="原价(元)"
+        >
+          <template slot-scope="scope">
+            {{ scope.row.rechargeAmount | hasMoneyFormat }}
+          </template>
+        </el-table-column>
+        <el-table-column
+          align="center"
+          label="支付价格(元)"
+        >
+          <template slot-scope="scope">
+            <span style="color: red;">{{ scope.row.value | hasMoneyFormat }}</span>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <el-form-item prop="checked" :rules="[{required: true, message: '请阅读并同意协议', trigger: 'change'}]">
+        <el-checkbox v-model="payForm.checked" @change="onCheckChange">
+        </el-checkbox>
+          <span style="color: #606266; padding-left: 5px;cursor: pointer;" @click="onCheckBox">我已阅读并同意</span>
+          <span @click.stop="onLookContract" style="color: #01C1B5;cursor: pointer;">《产品及服务协议》</span>
+      </el-form-item>
+    </el-form>
 
     <span slot="footer" class="dialog-footer">
       <el-button @click="$listeners.close();">取 消</el-button>
@@ -57,6 +64,20 @@
     </span>
 
     <el-dialog
+      title="查看协议"
+      :visible.sync="lookVisible"
+      width="415px"
+      append-to-body
+    >
+      <previewProtocol
+        @close="lookVisible = false"
+        :look="true"
+        :fileContent="fileContent"
+        v-if="lookVisible"
+      />
+    </el-dialog>
+
+    <el-dialog
       title="支付二维码"
       :visible.sync="payMentVisible"
       :before-close="onClose"
@@ -72,16 +93,20 @@
 // import { recharge } from '../api'
 import { vaildStudentUrl } from '@/utils/validate'
 import payment from '@/views/productService/model/payment'
-import { tenantInfoRePay } from '../api'
+import previewProtocol from "@/views/tenantSetting/model/previewProtocol";
+import { tenantInfoRePay, getContract } from '../api'
 import { getTenantId } from '@/utils/auth'
 export default {
   props: ["value", "tenantInfo"],
-  components: { payment },
+  components: { payment, previewProtocol },
   data() {
     return {
       payForm: {
         payType: null,
+        checked: null,
       },
+      fileContent: null,
+      lookVisible: false,
       pay_channel: null, //支付渠道
       selectStudentMoney: 0, // 选中学生金额
       payMentVisible: false,
@@ -102,6 +127,18 @@ export default {
     }
   },
   methods: {
+    onCheckChange() {
+      if(!this.payForm.checked) {
+        this.payForm.checked = null
+      }
+    },
+    onCheckBox() {
+      if(this.payForm.checked) {
+        this.payForm.checked = null
+      } else {
+        this.payForm.checked = true
+      }
+    },
     onMemberPay() {
       this.$refs.payForm.validate(async (_) => {
         if(_) {
@@ -129,6 +166,17 @@ export default {
     onClose(done) {
       this.onPaymentClose(false, done)
     },
+    async onLookContract() {
+      // 如果有协议则不需要重新请求
+      if(!this.fileContent) {
+        try {
+          const tenantId = this.$helpers.tenantId
+          const res = await getContract({ id: tenantId, type: 1, renewCount: this.value })
+          this.fileContent = res.data
+        } catch(e) {}
+      }
+      this.lookVisible = true
+    },
     onPaymentClose(hideTip = false, callBack) {
       if(hideTip) {
         this.payMentVisible = false

+ 0 - 0
src/views/studentManager/modals/js.js


+ 60 - 0
src/views/studentManager/modals/protocolModel.vue

@@ -0,0 +1,60 @@
+<template>
+  <div>
+    <el-table
+      style="width: 100%"
+      :header-cell-style="{ background: '#EDEEF0', color: '#444' }"
+      :data="protocolVersions"
+    >
+      <el-table-column align="center" prop="studentId" label="协议名称">
+        <template slot-scope="scope">
+          <span>{{scope.row.contractName || name}} {{ scope.row.version ? 'v' + scope.row.version : null }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column align="center" label="签署时间" prop="createTime">
+      </el-table-column>
+      <el-table-column align="center" width="150px" label="操作">
+        <template slot-scope="scope">
+          <div>
+            <el-button type="text" @click="onDownloadProtocol(scope.row)"
+              >下载</el-button
+            >
+          </div>
+        </template>
+      </el-table-column>
+    </el-table>
+    <span slot="footer" class="dialog-footer">
+      <el-button @click="$listeners.close()">取 消</el-button>
+    </span>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'protocolModel',
+  props: {
+    name: {
+      type: String,
+      default: '产品与服务协议'
+    },
+    protocolVersions: {
+      type: [Array, Object],
+      default() {
+        return []
+      }
+    }
+  },
+  methods: {
+    onDownloadProtocol(item) {
+      window.location.href = item.url;
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.dialog-footer {
+  display: block;
+  text-align: right;
+  margin-top: 20px;
+}
+</style>

+ 3 - 53
src/views/studentManager/studentList.vue

@@ -845,44 +845,7 @@
     </el-dialog>
 
     <el-dialog title="协议下载" :visible.sync="protocolVisible" width="650px">
-      <div v-if="protocolVisible">
-        <!-- <el-alert
-          title="点击下载"
-          :closable="false"
-          type="info">
-        </el-alert>
-        <p style="font-size: 14px; color: var(--color-primary); line-height: 1.5; padding: 8px 16px; cursor: pointer;" v-for="item in protocolVersions" :key="item.id" @click="onDownloadProtocol(item)">
-          产品与服务协议{{ item.version == 2 ? "(含课程)" : "(含系统)" }}
-        </p> -->
-        <el-table
-          style="width: 100%"
-          :header-cell-style="{ background: '#EDEEF0', color: '#444' }"
-          :data="protocolVersions"
-        >
-          <el-table-column align="center" prop="studentId" label="协议名称">
-            <template slot-scope="scope">
-              <!-- {{ formatProtocol(scope.row.version) }} -->
-              <span
-                v-html="`产品与服务协议${formatProtocol(scope.row.version)}`"
-              ></span>
-            </template>
-          </el-table-column>
-          <el-table-column align="center" label="签署时间" prop="createTime">
-          </el-table-column>
-          <el-table-column align="center" width="150px" label="操作">
-            <template slot-scope="scope">
-              <div>
-                <el-button type="text" @click="onDownloadProtocol(scope.row)"
-                  >下载</el-button
-                >
-              </div>
-            </template>
-          </el-table-column>
-        </el-table>
-      </div>
-      <span slot="footer" class="dialog-footer">
-        <el-button @click="protocolVisible = false">取 消</el-button>
-      </span>
+      <protocol-model v-if="protocolVisible" @close="protocolVisible = false" :protocolVersions="protocolVersions" />
     </el-dialog>
 
     <el-dialog
@@ -906,6 +869,7 @@
 </template>
 <script>
 import pagination from "@/components/Pagination/index";
+import protocolModel from '@/views/studentManager/modals/protocolModel';
 import {
   queryStudentList,
   getStudentInfoByPhone,
@@ -925,7 +889,7 @@ import load from "@/utils/loading";
 import createMember from "./modals/createMember";
 export default {
   name: "studentManagerList",
-  components: { pagination, qrCode, createMember },
+  components: { pagination, qrCode, createMember, protocolModel },
   data() {
     return {
       studentVisible: false,
@@ -1065,17 +1029,6 @@ export default {
       this.qrcodeStatus = true;
       this.qrcodeUrl = vaildStudentUrl() + `/#/queryStudentPer?tenantId=`+ this.tenantId;
     },
-    formatProtocol(version) {
-      let str = "(含课程)";
-      if (version == 3) {
-        str = "(含系统)";
-      } else if (version == 4) {
-        str = "(含团练宝)";
-      } else if (version == 5) {
-        str = "(含团练宝<sup>+</sup>)";
-      }
-      return str;
-    },
     getList() {
       let params = {...this.searchForm};
       params.rows = this.pageInfo.limit;
@@ -1414,9 +1367,6 @@ export default {
         }
       });
     },
-    onDownloadProtocol(item) {
-      window.location.href = item.url;
-    },
     checkDate(dateStr) {
       let dayjs = this.$helpers.dayjs;
       let nowDate = new Date().getTime();

+ 239 - 0
src/views/systemMaintain.vue

@@ -0,0 +1,239 @@
+<template>
+  <div class="wscn-http404-container">
+    <div class="wscn-http404">
+      <div class="pic-404">
+        <img class="pic-404__parent"
+             src="@/assets/404_images/404.png"
+             alt="404">
+        <img class="pic-404__child left"
+             src="@/assets/404_images/404_cloud.png"
+             alt="404">
+        <img class="pic-404__child mid"
+             src="@/assets/404_images/404_cloud.png"
+             alt="404">
+        <img class="pic-404__child right"
+             src="@/assets/404_images/404_cloud.png"
+             alt="404">
+      </div>
+      <div class="bullshit">
+        <div class="bullshit__oops">OOPS!</div>
+        <!-- <div class="bullshit__info">All rights reserved
+          <a style="color:#20a0ff"
+             href="https://wallstreetcn.com"
+             target="_blank"> </a>
+        </div> -->
+        <div class="bullshit__headline">{{ message }}</div>
+        <div class="bullshit__info">请检查您输入的网址是否正确,或者点击链接继续浏览</div>
+        <a href=""
+           class="bullshit__return-home">返回首页</a>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+
+export default {
+  name: 'Page404',
+  computed: {
+    message () {
+      return '很抱歉,你访问的页面不存在'
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.wscn-http404-container {
+  transform: translate(-50%, -50%);
+  position: absolute;
+  top: 40%;
+  left: 50%;
+}
+.wscn-http404 {
+  position: relative;
+  width: 1200px;
+  padding: 0 50px;
+  overflow: hidden;
+  .pic-404 {
+    position: relative;
+    float: left;
+    width: 600px;
+    overflow: hidden;
+    &__parent {
+      width: 100%;
+    }
+    &__child {
+      position: absolute;
+      &.left {
+        width: 80px;
+        top: 17px;
+        left: 220px;
+        opacity: 0;
+        animation-name: cloudLeft;
+        animation-duration: 2s;
+        animation-timing-function: linear;
+        animation-fill-mode: forwards;
+        animation-delay: 1s;
+      }
+      &.mid {
+        width: 46px;
+        top: 10px;
+        left: 420px;
+        opacity: 0;
+        animation-name: cloudMid;
+        animation-duration: 2s;
+        animation-timing-function: linear;
+        animation-fill-mode: forwards;
+        animation-delay: 1.2s;
+      }
+      &.right {
+        width: 62px;
+        top: 100px;
+        left: 500px;
+        opacity: 0;
+        animation-name: cloudRight;
+        animation-duration: 2s;
+        animation-timing-function: linear;
+        animation-fill-mode: forwards;
+        animation-delay: 1s;
+      }
+      @keyframes cloudLeft {
+        0% {
+          top: 17px;
+          left: 220px;
+          opacity: 0;
+        }
+        20% {
+          top: 33px;
+          left: 188px;
+          opacity: 1;
+        }
+        80% {
+          top: 81px;
+          left: 92px;
+          opacity: 1;
+        }
+        100% {
+          top: 97px;
+          left: 60px;
+          opacity: 0;
+        }
+      }
+      @keyframes cloudMid {
+        0% {
+          top: 10px;
+          left: 420px;
+          opacity: 0;
+        }
+        20% {
+          top: 40px;
+          left: 360px;
+          opacity: 1;
+        }
+        70% {
+          top: 130px;
+          left: 180px;
+          opacity: 1;
+        }
+        100% {
+          top: 160px;
+          left: 120px;
+          opacity: 0;
+        }
+      }
+      @keyframes cloudRight {
+        0% {
+          top: 100px;
+          left: 500px;
+          opacity: 0;
+        }
+        20% {
+          top: 120px;
+          left: 460px;
+          opacity: 1;
+        }
+        80% {
+          top: 180px;
+          left: 340px;
+          opacity: 1;
+        }
+        100% {
+          top: 200px;
+          left: 300px;
+          opacity: 0;
+        }
+      }
+    }
+  }
+  .bullshit {
+    position: relative;
+    float: left;
+    width: 300px;
+    padding: 30px 0;
+    overflow: hidden;
+    &__oops {
+      font-size: 32px;
+      font-weight: bold;
+      line-height: 40px;
+      color: #1482f0;
+      opacity: 0;
+      margin-bottom: 20px;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-fill-mode: forwards;
+    }
+    &__headline {
+      font-size: 20px;
+      line-height: 24px;
+      color: #222;
+      font-weight: bold;
+      opacity: 0;
+      margin-bottom: 10px;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-delay: 0.1s;
+      animation-fill-mode: forwards;
+    }
+    &__info {
+      font-size: 13px;
+      line-height: 21px;
+      color: grey;
+      opacity: 0;
+      margin-bottom: 30px;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-delay: 0.2s;
+      animation-fill-mode: forwards;
+    }
+    &__return-home {
+      display: block;
+      float: left;
+      width: 110px;
+      height: 36px;
+      background: #1482f0;
+      border-radius: 100px;
+      text-align: center;
+      color: #ffffff;
+      opacity: 0;
+      font-size: 14px;
+      line-height: 36px;
+      cursor: pointer;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-delay: 0.3s;
+      animation-fill-mode: forwards;
+    }
+    @keyframes slideUp {
+      0% {
+        transform: translateY(60px);
+        opacity: 0;
+      }
+      100% {
+        transform: translateY(0);
+        opacity: 1;
+      }
+    }
+  }
+}
+</style>

+ 9 - 0
src/views/teacherManager/teacherList.vue

@@ -291,6 +291,12 @@
                   >修改密码</el-button
                 >
               </auth>
+
+              <el-button
+                v-if="scope.row.contractUrl"
+                @click="onDownloadProtocol(scope.row)"
+                type="text"
+                >下载协议</el-button>
               <!--  -->
             </template>
           </el-table-column>
@@ -487,6 +493,9 @@ export default {
         "是否确认导出老师列表?"
       );
     },
+    onDownloadProtocol(item) {
+      window.location.href = item.contractUrl;
+    },
     getList() {
       let params = {...this.searchForm};
       // params.organId = this.organId

+ 7 - 0
src/views/teamDetail/teamList.vue

@@ -280,6 +280,13 @@
               </div>
             </template>
           </el-table-column>
+          <el-table-column align="center" width="100px" label="收费标准">
+            <template slot-scope="scope">
+              <div>
+                {{ scope.row.chargeStandard | moneyFormat(true) }}
+              </div>
+            </template>
+          </el-table-column>
           <el-table-column
             align="center"
             width="220px"

+ 26 - 0
src/views/tenantSetting/api.js

@@ -22,4 +22,30 @@ export const tenantCloudCourseRecord = (data) => request2({
   url: '/api-web/tenantCloudCourseRecord/queryPage',
   method: 'post',
   data,
+})
+
+export const tenantContractTemplateAdd = (data) => request2({
+  url: '/api-web/tenantContractTemplate/add',
+  requestType: 'form',
+  method: 'post',
+  data
+})
+
+export const tenantContractTemplateList = (data) => request2({
+  url: '/api-web/tenantContractTemplate/list',
+  method: 'get',
+  params: data,
+})
+
+export const tenantContractTemplateQuery = (data) => request2({
+  url: '/api-web/tenantContractTemplate/query',
+  method: 'get',
+  params: data,
+})
+
+export const updateStatus = (data) => request2({
+  url: '/api-web/tenantContractTemplate/updateStatus',
+  method: 'post',
+  requestType: 'form',
+  data,
 })

+ 154 - 0
src/views/tenantSetting/model/addProtocol.vue

@@ -0,0 +1,154 @@
+<template>
+  <div id="addProtocol">
+    <el-form :model="form" label-width="100px" class="form" ref="form">
+      <el-form-item
+        label="协议名称"
+        prop="name"
+        :rules="[
+          {
+            required: true,
+            message: '请输入协议名称',
+            trigger: 'blur'
+          }
+        ]"
+      >
+        <el-input
+          v-model="form.name"
+          placeholder="请输入协议名称"
+          style="width: 100%"
+        ></el-input>
+      </el-form-item>
+      <el-form-item
+        label="协议号"
+        prop="contractNo"
+        :rules="[
+          {
+            required: true,
+            message: '请输入协议号',
+            trigger: 'blur'
+          }
+        ]"
+      >
+        <el-input
+          v-model="form.contractNo"
+          placeholder="请输入协议号"
+          style="width: 100%"
+        ></el-input>
+      </el-form-item>
+      <el-form-item
+        label="上传协议"
+        prop="origanalFileUrl"
+        :rules="[
+          {
+            required: true,
+            message: '请上传协议',
+            trigger: 'blur, change'
+          }
+        ]"
+      >
+        <singe-file-upload
+          tips="仅支持上传 doc/docx 格式文件"
+          buttonText="点击上传协议"
+          accept=".doc, .docx"
+          @inputFile="readFileInputEventAsArrayBuffer"
+          v-model="form.origanalFileUrl"
+        />
+      </el-form-item>
+    </el-form>
+    <span slot="footer" class="dialog-footer">
+      <el-button @click="$listeners.close()">取 消</el-button>
+      <el-button type="primary" @click="addSubmit">下一步</el-button>
+    </span>
+
+    <el-dialog
+      title="查看协议"
+      :visible.sync="lookVisible"
+      width="415px"
+      append-to-body
+    >
+      <previewProtocol
+        @close="lookVisible = false"
+        @onSubmit="onSubmit"
+        :fileContent="fileContent"
+        v-if="lookVisible"
+      />
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { tenantContractTemplateAdd } from '../api'
+import previewProtocol from "@/views/tenantSetting/model/previewProtocol";
+import mammoth from "mammoth";
+export default {
+  components: { previewProtocol },
+  data() {
+    return {
+      form: {
+        name: null,
+        contractNo: null,
+        origanalFileUrl: null,
+        status: 0, // 默认不启用
+        contractTemplateContent: null, // 后台需要html内容
+      },
+      fileContent: null, // 文件内容
+      lookVisible: false
+    };
+  },
+  mounted() {
+  },
+  methods: {
+    async onSubmit() {
+      try {
+        await tenantContractTemplateAdd(this.form)
+        this.$message.success('创建成功')
+        this.lookVisible = false
+        this.$listeners.close()
+        this.$listeners.getList()
+      } catch(e) {
+        //
+      }
+    },
+    addSubmit() {
+      this.$refs['form'].validate((_) => {
+        if(_) {
+          this.lookVisible = true
+          console.log(this.form)
+        }
+      })
+    },
+    displayResult(result) {
+      let html = result.value;
+      let newHTML = html.replace(//g, '')
+      .replace('<h1>', '<h2 style="font-size: 16px;font-weight: bold; padding-top: 15px;">')
+      this.form.contractTemplateContent = newHTML
+      this.fileContent = newHTML
+    },
+    readFileInputEventAsArrayBuffer(file) {
+      let reader = new FileReader();
+      let that = this
+      reader.onload = function(loadEvent) {
+        let arrayBuffer = loadEvent.target.result; //arrayBuffer
+        mammoth
+          .convertToHtml({ arrayBuffer: arrayBuffer })
+          .then(function(result) {
+            console.log(result)
+            that.displayResult(result)
+          })
+          .done();
+      };
+      reader.readAsArrayBuffer(file.raw);
+    }
+  }
+};
+</script>
+
+<style lang="less" scoped>
+.dialog-footer {
+  display: block;
+  text-align: right;
+}
+/deep/ .el-dialog__body {
+  padding: 10px 10px 20px;
+}
+</style>

+ 56 - 0
src/views/tenantSetting/model/previewProtocol.vue

@@ -0,0 +1,56 @@
+<template>
+  <div>
+    <div class="scroll">
+      <div v-html="fileContent"></div>
+    </div>
+    <span slot="footer" class="dialog-footer">
+      <el-button v-if="!look" @click="$listeners.close()">取 消</el-button>
+      <el-button type="primary" @click="addSubmit">确 认</el-button>
+    </span>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    fileContent: {
+      type: String,
+      default: ''
+    },
+    look: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+    }
+  },
+  async mounted() {
+  },
+  methods: {
+    addSubmit() {
+      if(this.look) {
+        this.$listeners.close()
+      } else {
+        this.$emit("onSubmit")
+      }
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.scroll {
+  overflow: auto;
+  max-height: 400px;
+  /deep/.container {
+    padding: 0;
+  }
+}
+.dialog-footer {
+  display: block;
+  text-align: right;
+  margin-top: 20px;
+}
+</style>

+ 134 - 0
src/views/tenantSetting/model/protocolTemplate.js

@@ -0,0 +1,134 @@
+export function headHtml (isPreview = true, title) {
+  let template = `
+  <!DOCTYPE html>
+  <html lang="en">
+  <head>
+      <meta charset="utf-8" />
+      <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+      <meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
+      <meta http-equiv="Pragma" content="no-cache" />
+      <meta http-equiv="Cache-Control" content="no-cache" />
+      <meta http-equiv="Expires" content="0" />
+      <title>${title || '产品与服务协议'}</title>
+      <style>
+          body { margin: 0; }
+          header {
+              height: 40px;
+              line-height: .40px;
+              color: #000;
+              font-size: 17px;
+              background: #fff;
+              box-shadow: 0px 1px 8px 0px rgba(0, 0, 0, 0.07);
+              text-align: center;
+          }
+          header .back {
+              width: 20px;
+              height: 20px;
+              position: absolute;
+              left: 12px;
+              top: 10px;
+          }
+          .container {
+              padding: 22px 20px 3px;
+              font-size: 14px;
+          }
+          h1 {
+              font-size: 18px;
+              text-align: center;
+              margin-bottom: 8px;
+          }
+          h2 {
+              font-size: 16px;
+              font-weight: bold;
+              padding-top: 15px;
+          }
+          h3 {
+              font-size: 14px;
+              font-weight: bold;
+          }
+          .signature {
+              padding-top: 50px;
+          }
+          .signature .sign {
+              position: relative;
+              width: 49%;
+              display: inline-block;
+          }
+          .signature span {
+              display: block;
+          }
+          .signature .cachet {
+              position: absolute;
+              top: -60px;
+              left: 0;
+              width: 150px;
+              height: 150px;
+          }
+          .iInfo {
+              display: flex;
+          }
+          .iInfo span {
+              flex: 1;
+          }
+          .iInfoContent, .iInfoContent span {
+              display: block;
+          }
+          .underline {
+              text-decoration: underline;
+          }
+          .bold {
+              font-weight: bold;
+          }
+      </style>
+  </head>
+  <body style="font-family:'SimSun'">
+      <div class="container">`
+    if(isPreview) {
+        return template + `<h1>《${title || '产品与服务协议'}》</h1>
+        甲方: <br/>
+        <div style="display: flex;">乙方:
+            <div style="flex: 1 auto;">
+                <div class="iInfo">
+                    <span>家长姓名: </span>
+                    <span>电话:</span>
+                </div>
+                <div class="iInfo">
+                    <span>身份证号:</span>
+                </div>
+                <div class="iInfo">
+                    <span>学生姓名:</span>
+                </div>
+                <div class="iInfo">
+                    <span>所在学校与班级:</span>
+                </div>
+                <div class="iInfo">
+                    <span>所在声部:</span>
+                </div>
+            </div>
+        </div>
+        <div>(本协议中“乙方“指学员及家长;”乙方学员“指购买甲方产品或服务的学员;”乙方家长“仅指乙方学员的法定监护人。)</div>`
+    } else {
+        return template + '<h1>《产品与服务协议》</h1>甲方:${companyName} <br/><div style="display: flex;">乙方:<div style="flex: 1 auto;"><div class="iInfo"><span>家长姓名:${studentInfo.realName!}</span><span>电话:${studentInfo.phone!}</span></div><#if studentInfo.certificateType == "IDENTITY"><div class="iInfo"><span>身份证号:${studentInfo.idCardNo!}</span></div></#if><div class="iInfo"><span>学生姓名:${studentInfo.username!}</span></div><#if studentInfo.grade?default("")?trim?length gt 1><div class="iInfo"><span>所在班级:${studentInfo.grade!} ${studentInfo.clazz!}</span></div></#if><#if studentInfo.subject.name?default("")?trim?length gt 1><div class="iInfo"><span>所在声部:${studentInfo.subject.name!}</span></div></#if></div></div><div>(本协议中“乙方“指学员及家长;”乙方学员“指购买甲方产品或服务的学员;”乙方家长“仅指乙方学员的法定监护人。)</div>'
+    }
+}
+
+// 默认预览
+export function footerHtml(isPreview = true) {
+    if(isPreview) {
+        // <img class="cachet" src="https://daya-online.oss-cn-beijing.aliyuncs.com/website/cachet.png" alt="" />
+        return `
+        <div class="signature">
+                    <div class="sign">甲方签章:
+                        <span>日期:</span>
+                    </div>
+                    <div class="sign">乙方签字:
+                        <span>日期: </span>
+                    </div>
+                </div>
+            </div>
+        </body>
+        </html>`
+    } else {
+        return '<div class="signature"><#if isShowVisualSeal><div class="sign">甲方签章:${companyName}<img class="cachet" src="${sealPicture!}" alt="" /><span>日期:${.now?string("yyyy年MM月dd日")}</span></div><div class="sign">乙方签章:${studentInfo.realName!}<span>日期:${.now?string("yyyy年MM月dd日")} </span></div><#else><div class="sign">甲方签章:${companyName}<span>日期:${.now?string("yyyy年MM月dd日")}</span></div><div class="sign">乙方签章:<span>日期:${.now?string("yyyy年MM月dd日")} </span></div></#if></div></body></html>'
+    }
+}

+ 203 - 0
src/views/tenantSetting/tenantInfoProtocol.vue

@@ -0,0 +1,203 @@
+<template>
+  <div class="m-container">
+    <h2>
+      <div class="squrt"></div>
+      机构协议管理
+    </h2>
+    <div class="m-core">
+      <save-form
+        :inline="true"
+        class="searchForm"
+        ref="searchForm"
+        @submit="search"
+        @reset="reset"
+        :saveKey="'tenantTradeManager'"
+        :model.sync="searchForm"
+      >
+        <el-form-item prop="status">
+          <el-select
+            v-model.trim="searchForm.status"
+            clearable
+            filterable
+            @clear="onClear('status')"
+            placeholder="状态"
+          >
+            <el-option label="启用" :value="1"></el-option>
+            <el-option label="停用" :value="0"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button native-type="submit" type="danger">搜索</el-button>
+          <el-button native-type="reset" type="primary">重置</el-button>
+        </el-form-item>
+      </save-form>
+      <el-button
+        type="primary"
+        style="margin-bottom: 20px"
+        @click="protocolVisible = true"
+        >新增协议</el-button
+      >
+      <!-- 列表 -->
+      <div class="tableWrap">
+        <el-table
+          :data="tableList"
+          :header-cell-style="{ background: '#EDEEF0', color: '#444' }"
+        >
+          <el-table-column align="center" label="协议编号" prop="id">
+          </el-table-column>
+          <el-table-column align="center" label="协议号" prop="contractNo">
+          </el-table-column>
+          <el-table-column align="center" label="协议名称" prop="name">
+          </el-table-column>
+          <el-table-column align="center" label="上传时间" prop="updateTime">
+          </el-table-column>
+          <el-table-column align="center" label="操作人" prop="latestOperator">
+          </el-table-column>
+          <el-table-column align="center" label="状态">
+            <template slot-scope="scope">
+              {{ scope.row.status ? '启用' : '停用' }}
+            </template>
+          </el-table-column>
+          <el-table-column align="center" label="操作">
+            <template slot-scope="scope">
+              <el-button
+                @click="openService(scope.row, 'look')"
+                type="text"
+                >查看</el-button>
+                <el-button
+                @click="openService(scope.row, 'open')"
+                v-if="!scope.row.status"
+                v-permission="'tenantContractTemplate/updateStatus'"
+                type="text"
+                >启用</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+        <pagination
+          :saveKey="'tenantTradeManager'"
+          sync
+          :total.sync="pageInfo.total"
+          :page.sync="pageInfo.page"
+          :limit.sync="pageInfo.limit"
+          :page-sizes="pageInfo.page_size"
+          @pagination="getList"
+        />
+      </div>
+    </div>
+
+    <el-dialog title="新增协议" :visible.sync="protocolVisible" width="560px">
+      <addProtocol v-if="protocolVisible" @close="protocolVisible = false" @getList="getList"  />
+    </el-dialog>
+    <el-dialog
+      title="查看协议"
+      :visible.sync="lookVisible"
+      width="415px"
+      append-to-body
+    >
+      <previewProtocol
+        @close="lookVisible = false"
+        :look="true"
+        :fileContent="fileContent"
+        v-if="lookVisible"
+      />
+    </el-dialog>
+  </div>
+</template>
+<script>
+import pagination from "@/components/Pagination/index";
+import previewProtocol from "@/views/tenantSetting/model/previewProtocol";
+import { tenantContractTemplateList, updateStatus } from "./api";
+import { tenantStatus } from '@/constant'
+import { dealStatus } from "@/utils/searchArray";
+import addProtocol from './model/addProtocol'
+const initSearch = {
+  status: null
+};
+export default {
+  components: { pagination, addProtocol, previewProtocol },
+  data() {
+    const baseTenantId = sessionStorage.getItem('baseTenantId')
+    return {
+      baseTenantId,
+      tenantStatus,
+      dealStatus,
+      tableList: [],
+      protocolVisible: false,
+      pageInfo: {
+        // 分页规则
+        limit: 10, // 限制显示条数
+        page: 1, // 当前页
+        total: 0, // 总条数
+        page_size: [10, 20, 40, 50], // 选择限制显示条数
+      },
+      lookVisible: false,
+      fileContent: null,
+      searchForm: { ...initSearch },
+    };
+  },
+  async mounted() {
+    this.getList();
+  },
+  methods: {
+    async getList() {
+      try {
+        let { createTimer, ...reset } = this.searchForm;
+        const res = await tenantContractTemplateList({
+          ...reset,
+          page: this.pageInfo.page,
+          rows: this.pageInfo.limit,
+        });
+        console.log(res)
+        this.pageInfo.total = res.data.total;
+        this.tableList = res.data.rows;
+      } catch (e) {}
+    },
+    search() {
+      this.pageInfo.page = 1;
+      this.$refs.searchForm.save(this.searchForm);
+      this.$refs.searchForm.save(this.pageInfo, "page");
+      this.getList();
+    },
+    reset() {
+      this.searchForm = { ...initSearch };
+      this.search();
+    },
+    async openService(row, type) {
+      if(type == 'look') {
+        this.fileContent = row.contractTemplateContent
+        this.lookVisible = true
+      } else if(type == 'open') {
+        try {
+          this.$confirm(`同一时间协议只能启用一个,启用后,已启用的协议将自动停用?`, "提示", {
+            confirmButtonText: "确定",
+            cancelButtonText: "取消",
+            type: "warning",
+          }).then( async() => {
+            await updateStatus({ id: row.id })
+            this.$message.success('启用成功')
+            this.getList()
+          });
+        } catch(e) {
+          //
+        }
+      }
+      // this.protocolVisible = true
+    },
+  },
+  filters: {
+    tenantOrderStatus(val) {
+      const template = {
+        0: "待支付",
+        1: "已支付",
+        2: "支付失败"
+      }
+      return template[val]
+    }
+  }
+};
+</script>
+<style lang="scss" scoped>
+.courseMask .el-dialog__body {
+  padding-bottom: 0;
+}
+</style>