Browse Source

Merge remote-tracking branch 'origin/master'

yanglingling 11 months ago
parent
commit
14bc4523e3

+ 3 - 0
package.json

@@ -11,7 +11,10 @@
     "@fullcalendar/core": "^6.1.15",
     "@fullcalendar/daygrid": "^6.1.15",
     "@fullcalendar/interaction": "^6.1.15",
+    "@fullcalendar/timegrid": "^6.1.5",
     "@fullcalendar/vue": "^6.1.15",
+    "lodash": "^4.17.20",
+    "moment": "^2.29.4",
     "await-to-js": "^3.0.0",
     "axios": "^0.19.0",
     "core-js": "^2.6.5",

+ 1 - 0
public/config.js

@@ -12,6 +12,7 @@ const $GlobalConfig = {
   // 登录验证微服务名称
   VUE_APP_AdminPath: 'dashoo.labsop.admin',
   VUE_APP_SETTING_PATH: 'dashoo.labsop.finance-zzh',
+  VUE_APP_FINANCE_PATH: 'dashoo.labsop.finance',
   VUE_APP_INSTR_PATH: 'dashoo.labsop.apparatus',
   VUE_APP_SCIENTIFIC_PATH: 'dashoo.labsop.scientific-yll',
   // 租户码

+ 23 - 15
src/App.vue

@@ -9,15 +9,22 @@
           <el-menu :default-active="defaultActive"
                    class="el-menu-demo"
                    mode="horizontal"
-                   menu-trigger="click"
+                   menu-trigger="hover"
                    @select="handleSelect"
                    :router="router">
             <el-menu-item index="/">首页</el-menu-item>
             <el-menu-item index="/introduce">中心介绍</el-menu-item>
             <el-menu-item index="/news">新闻动态</el-menu-item>
             <el-submenu index="/appointment" popper-class="custom-submenu">
-              <template slot="title">预约管理</template>
-              <el-menu-item index="2-1">仪器设备</el-menu-item>
+              <template slot="title">
+                预约管理
+              </template>
+              <el-menu-item index="/appointment/equipment">
+                仪器设备
+              </el-menu-item>
+              <el-menu-item index="/appointment/equipment-details">
+                仪器设备详情
+              </el-menu-item>
               <el-menu-item index="2-2">中心平台</el-menu-item>
               <el-menu-item index="2-3">技术服务</el-menu-item>
             </el-submenu>
@@ -26,19 +33,20 @@
             <el-menu-item index="/contact-us">联系我们</el-menu-item>
           </el-menu>
         </div>
-        <div class="user"
-             v-if="$route.path != '/login'">
+        <div class="user" v-if="$route.path != '/login'">
           <template v-if="!userInfo.id">
-            <el-button type="text"
-                     @click="toLogin">登录</el-button>
-            <el-button type="text"
-                     @click="onRegister">注册</el-button>
+            <el-button type="text" @click="toLogin">登录</el-button>
+            <el-button type="text" @click="onRegister">注册</el-button>
           </template>
           <template v-else>
-            <el-button type="primary"
-                     size="mini"
-                     color="#386AFE"
-                     @click="toCenter">个人中心</el-button>
+            <el-button
+              type="primary"
+              size="mini"
+              color="#386AFE"
+              @click="toCenter"
+            >
+              个人中心
+            </el-button>
             <el-dropdown class="ml12" size="medium" @command="handleCommand">
               <span class="flex flex-center">
                 <img :src="userInfo.avatar" />
@@ -94,13 +102,13 @@ export default {
       this.$router.push("/personal-center");
     },
     handleCommand(command) {
-      if(command == 'logOut') {
+      if (command == "logOut") {
         this.$confirm("确定注销吗?", "提示", {
           confirmButtonText: "确定",
           cancelButtonText: "取消",
           type: "warning"
         }).then(() => {
-          this.$store.dispatch('logOut')
+          this.$store.dispatch('logOut').then(() => this.$router.push('/'))
         })
       }
     }

+ 144 - 14
src/api/instr/index.js

@@ -8,12 +8,14 @@
  */
 import request from "@/utils/micro_request";
 
-const basePath = $GlobalConfig.VUE_APP_INSTR_PATH;
+const insrtPath = $GlobalConfig.VUE_APP_INSTR_PATH;
+const financePath = $GlobalConfig.VUE_APP_FINANCE_PATH;
+const basePath = $GlobalConfig.VUE_APP_AdminPath;
 
 // 获取仪器列表
 export function getInstrList(data) {
-  return request.postRequest(
-    basePath,
+  return request.postRequestWithClientInfo(
+    insrtPath,
     "JiangSuUniversity",
     "GetList",
     data
@@ -21,26 +23,26 @@ export function getInstrList(data) {
 }
 // 获取仪器详情
 export function getInstrDetails(data) {
-  return request.postRequest(
-    basePath,
+  return request.postRequestWithClientInfo(
+    insrtPath,
     "JiangSuUniversity",
     "GetEntityById",
     data
   );
 }
-// 获取附件列表 
+// 获取附件列表
 export function getFileTable(data) {
-  return request.postRequest(
-    basePath,
+  return request.postRequestWithClientInfo(
+    insrtPath,
     "JiangSuUniversity",
     "GetDocumentListByInst",
     data
   );
 }
-// 获取公告列表 
+// 获取公告列表
 export function getNoticeTable(data) {
-  return request.postRequest(
-    basePath,
+  return request.postRequestWithClientInfo(
+    insrtPath,
     "JiangSuUniversity",
     "GetNoticeListByInst",
     data
@@ -48,10 +50,138 @@ export function getNoticeTable(data) {
 }
 // 获取型号数量
 export function getInstNameEnCount(data) {
-  return request.postRequest(
-    basePath,
+  return request.postRequestWithClientInfo(
+    insrtPath,
     "JiangSuUniversity",
     "GetInstNameEnCount",
     data
   );
-}
+}
+
+// 获取计费信息
+export function getChargeCfg(data) {
+  return request.postRequestWithClientInfo(
+    insrtPath,
+    "JiangSuUniversity",
+    "ChargeCfg",
+    data
+  );
+}
+
+// 获取送样信息
+export function getSampleOption(data) {
+  return request.postRequestWithClientInfo(
+    insrtPath,
+    "JiangSuUniversity",
+    "SampleOption",
+    data
+  );
+}
+
+// 获取预约信息
+export function getAppointListByInst(data) {
+  return request.postRequestWithClientInfo(
+    insrtPath,
+    "JiangSuUniversity",
+    "GetListByInst",
+    data
+  );
+}
+
+// 获取仪器预约设置详情
+export function getSettingDetail(data) {
+  return request.postRequestWithClientInfo(
+    insrtPath,
+    "TusInstrument",
+    "GetConfig",
+    data
+  );
+}
+// 获取全部预约情况
+export function getAppointInfo(data) {
+  return request.postRequestWithClientInfo(
+    insrtPath,
+    "TusInstrumentAppointment",
+    "AppointInfo",
+    data
+  );
+}
+
+// 是否在黑名单中
+export function checkInBlacklist(data) {
+  return request.postRequestWithClientInfo(
+    insrtPath,
+    "TusBlacklist",
+    "IsBlocked",
+    data
+  );
+}
+
+// 获取通知列表
+export function getNoticelist(data) {
+  return request.postRequestWithClientInfo(
+    insrtPath,
+    "TusInstrumentNotice",
+    "GetList",
+    data
+  );
+}
+// 获取技术服务列表
+export function getTechList(data) {
+  return request.postRequestWithClientInfo(
+    insrtPath,
+    "TechTechnicalService",
+    "GetList",
+    data
+  );
+}
+// 获取设备预约情况
+export function getInstrAppointInfo(data) {
+  return request.postRequestWithClientInfo(
+    insrtPath,
+    "TusInstrumentAppointment",
+    "GetEntityById",
+    data
+  );
+}
+
+// 预约新增
+export function appointAdd(data) {
+  return request.postRequestWithClientInfo(
+    insrtPath,
+    "TusInstrumentAppointment",
+    "Create",
+    data
+  );
+}
+// 预约编辑
+export function appointUpdate(data) {
+  return request.postRequestWithClientInfo(
+    insrtPath,
+    "TusInstrumentAppointment",
+    "UpdateById",
+    data
+  );
+}
+// 获取用户列表
+export function getUserList(data) {
+  return request.postRequestWithClientInfo(basePath, "User", "GetList", data);
+}
+// 列表
+export function getFinanceAccountList(data) {
+  return request.postRequestWithClientInfo(
+    financePath,
+    "Finance",
+    "GetFinanceAccountList",
+    data
+  );
+}
+// 当前用户课题详情
+export function getMySelfProjectGroup(data) {
+  return request.postRequestWithClientInfo(
+    basePath,
+    "ProjectGroup",
+    "GetProjectGroupList",
+    data
+  );
+}

+ 7 - 3
src/api/login.js

@@ -2,7 +2,7 @@
  * @Author: wanglj
  * @Date: 2022-04-25 10:38:19
  * @LastEditors: wanglj
- * @LastEditTime: 2025-01-11 09:13:02
+ * @LastEditTime: 2025-01-11 10:58:19
  * @Description: file content
  * @FilePath: \labsop_website\src\api\login.js
  */
@@ -18,11 +18,15 @@ export function signIn(data) {
 export function signOut(data) {
   return request.postRequestWithClientInfo(basePath, "System", "Logout", data);
 }
- // 根据用户名获取用户信息
+// 首页注册
+export function register(query) {
+  return request.postRequest(basePath, 'System', 'Register', query)
+}
+// 根据用户名获取用户信息
 export function getUserByUserName(data) {
   return request.postRequest(basePath, "User", "GetUserByUserName", data);
 }
 // 通知公告列表
 export function getNoticeList(query) {
-  return request.postRequest(basePath,'Notice','GetList', query)
+  return request.postRequest(basePath, 'Notice', 'GetList', query)
 }

+ 3 - 1
src/assets/styles/index.scss

@@ -97,6 +97,8 @@ body,
 }
 
 .custom-submenu {
+  border-radius: 4px;
+  overflow: hidden;
   .el-menu {
     padding: 0;
     border-radius: 4px;
@@ -120,7 +122,7 @@ body,
 
 .el-card {
   border-radius: 8px !important;
-  
+
   .header {
     display: flex;
     align-items: center;

+ 1 - 1
src/components/RightContent.vue

@@ -57,7 +57,7 @@
            v-html="document.remark"></div>
       <div slot="footer" class="card-footer">
         附件【
-        <el-link @click="toDownload(document)">{{ document.docUrlName }}</el-link>
+        <el-link type="primary" @click="toDownload(document)">{{ document.docUrlName }}</el-link>
         】已下载{{document.downloadCount}}次
       </div>
     </div>

+ 5 - 2
src/views/Case/RightContent.vue

@@ -2,9 +2,9 @@
  * @Author: wanglj wanglijie@dashoo.cn
  * @Date: 2025-01-09 10:52:48
  * @LastEditors: wanglj
- * @LastEditTime: 2025-01-09 19:55:04
+ * @LastEditTime: 2025-01-11 11:45:53
  * @Description: file content
- * @FilePath: \labsop_website\src\components\RightContent.vue
+ * @FilePath: \labsop_website\src\views\Case\RightContent.vue
 -->
 <template>
   <el-card class="right-content">
@@ -245,4 +245,7 @@ export default {
     }
   }
 }
+::v-deep .el-card__body {
+  height: auto !important;
+}
 </style>

+ 97 - 23
src/views/Register.vue

@@ -7,10 +7,10 @@
       </div>
       <div>
         <ul class="choose-type">
-          <li :class="{ active: type == 'person' }"
-              @click="changeType('person')"><i class="el-icon-user-solid"></i>注册课题组成员</li>
-          <li :class="{ active: type == 'project' }"
-              @click="changeType('project')"><i class="el-icon-notebook-2"></i>注册课题组负责人</li>
+          <li :class="{ active: form.registerType == '10' }"
+              @click="changeType('10')"><i class="el-icon-user-solid"></i>注册课题组成员</li>
+          <li :class="{ active: form.registerType == '20' }"
+              @click="changeType('20')"><i class="el-icon-notebook-2"></i>注册课题组负责人</li>
         </ul>
         <el-steps :space="200"
                   :active="active"
@@ -19,7 +19,7 @@
                   class="mb20">
           <el-step title="登录信息"></el-step>
           <el-step title="个人信息"></el-step>
-          <el-step title="其它信息"></el-step>
+          <!-- <el-step title="其它信息"></el-step> -->
         </el-steps>
         <el-form ref="loginInfoRef"
                  size="mini"
@@ -28,10 +28,10 @@
                  :rules="rules"
                  label-width="80px">
           <el-form-item label="登录账号"
-                        prop="username">
+                        prop="userName">
             <el-input name="username"
                       placeholder="请输入登录账号"
-                      v-model="form.username"></el-input>
+                      v-model="form.userName"></el-input>
           </el-form-item>
           <el-form-item label="密码"
                         prop="password">
@@ -57,9 +57,9 @@
                  :rules="rules"
                  label-width="80px">
           <el-form-item label="姓名"
-                        prop="name">
+                        prop="nickName">
             <el-input placeholder="请输入姓名"
-                      v-model="form.name"></el-input>
+                      v-model="form.nickName"></el-input>
           </el-form-item>
           <el-form-item label="性别"
                         prop="sex">
@@ -113,9 +113,16 @@
             </el-input>
           </el-form-item>
           <el-form-item label="课题组"
-                        prop="projectId">
-            <el-select v-model="form.projectId"
+                        prop="applyPgName"
+                        v-if="form.registerType === '20'">
+            <el-input v-model="form.applyPgName"></el-input>
+          </el-form-item>
+          <el-form-item label="课题组"
+                        prop="applyPg"
+                        v-else>
+            <el-select v-model="form.applyPg"
                        placeholder="请选择课题组"
+                       value-key="id"
                        class="w100">
               <el-option v-for="item in pjtList"
                          :key="item.id"
@@ -124,8 +131,8 @@
             </el-select>
           </el-form-item>
           <el-form-item label="所在时间"
-                        prop="projectId" 
-                        v-show="type === 'person'">
+                        prop="projectDate"
+                        v-show="form.registerType === '10'">
             <el-date-picker v-model="form.projectDate"
                             type="daterange"
                             class="w100"
@@ -140,8 +147,13 @@
                      v-if="active > 0"
                      @click="preStep">上一步</el-button>
           <el-button size="mini"
+                     v-if="active < 1"
                      type="primary"
                      @click="nextStep">下一步</el-button>
+          <el-button size="mini"
+                     v-if="active === 1"
+                     @click="onRegister"
+                     type="primary">注册</el-button>
         </div>
       </div>
     </el-card>
@@ -151,7 +163,7 @@
 <script>
 import crypto from "sm-crypto";
 import { setToken } from "@/utils/auth";
-import { signIn } from "@/api/login";
+import { register } from "@/api/login";
 import to from "await-to-js";
 import {
   getDictDataByType,
@@ -169,6 +181,14 @@ export default {
         callback();
       }
     };
+    const checkDate = (rule, value, callback) => {
+      console.log(value, 'valueeeeee');
+      
+      if(this.form.registerType === '10' && !value.length) {
+        callback(new Error("请选择所在时间"));
+      }
+      callback()
+    };
     return {
       active: 0,
       type: "person",
@@ -185,7 +205,7 @@ export default {
         groupIds: [],
         phone: "", // 手机号
         email: "", // 邮箱
-        sex: "", // 性别
+        sex: "30", // 性别
         password: "", // 账户密码
         confirmPassword: "",
         status: "10", // 用户状态
@@ -197,9 +217,13 @@ export default {
         projectId: null,
         projectName: "",
         projectDate: [],
+        registerType: '10',
+        applyPg: {},
+        applyPgName: '',
+        applyPgId: null
       },
       rules: {
-        username: [
+        userName: [
           { required: true, message: "请输入用户名", trigger: "blur" },
         ],
         password: [
@@ -214,13 +238,30 @@ export default {
         confirmPassword: [
           { required: true, validator: checkPassword, trigger: "blur" },
         ],
+        nickName: [{ required: true, message: "请输入姓名", trigger: "blur" }],
+        userType: [
+          { required: true, message: "请选择用户类型", trigger: "change" },
+        ],
+        deptId: [
+          { required: true, message: "请选择组织部门", trigger: "change" },
+        ],
+        idCode: [{ required: true, message: "请输入证件号", trigger: "blur" }],
+        applyPg: [
+          { required: true, message: "请选择课题组", trigger: "change" },
+        ],
+        applyPgName: [
+          { required: true, message: "请输入课题组", trigger: "blur" },
+        ],
+        projectDate: [
+          { required: true, validator: checkDate, trigger: "change" },
+        ]
       },
       loading: false,
       userTypeList: [],
       userSexList: [],
       userCertList: [],
       deptData: [],
-      projectList: [],
+      pjtList: [],
     };
   },
   mounted() {
@@ -233,17 +274,17 @@ export default {
         getDictDataByType("sys_com_sex"),
         getDictDataByType("sys_user_certificate"),
         getDeptTree(),
-        // getProjectGroupList(),
+        getProjectGroupList(),
       ]).then(([type, sex, cert, dept, pjt]) => {
         this.userTypeList = type.data.values || [];
         this.userSexList = sex.data.values || [];
         this.userCertList = cert.data.values || [];
         this.deptData = dept.data || [];
-        // this.projectList = pjt.data.list || [];
+        this.pjtList = pjt.data.list || [];
       });
     },
     changeType(val) {
-      this.type = val;
+      this.form.registerType = val;
     },
     // 部门选择
     deptChange() {
@@ -252,10 +293,10 @@ export default {
       this.form.deptName = nodes[0].label;
     },
     preStep() {
-      this.active--
+      this.active--;
     },
     nextStep() {
-      if (this.active < 3) {
+      if (this.active < 2) {
         let form = "loginInfoRef";
         if (this.active == 1) {
           form = "personInfoRef";
@@ -267,6 +308,36 @@ export default {
         });
       }
     },
+    onRegister() {
+      this.$refs.personInfoRef.validate(async valid => {
+        if(valid) {
+          const params = JSON.parse(JSON.stringify(this.form))
+          params.password = crypto.sm3(params.password);
+          delete params.confirmPassword
+          if(params.projectDate.length) {
+            params.startDate = params.projectDate[0]
+            params.endDate = params.projectDate[1]
+          }
+          delete params.projectDate
+          if(params.registerType === '10') {
+            params.applyPgId = params.applyPg.id
+            params.applyPgName = params.applyPg.padName
+          }
+          delete params.applyPg
+          const [err] = await to(register(params));
+          if(err) {
+            this.$message.warning('操作失败')
+            return
+          }
+          this.$message.success({
+            message: '注册成功,即将跳转登录页',
+            onClose: () => {
+              this.$router.push('/login')
+            }
+          })
+        }
+      })
+    },
     login() {
       if (this.active < 3) {
         this.active++;
@@ -290,7 +361,7 @@ a {
   display: flex;
   align-items: center;
   justify-content: center;
-  background: url('../assets/img/login-bg.png') center no-repeat;
+  background: url("../assets/img/login-bg.png") center no-repeat;
   background-size: 100% 100%;
 }
 
@@ -356,4 +427,7 @@ a {
   background-color: #fff;
   width: 40%;
 }
+.el-steps {
+  justify-content: center;
+}
 </style>

+ 556 - 0
src/views/equipment/components/appoint-create.vue

@@ -0,0 +1,556 @@
+<template>
+  <div v-if="state.isShowDialog">
+    <el-dialog
+      :title="title"
+      :visible.sync="state.isShowDialog"
+      :close-on-click-modal="false"
+      width="800px"
+    >
+      <div class="add-inst-content">
+        <div class="r-form">
+          <el-form
+            ref="editFormRef"
+            :model="form"
+            label-width="100px"
+            size="mini"
+            label-position="top"
+            :rules="rules"
+            closeable
+          >
+            <!-- 附加信息 -->
+            <div class="form-group">
+              <el-row :gutter="14" class="form-row-wrap">
+                <el-col :span="12">
+                  <el-form-item label="开始时间" prop="startTime">
+                    <el-date-picker
+                      type="datetime"
+                      placeholder="请输入"
+                      class="w100"
+                      v-model="form.startTime"
+                      value-format="yyyy-MM-dd HH:mm:ss"
+                      format="yyyy-MM-dd HH:mm"
+                    />
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="结束时间" prop="endTime">
+                    <el-date-picker
+                      type="datetime"
+                      placeholder="请输入"
+                      class="w100"
+                      v-model="form.endTime"
+                      value-format="yyyy-MM-dd HH:mm:ss"
+                      format="yyyy-MM-dd HH:mm"
+                    />
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item
+                    :label="activateService ? '课题/服务' : '课题组'"
+                    prop="projectType"
+                  >
+                    <el-row :gutter="20" class="w100">
+                      <el-col :span="12" v-if="activateService">
+                        <el-select
+                          :disabled="form.id"
+                          v-model="form.projectType"
+                          class="w100"
+                          placeholder="请选择"
+                          clearable
+                          @change="changeProjectType"
+                        >
+                          <el-option label="课题组" value="project" />
+                          <el-option label="服务" value="service" />
+                        </el-select>
+                      </el-col>
+                      <!-- 选择 经费卡、课题组 -->
+                      <el-col :span="activateService ? 12 : 24">
+                        <el-select
+                          style="width: 100%"
+                          v-if="form.projectType === 'project'"
+                          :disabled="form.id"
+                          v-model.number="form.projectId"
+                          class="w100"
+                          placeholder="请选择"
+                          clearable
+                          @change="changeProject"
+                        >
+                          <el-option
+                            :label="v.projectName"
+                            :value="v.projectId"
+                            v-for="(v, i) in projectOptions"
+                            :key="i"
+                          />
+                        </el-select>
+                        <el-select
+                          :disabled="form.id"
+                          v-if="form.projectType === 'service'"
+                          v-model.number="form.serviceId"
+                          class="w100"
+                          placeholder="请选择"
+                          clearable
+                          @change="changeService"
+                        >
+                          <el-option
+                            :label="v.name"
+                            :value="v.id"
+                            v-for="(v, i) in serviceOptions"
+                            :key="i"
+                          />
+                        </el-select>
+                      </el-col>
+                    </el-row>
+                    <!-- <el-cascader :options="options" style="width: 100%" /> -->
+                    <!-- <el-input placeholder="请输入" readonly :rows="3" class="w100" v-model="form.projectName" /> -->
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="经费卡" prop="expenseCardId">
+                    <el-select
+                      v-model.number="form.expenseCardId"
+                      class="w100"
+                      placeholder="请选择"
+                      clearable
+                      :disabled="form.projectType == 'service'"
+                    >
+                      <el-option
+                        :label="v.finAccount"
+                        :value="v.id"
+                        :key="v.id"
+                        v-for="v in fundsList"
+                      />
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="预约人" prop="userName" required>
+                    <el-select
+                      v-model="form.userId"
+                      class="w100"
+                      filterable
+                      placeholder="请选择"
+                      @change="changeUser"
+                    >
+                      <el-option
+                        :value="v.id"
+                        :label="v.nickName"
+                        :key="v.id"
+                        v-for="v in userList"
+                      />
+                    </el-select>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="联系电话" prop="userContact">
+                    <el-input
+                      placeholder="请输入"
+                      class="w100"
+                      maxlength="11"
+                      v-model="form.userContact"
+                    />
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item label="辅助上机" prop="assistEnable" required>
+                    <el-radio-group v-model="form.assistEnable">
+                      <el-radio :value="false" :label="false" size="large">
+                        否
+                      </el-radio>
+                      <el-radio :value="true" :label="true" size="large">
+                        是
+                      </el-radio>
+                    </el-radio-group>
+                    <el-text
+                      v-if="form.assistEnable"
+                      class="ml10"
+                      type="danger"
+                    >
+                      注:需要联系仪器负责人确认
+                    </el-text>
+                  </el-form-item>
+                </el-col>
+                <el-col :span="24">
+                  <el-form-item label="备注" prop="assistEnable" required>
+                    <el-input
+                      type="textarea"
+                      placeholder="请输入"
+                      :rows="4"
+                      class="w100"
+                      maxlength="300"
+                      v-model="form.remark"
+                    />
+                  </el-form-item>
+                </el-col>
+                <!-- END -->
+              </el-row>
+            </div>
+            <!-- END -->
+          </el-form>
+          <!-- 自定义表单 -->
+          <el-card
+            class="mb20 pd10"
+            shadow="hover"
+            v-for="(v, i) in state.dynamicForm"
+            :key="i"
+          >
+            <el-form
+              :ref="'dynamicFormRef' + i"
+              :model="v.formItemList"
+              label-width="100px"
+              size="small"
+              label-position="top"
+              closeable
+            >
+              <div class="mb15">
+                <el-text type="primary">
+                  <el-icon><InfoFilled /></el-icon>
+                  {{ v.formTitle }}
+                </el-text>
+              </div>
+              <el-row :gutter="20">
+                <el-col
+                  :span="element.tagIcon === 'textarea' ? 24 : 12"
+                  v-for="element in v.formItemList"
+                  :key="element.Field"
+                  class="mb20"
+                >
+                  <el-form-item
+                    :label="element.label"
+                    :class="element.required ? 'is-required' : ''"
+                    :prop="element.defaultValue"
+                  >
+                    <!-- 单行文本 -->
+                    <el-input
+                      style="width: 100%"
+                      v-model="element.defaultValue"
+                      v-if="element.tagIcon == 'input'"
+                      type="input"
+                      placeholder=""
+                      clearable
+                    ></el-input>
+
+                    <!-- 多行文本 -->
+                    <el-input
+                      style="width: 100%"
+                      v-model="element.defaultValue"
+                      v-else-if="element.tagIcon == 'textarea'"
+                      type="textarea"
+                      :rows="3"
+                      placeholder=""
+                      clearable
+                    ></el-input>
+
+                    <!-- 下拉菜单 单选多选 -->
+                    <el-select
+                      clearable
+                      v-model="element.defaultValue"
+                      :multiple="element.multiple"
+                      v-if="
+                        element.tagIcon == 'select' ||
+                        element.tagIcon == 'multiple-select'
+                      "
+                      style="width: 100%"
+                    >
+                      <el-option
+                        v-for="item in element.options"
+                        :key="item.value"
+                        :label="item.label"
+                        :value="item.value"
+                      />
+                    </el-select>
+
+                    <!-- 日期 -->
+                    <el-date-picker
+                      clearable
+                      style="width: 100%"
+                      v-model="element.defaultValue"
+                      v-else-if="element.tagIcon == 'date'"
+                      type="date"
+                      placeholder="选择日期"
+                      format="YYYY-MM-DD"
+                      value-format="YYYY-MM-DD"
+                    ></el-date-picker>
+
+                    <!-- 时间 -->
+                    <el-time-picker
+                      clearable
+                      style="width: 100%"
+                      v-model="element.defaultValue"
+                      v-else-if="element.tagIcon == 'time'"
+                      placeholder="请选择时间"
+                      format="HH:mm:ss"
+                      value-format="HH:mm:ss"
+                    ></el-time-picker>
+
+                    <!-- 整数 -->
+                    <el-input-number
+                      clearable
+                      style="width: 100%"
+                      v-model="element.defaultValue"
+                      v-else-if="element.tagIcon == 'number'"
+                      placeholder="请输入整数默认值"
+                      :precision="0"
+                    />
+
+                    <!-- 浮点型小数 -->
+                    <el-input-number
+                      clearable
+                      style="width: 100%"
+                      v-model="element.defaultValue"
+                      v-else-if="element.tagIcon == 'float'"
+                      placeholder="请输入两位小数默认值"
+                      :precision="2"
+                      :step="0.1"
+                    />
+                  </el-form-item>
+                </el-col>
+              </el-row>
+            </el-form>
+          </el-card>
+        </div>
+      </div>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="onCancel" size="small">取 消</el-button>
+          <el-button
+            type="primary"
+            @click="subAdd"
+            size="small"
+            :disabled="state.loading"
+          >
+            提 交
+          </el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts">
+  import to from "await-to-js";
+  import {
+    getMySelfProjectGroup,
+    getTechList,
+    getFinanceAccountList,
+    getUserList,
+    getInstrAppointInfo,
+    getSettingDetail,
+  } from "@/api/instr/index";
+  export default {
+    name: "FrontendWebTest",
+    data() {
+      return {
+        userInfos: {},
+        title: "预约信息",
+        visible: false,
+        InstCfgCharge: {},
+        activateService: false,
+        form: {
+          assistEnable: false,
+          startTime: "",
+          endTime: "",
+          instId: 0,
+          instName: "",
+          expenseCardId: null,
+          expenseCardName: "",
+          projectId: null,
+          projectName: "",
+          serviceId: null, //技术服务id
+          serviceName: "", //技术服务
+          userContact: "",
+          userName: "",
+          userId: null,
+          projectType: "project",
+          appointJump: false,
+          appointJumpLink: "",
+          appointJumpType: "",
+          instCode: "",
+          assetNumber: "",
+          instHeadId: "",
+          createForm: "",
+          remark: "",
+        },
+        state: {
+          isShowDialog: false,
+          loading: false,
+          dynamicForm: [],
+        },
+        userList: [],
+        editType: "",
+        projectOptions: [],
+        serviceOptions: [],
+        fundsList: [],
+        rules: {
+          startTime: [{ required: true, message: "不能为空", trigger: "blur" }],
+          endTime: [{ required: true, message: "不能为空", trigger: "blur" }],
+          projectType: [
+            { required: true, message: "请选择选课组/服务", trigger: "blur" },
+          ],
+          userContact: [
+            { required: true, message: "不能为空", trigger: "blur" },
+            {
+              pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
+              message: "请输入正确的手机号码",
+              trigger: "blur",
+            },
+          ],
+        },
+      };
+    },
+    methods: {
+      // 获取课题组和服务信息
+      async getProData() {
+        const [err, res] = await to(getMySelfProjectGroup({}));
+        if (err) return;
+        this.projectOptions = [
+          {
+            projectName: res.data.pgName || "",
+            projectId: res.data.id || null,
+          },
+        ];
+        if (res.data.id) {
+          changeProject(res.data.id);
+        }
+        const [serErr, serRes] = await to(getTechList({ noPage: true }));
+        if (serErr) return;
+        this.serviceOptions = serRes.data.list;
+      },
+      // 选择课题组
+      async changeProject(id) {
+        console.log(id);
+        if (id) {
+          this.form.projectId = id;
+          this.form.projectName = this.projectOptions.find(
+            (item) => item.projectId === id
+          ).projectName;
+          getFundsData();
+        } else {
+          this.form.expenseCardId = null;
+          this.fundsList = [];
+        }
+      },
+      // 根据课题组获取所有的经费卡
+      async getFundsData() {
+        const [err, res] = await to(
+          getFinanceAccountList({ projId: this.form.projectId })
+        );
+        if (err) return;
+        this.fundsList = res.data.list;
+        if (
+          this.fundsList.length > 0 &&
+          !this.form.expenseCardId &&
+          this.editType == "create"
+        ) {
+          this.form.expenseCardId = this.fundsList[0].id;
+        }
+      },
+      // 切换课题和服务的时候 清空课题和服务和经费卡
+      changeProjectType() {
+        this.clearProject();
+      },
+      // 清空课题和服务和经费卡
+      clearProject() {
+        this.form.projectId = null;
+        this.form.projectName = "";
+        this.form.serviceId = null;
+        this.form.serviceName = "";
+        this.form.expenseCardId = null;
+        this.fundsList = [];
+      },
+      // 选择技术服务
+      changeService(id) {
+        if (!id) return;
+        this.form.serviceId = id;
+        this.form.serviceName = this.serviceOptions.find(
+          (item) => item.id === id
+        ).name;
+      },
+      // 切换预约人的时候、清空课题和服务和经费卡
+      async changeUser(userId) {
+        const findUser = this.userList.find((item) => item.id === userId);
+        this.form.userContact = findUser.phone || "";
+        this.form.userName = findUser.nickName || "";
+        this.clearProject();
+        this.projectOptions = [];
+        this.fundsList = [];
+        const [err, res] = await to(getMySelfProjectGroup({ id: userId }));
+        if (err) return;
+        this.projectOptions = [
+          {
+            projectName: res.data.pgName || "",
+            projectId: res.data.id || null,
+          },
+        ];
+      },
+      async openDialog(row) {
+        const sysParamsConfig = JSON.parse(
+          localStorage.getItem("base:themeConfig") || "{}"
+        );
+        this.activateService =
+          sysParamsConfig.instr_is_activate_service == "10" ? true : false;
+        await this.getProData();
+        this.editType = row.id ? "edit" : "create";
+        try {
+          const [err, res] = await to(getUserList({ noPage: true }));
+          if (err) return;
+          this.userList = res.data.list;
+        } catch (error) {
+          console.log(error);
+        }
+        // 编辑的时候信息查详情
+        if (this.editType == "edit") {
+          const [infoErr, infoRes] = await to(
+            getInstrAppointInfo({ id: row.id })
+          );
+          if (infoErr) return;
+          const detailsRes = infoRes.data;
+          const dynamicForm = detailsRes.createForm
+            ? JSON.parse(detailsRes.createForm)
+            : [];
+          this.form = Object.assign(this.form, { ...detailsRes });
+          this.form.userContact = detailsRes.userContact || "";
+          this.form.userName = detailsRes.userName || "";
+          this.form.userId = detailsRes.userId || null;
+          this.form.expenseCardId = detailsRes.expenseCardId || null;
+          console.log(this.form.expenseCardId);
+
+          this.state.dynamicForm = dynamicForm;
+        } else {
+          const dynamicForm = row.createForm ? JSON.parse(row.createForm) : [];
+          this.form = Object.assign(this.form, { ...row });
+          this.form.userContact = this.userInfos.phone || "";
+          this.form.userName = this.userInfos.nickName || "";
+          this.form.userId = this.userInfos.id || null;
+          this.form.expenseCardId = row.expenseCardId || null;
+          this.state.dynamicForm = dynamicForm;
+        }
+        if (this.form.projectId) {
+          this.form.projectType = "project";
+          this.getFundsData();
+        } else if (this.form.serviceId) {
+          this.form.projectType = "service";
+        }
+        const params = {
+          instId: row.instId,
+          code: "InstCfgCharge",
+        };
+        const [err, res] = await to(getSettingDetail({ ...params }));
+        if (err) return;
+        this.InstCfgCharge = res.data.config.enable;
+        this.state.isShowDialog = true;
+      },
+      onCancel() {
+        this.editFormRef.clearValidate();
+        this.editFormRef.resetFields();
+        this.state.isShowDialog = false;
+      },
+      subAdd() {},
+    },
+  };
+</script>
+
+<style lang="scss" scoped>
+  :deep(.el-dialog__body) {
+    padding-top: 10px;
+  }
+</style>

+ 521 - 0
src/views/equipment/components/appoint-record.vue

@@ -0,0 +1,521 @@
+<template>
+  <div class="container">
+    <FullCalendar
+      ref="fullCalendar"
+      class="calendar-container-calendar"
+      :options="calendarOptions"
+    >
+      <template v-slot:eventContent="arg">
+        <!-- 弹窗信息 -->
+        <el-popover
+          trigger="click"
+          popper-class="fullcalendar-popover"
+          placement="right"
+          :width="268"
+          popper-style="padding: 0;border:none;background:none"
+        >
+          <!-- 预约信息 -->
+          <template #reference>
+            <div
+              class="st1 cursor-pointer"
+              :class="'status' + arg.event.extendedProps.status"
+            >
+              <span class="mr8">{{ arg.event.extendedProps.projectName }}</span>
+              <span>
+                {{ parseTime(arg.event.start, "HH:mm") }} -
+                {{ parseTime(arg.event.end, "HH:mm") }}
+              </span>
+            </div>
+          </template>
+          <!-- 预约详细信息 -->
+          <template #default>
+            <div class="popover-def-header">
+              <div class="name mt7">
+                <span class="mr2">{{ arg.event.extendedProps.instName }}</span>
+                预约信息
+              </div>
+              <div class="date mt8 pb25">
+                <span class="mr2">{{ changeTime(arg.event.start) }}</span>
+                <span class="mr2">({{ getWeek(arg.event.start) }})</span>
+                <span>
+                  {{ parseTime(arg.event.start, "HH:mm") }} ~
+                  {{ parseTime(arg.event.end, "HH:mm") }}
+                </span>
+              </div>
+            </div>
+            <div class="popover-def-content">
+              <div class="content-felx mb12">
+                <p class="mx-1" tag="b" size="small">
+                  地点: {{ arg.event.extendedProps.placeAddress }}
+                </p>
+              </div>
+              <div class="content-felx mb12">
+                <p class="mx-1" size="small">
+                  课题组:{{ arg.event.extendedProps.projectName }}
+                </p>
+              </div>
+              <div class="content-felx mb12">
+                <p class="mx-1" size="small">
+                  预约人: {{ arg.event.extendedProps.userName }}
+                </p>
+              </div>
+            </div>
+          </template>
+        </el-popover>
+      </template>
+    </FullCalendar>
+  </div>
+</template>
+
+<script>
+  import to from "await-to-js";
+  import FullCalendar from "@fullcalendar/vue";
+  import dayGridPlugin from "@fullcalendar/daygrid";
+  import timeGridPlugin from "@fullcalendar/timegrid";
+  import interactionPlugin from "@fullcalendar/interaction";
+  import { getAppointListByInst } from "@/api/instr/index";
+  import moment from "moment";
+  export default {
+    name: "equipment",
+    components: {
+      FullCalendar,
+    },
+    data() {
+      return {
+        recordData: [],
+        searchForm: {
+          instId: 0,
+          startTimeEnd: "",
+          startTimeStart: "",
+        },
+        calendarOptions: {
+          plugins: [
+            dayGridPlugin,
+            timeGridPlugin,
+            interactionPlugin, // needed for dateClick
+          ],
+          height: 600,
+          headerToolbar: {
+            left: null,
+            center: "prev,title,next,today",
+            right: null,
+          },
+          customButtons: {
+            prev: {
+              click: () => {
+                this.prevWeekClick();
+              },
+            },
+            next: {
+              click: () => {
+                this.nextWeekClick();
+              },
+            },
+            today: {
+              text: "本周",
+              click: () => {
+                this.todayWeekClick();
+              },
+            },
+          },
+          locale: "zh-cn", // 切换语言,当前为中文
+          // headerToolbar: false,
+          headerToolbar: {
+            left: null,
+            center: "prev,title,next,today",
+            right: null,
+          },
+          events: [], //事件事件+文本
+          initialView: "timeGridWeek", // 默认为那个视图(月:dayGridMonth,周:timeGridWeek,日:timeGridDay)
+          selectMirror: true,
+          dayMaxEvents: true,
+          firstDay: 1, //new Date().getDay(), // 设置一周中显示的第一天是哪天,周日是0,周一是1,类推  new Date().getDay()当前天
+          slotLabelFormat: {
+            hour: "2-digit",
+            minute: "2-digit",
+            meridiem: false,
+            hour12: false, // 设置时间为24小时
+          },
+          slotMinTime: "00:00:00",
+          slotMaxTime: "24:00:00",
+          allDaySlot: false, //是否显示日历上方的allDay
+          moreLinkClassNames: "more-btns",
+        },
+        currentEvents: [],
+        timeSplit: 10,
+        recordData: [],
+        curTime: "",
+      };
+    },
+    created() {},
+    mounted() {
+      this.curTime = this.getToday();
+      this.$nextTick(async () => {
+        this.searchForm.instId = this.$route.query.id * 1;
+        this.searchForm.startTimeStart = this.getWeekDate()[0];
+        this.searchForm.startTimeEnd = this.getWeekDate()[1];
+        await this.getAppointData();
+        let calendarFunc = this.$refs["fullCalendar"].getApi().view.calendar;
+        calendarFunc.setOption(
+          "slotDuration",
+          this.setSplitTime(this.timeSplit)
+        );
+        this.initData();
+      });
+    },
+
+    methods: {
+      getToday() {
+        return moment(moment().startOf("day").valueOf()).format("YYYY-MM-DD");
+      },
+      getWeekDate() {
+        // 获取当前周的周一0点
+        var startOfWeek = moment(this.curTime)
+          .startOf("isoWeek")
+          .format("YYYY-MM-DD HH:mm:ss");
+
+        // 获取下一周的周一0点
+        var startOfNextWeek = moment(this.curTime)
+          .startOf("isoWeek")
+          .add(1, "weeks")
+          .format("YYYY-MM-DD HH:mm:ss");
+        return [startOfWeek, startOfNextWeek];
+      },
+      async getAppointData() {
+        const [err, res] = await to(getAppointListByInst(this.searchForm));
+        if (err) return;
+        if (res.code == 200) {
+          this.recordData = res.data.list;
+        }
+      },
+      // 上周点击
+      async prevWeekClick() {
+        let calendarApi = this.$refs["fullCalendar"].getApi();
+        calendarApi.prev();
+        this.curTime = moment(calendarApi.getCurrentData().currentDate).format(
+          "YYYY-MM-DD"
+        );
+        this.searchForm.startTimeStart = this.curTime + " 00:00:00";
+        this.searchForm.startTimeEnd = this.getNextWeekDays() + " 00:00:00";
+        await this.getAppointData();
+        this.initData();
+      },
+      // 下周点击
+      async nextWeekClick() {
+        let calendarApi = this.$refs["fullCalendar"].getApi();
+        calendarApi.next();
+        this.curTime = moment(calendarApi.getCurrentData().currentDate).format(
+          "YYYY-MM-DD"
+        );
+        this.searchForm.startTimeStart = this.curTime + " 00:00:00";
+        this.searchForm.startTimeEnd = this.getNextWeekDays() + " 00:00:00";
+        await this.getAppointData();
+        this.initData();
+      },
+      // 今天点击
+      async todayWeekClick() {
+        let calendarApi = this.$refs["fullCalendar"].getApi();
+        calendarApi.today();
+        const date = moment(calendarApi.getCurrentData().currentDate).format(
+          "YYYY-MM-DD"
+        );
+        const currentMonday = moment(date).startOf("isoWeek");
+        const nextMonday = currentMonday.clone().add(1, "week");
+        this.searchForm.startTimeStart =
+          currentMonday.format("YYYY-MM-DD") + " 00:00:00";
+        this.searchForm.startTimeEnd =
+          nextMonday.format("YYYY-MM-DD") + " 00:00:00";
+        await this.getAppointData();
+        this.initData();
+      },
+      initData() {
+        const filterArr = ["10", "20", "50", "60"];
+        let appointData = [];
+        let status = 1;
+        this.recordData
+          .filter((item) => filterArr.includes(item.appointStatus))
+          .map((item) => {
+            appointData.push({
+              ...item,
+              status,
+              start: item.startTime,
+              end: item.endTime,
+            });
+            status++;
+            if (status > 3) {
+              status = 1;
+            }
+          });
+        let calendarApi = this.$refs["fullCalendar"].getApi();
+        let calendarFunc = calendarApi.view.calendar;
+        calendarFunc.unselect();
+        let getEvents = calendarFunc.getEvents();
+        if (getEvents && getEvents.length > 0) {
+          //如果日历看板之前有数据,那么删除之前的数据
+          getEvents.map((item) => {
+            calendarFunc.getEventById(item.id).remove();
+          });
+        }
+        appointData.map((item) => {
+          calendarFunc.addEvent(item); //数据填充到日历看板中
+        });
+        // calendarApi.gotoDate(new Date(this.curTime));
+      },
+      // 设置一格时间槽代表多长时间
+      setSplitTime(time) {
+        let split_time = "";
+        if (time < 60) {
+          split_time = "00:" + (time < 10 ? "0" + time : time) + ":00";
+        } else if (time == 60) {
+          split_time = "01:00:00";
+        }
+        return split_time;
+      },
+      parseTime(date, format) {
+        return moment(date).format(format);
+      },
+      setDiffTime(row) {
+        // 计算时间差
+        const startTime = moment(row.start);
+        const endTime = moment(row.end);
+        return (
+          moment.duration(endTime.diff(startTime)).asHours().toFixed(1) + "h"
+        );
+      },
+      changeTime(date) {
+        const time = moment(date).format("MM:DD").split(":");
+        return `${time[0]}月${time[1]}日`;
+      },
+      getWeek(date) {
+        // 参数时间戳
+        let week = moment(date).day();
+        switch (week) {
+          case 1:
+            return "周一";
+          case 2:
+            return "周二";
+          case 3:
+            return "周三";
+          case 4:
+            return "周四";
+          case 5:
+            return "周五";
+          case 6:
+            return "周六";
+          case 0:
+            return "周日";
+        }
+      },
+      // // 获取上一周
+      getPrevWeekDays() {
+        return moment(this.curTime).subtract(1, "week").format("YYYY-MM-DD");
+      },
+
+      // // 获取下一周
+      getNextWeekDays() {
+        return moment(this.curTime).add(1, "week").format("YYYY-MM-DD");
+      },
+    },
+  };
+</script>
+
+<style lang="scss" scoped>
+  :deep(.fc-day-disabled) {
+    opacity: 0.5;
+    pointer-events: none;
+  }
+  :deep(.fc-cell-not-allowed) {
+    background-color: #eee;
+  }
+  :deep(.fc-toolbar-chunk) {
+    div {
+      display: flex;
+    }
+  }
+  :deep(.fc-toolbar-title) {
+    font-size: 1.75em;
+    margin: 0;
+    width: 320px;
+    text-align: center;
+  }
+  :deep(.borderBlue) {
+    border-left: 5px solid blue !important;
+    border-radius: 0;
+  }
+  :deep(.borderOrange) {
+    border-left: 5px solid yellow !important;
+    border-radius: 0;
+  }
+  :deep(.fc) {
+    .fc-day-today {
+      background: unset;
+    }
+  }
+  :deep(.fc-past-event) {
+    background-color: #e3e3e3;
+  }
+  :deep(.fc-today-button) {
+    margin-left: 10px;
+  }
+  :deep(.fc .fc-toolbar.fc-header-toolbar) {
+    margin-bottom: 20px;
+  }
+  :deep(.fc .fc-timegrid-slot) {
+    height: 2em;
+  }
+
+  :deep(.fc-col-header-cell-cushion) {
+    font-size: 15px !important;
+    color: #333333 !important;
+    font-weight: bold !important;
+  }
+  :deep(.fc-timeGridDay-view) {
+    .fc-day-today {
+      background: none;
+    }
+    .st1 {
+      border-top: none;
+      display: block;
+    }
+    .fc-timegrid-col-events {
+      margin: 0;
+    }
+    .fc-v-event {
+      background: none;
+      width: 50%;
+      border: none;
+    }
+  }
+  :deep(.fc-timeGridWeek-view) {
+    .fc-day-today {
+      background: none;
+    }
+    .st1 {
+      border-left: none;
+      display: block;
+    }
+    .fc-timegrid-col-events {
+      margin: 0;
+    }
+    .fc-v-event {
+      background: none;
+      width: 100%;
+      border: none;
+    }
+  }
+  :deep(.fc-dayGridMonth-view) {
+    .fc-day {
+      height: 85px;
+    }
+    .st1 {
+      border-top: none;
+    }
+  }
+  :deep(.fc-popover-body) {
+    .st1 {
+      border-top: none;
+    }
+  }
+  .st1 {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    height: 100%;
+    border-left: 2px solid;
+    border-top: 2px solid;
+    padding-left: 7px;
+    margin-bottom: 2px;
+  }
+  // :deep(.fc .fc-scroller-liquid-absolute) {
+  // 	overflow: hidden !important;
+  // }
+  // :deep(.fc .fc-scroller) {
+  // 	overflow: hidden !important;
+  // }
+  :deep(.fc .fc-daygrid-more-link) {
+    color: #b5c1d8 !important;
+  }
+  :deep(.fc-h-event) {
+    border: none;
+  }
+  :deep(.fc-daygrid-day-number) {
+    color: #585858;
+  }
+  :deep(.fc .fc-timegrid-slot-label) {
+    color: #585858;
+  }
+  :deep(.fc .fc-daygrid-day-top) {
+    flex-direction: row;
+  }
+  :deep(.fc .fc-daygrid-day.fc-day-today) {
+    background: none;
+    .fc-daygrid-day-number {
+      background: #2c78ff;
+      border-radius: 12px;
+      color: #ffffff;
+    }
+  }
+  :deep(.fc-daygrid-day-number) {
+    margin: 3px 0 0 6px;
+  }
+  :deep(.fc-col-header-cell) {
+    .fc-scrollgrid-sync-inner {
+      height: 50px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      .fc-col-header-cell-cushion {
+        font-size: 13px;
+        color: #585858;
+        font-weight: 100;
+      }
+    }
+  }
+
+  .status1 {
+    background: #fff6e2;
+    border-color: #fdc33e;
+    color: #bf743c;
+  }
+  .status2 {
+    background: #f0faf2;
+    border-color: #0dda83;
+    color: #006e3f;
+  }
+  .status3 {
+    background: #eef3fe;
+    border-color: #2c78ff;
+    color: #2c78ff;
+  }
+  .fullcalendar-popover {
+    .popover-def-header {
+      // width: 268px;
+      padding: 8px 12px 0 14px;
+      background: linear-gradient(241deg, #2c78ff 0%, #1c9bfd 100%);
+      font-size: 12px;
+      color: #ffffff;
+      .header {
+        display: flex;
+        justify-content: end;
+      }
+    }
+    .popover-def-content {
+      padding: 12px 22px;
+      background: #fff;
+      .content-felx {
+        display: flex;
+        .icon-l {
+          width: 16px;
+          height: 16px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          margin-right: 6px;
+        }
+      }
+    }
+    :deep(.el-popper__arrow) {
+      opacity: 0;
+    }
+  }
+</style>

+ 866 - 0
src/views/equipment/components/appoint.vue

@@ -0,0 +1,866 @@
+<template>
+  <div v-if="visible">
+    <el-dialog
+      :title="title"
+      :visible.sync="visible"
+      :close-on-click-modal="false"
+      @close="dialogClose"
+      width="1200px"
+    >
+      <div>
+        <FullCalendar ref="fullCalendar" :options="calendarOptions">
+          <template v-slot:eventContent="arg">
+            <div
+              style="height: 100%; width: 100%"
+              :title="showTitlt(arg.event)"
+            >
+              <div
+                v-if="
+                  arg.event.extendedProps.appointStatus &&
+                  arg.event.extendedProps.appointStatus != 999
+                "
+                style="color: #ffffff; margin: 0px"
+              >
+                <div class="appoint-details">
+                  {{ arg.event.extendedProps.userName
+                  }}{{ "--" + arg.event.extendedProps.tel }}
+                </div>
+                <div class="appoint-details" v-if="showAllInfo(arg.event, 2)">
+                  {{
+                    parseTime(arg.event.start, "{m}月{d}日") ==
+                    parseTime(arg.event.end, "{m}月{d}日")
+                      ? parseTime(arg.event.start, "{h}:{i}")
+                      : parseTime(arg.event.start, "{m}月{d}日 {h}:{i}")
+                  }}
+                  -
+                  {{
+                    parseTime(arg.event.end, "{m}月{d}日") ==
+                    parseTime(arg.event.start, "{m}月{d}日")
+                      ? parseTime(arg.event.end, "{h}:{i}")
+                      : parseTime(arg.event.end, "{m}月{d}日 {h}:{i}")
+                  }}
+                </div>
+                <div class="appoint-details" v-if="showAllInfo(arg.event, 3)">
+                  {{
+                    arg.event.extendedProps.projectName ||
+                    arg.event.extendedProps.serviceName
+                  }}
+                </div>
+              </div>
+              <div
+                v-else-if="
+                  arg.event.extendedProps.appointStatus &&
+                  arg.event.extendedProps.appointStatus == 999
+                "
+                style="color: #61616e; margin: 0px"
+              >
+                不允许预约
+              </div>
+              <div v-else style="color: #ffffff; margin: 0px">
+                {{
+                  parseTime(arg.event.start, "{m}月{d}日") ==
+                  parseTime(arg.event.end, "{m}月{d}日")
+                    ? parseTime(arg.event.start, "{h}:{i}")
+                    : parseTime(arg.event.start, "{m}月{d}日 {h}:{i}")
+                }}
+                -
+                {{
+                  parseTime(arg.event.end, "{m}月{d}日") ==
+                  parseTime(arg.event.start, "{m}月{d}日")
+                    ? parseTime(arg.event.end, "{h}:{i}")
+                    : parseTime(arg.event.end, "{m}月{d}日 {h}:{i}")
+                }}
+              </div>
+            </div>
+          </template>
+        </FullCalendar>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogClose()" size="small ">取 消</el-button>
+          <el-button
+            :disabled="!form.startTime"
+            type="primary"
+            size="small "
+            @click="nextStep"
+          >
+            确认
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+    <appointCreate ref="appointCreateRef"></appointCreate>
+    <!-- <create-sub ref="sub" @refresh="emitData"></create-sub> -->
+  </div>
+</template>
+
+<script lang="ts">
+  // import CreateSub from "/@/views/instr/component/appoint/createSub.vue";
+  import FullCalendar from "@fullcalendar/vue";
+  import dayGridPlugin from "@fullcalendar/daygrid";
+  import interactionPlugin from "@fullcalendar/interaction";
+  import timeGridPlugin from "@fullcalendar/timegrid";
+  import to from "await-to-js";
+  import _ from "lodash";
+  import moment from "moment";
+  import appointCreate from "./appoint-create.vue";
+  import {
+    getSettingDetail,
+    getAppointInfo,
+    checkInBlacklist,
+  } from "@/api/instr/index";
+  export default {
+    name: "FrontendWebTest",
+    components: {
+      FullCalendar,
+      appointCreate,
+    },
+    data() {
+      return {
+        title: "",
+        visible: false,
+        form: {
+          startTime: "",
+          endTime: "",
+          instId: 0,
+          instName: "",
+          createForm: "",
+          appointJump: false,
+          appointJumpLink: "",
+          appointJumpType: "",
+          assetNumber: "",
+          instHeadId: "",
+        },
+        show: false,
+        split_time: 0,
+        calendarOptions: {
+          plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin],
+          initialView: "timeGridWeek", // 默认为那个视图(月:dayGridMonth,周:timeGridWeek,日:timeGridDay)
+          // validRange: this.setValidRange, //日期有效范围
+          allDaySlot: false, //是否显示日历上方的allDay
+          dayMaxEvents: true, // allow "more" link when too many events,只能选中或拖动一次
+          firstDay: 1, //new Date().getDay(), // 设置一周中显示的第一天是哪天,周日是0,周一是1,类推  new Date().getDay()当前天
+          locale: "zh-cn", // 切换语言,当前为中文
+          unselectAuto: false, //当点击页⾯⽇历以外的位置时,是否⾃动取消当前的选中状态。false是不取消
+          customButtons: {
+            prev: {
+              click: () => {
+                this.prevWeekClick();
+              },
+            },
+            next: {
+              click: () => {
+                this.nextWeekClick();
+              },
+            },
+            today: {
+              text: "本周",
+              click: () => {
+                this.todayWeekClick();
+              },
+            },
+          },
+          height: 600,
+          dragScroll: true,
+          headerToolbar: {
+            left: null,
+            center: "prev,title,next,today",
+            right: null,
+          },
+          events: [], //事件事件+文本
+          slotLabelFormat: {
+            hour: "2-digit",
+            minute: "2-digit",
+            meridiem: false,
+            hour12: false, // 设置时间为24小时
+          },
+          editable: false,
+          selectable: true,
+          eventConstraint: {
+            // 设置可以拖放的时间段为当下到100年后
+            start: new Date(), // 只允许选择当前时间之后的时间段
+          },
+          selectConstraint: {
+            start: new Date(),
+            end: "",
+          },
+          selectOverlap: false, //选择重叠
+          businessHours: [],
+          selectMirror: true,
+          weekends: true,
+          select: this.handleDateSelect, //当用户拖拽日期或时间时传递的参数
+        },
+        furtherLimit: "",
+      };
+    },
+    methods: {
+      emitData() {
+        this.dialogClose();
+      },
+      // 打开设备预约
+      async openDialog(row) {
+        const [err, res] = await to(checkInBlacklist());
+        if (err) return;
+        if (res.data) {
+          return this.$message.warning(
+            "您已被拉入黑名单,无法预约,请联系管理人员"
+          );
+        }
+        if (row.appointJump && row.appointJumpType == "10") {
+          return window.open(row.appointJumpLink, "_blank");
+        }
+        this.$nextTick(async () => {
+          this.form.instId = row.id;
+          this.form.instName = row.instName;
+          this.form.appointJump = row.appointJump;
+          this.form.appointJumpLink = row.appointJumpLink;
+          this.form.appointJumpType = row.appointJumpType;
+          this.form.assetNumber = row.assetNumber;
+          this.form.instHeadId = row.instHeadId;
+          this.title = "正在发起对" + row.instName + "的预约申请";
+          this.visible = true;
+          const params = {
+            instId: row.id,
+            code: "InstCfgAppoint",
+          };
+          const [err, res] = await to(getSettingDetail({ ...params }));
+          if (err) return;
+          if (res.code == 200) {
+            this.show = true;
+            this.split_time = res.data.config.timeSplit;
+            this.form.createForm = res.data.config.createForm;
+            let calendarFunc =
+              this.$refs["fullCalendar"].getApi().view.calendar;
+            calendarFunc.setOption(
+              "slotDuration",
+              this.setSplitTime(this.split_time)
+            );
+            this.getAppointData();
+          }
+          // this.initData();
+          // await this.getAppointData();
+        });
+      },
+      // 设置一格时间槽代表多长时间
+      setSplitTime(time) {
+        let split_time = "";
+        if (time < 60) {
+          split_time = "00:" + (time < 10 ? "0" + time : time) + ":00";
+        } else if (time == 60) {
+          split_time = "01:00:00";
+        }
+        return split_time;
+      },
+      // 预约信息
+      getAppointData() {
+        this.$nextTick(async () => {
+          const calendar = await this.$refs.fullCalendar.getApi(); // 获取 FullCalendar 实例
+          let params = {
+            dateType: "week",
+            instId: this.form.instId,
+            date: moment(calendar.getCurrentData().currentDate).format(
+              "YYYY-MM-DD"
+            ),
+          };
+          const [err, res] = await to(getAppointInfo({ ...params }));
+          if (err) return;
+          if (res.code == 200) {
+            const { appoint, unavailable, furtherLimit } = res.data;
+            this.furtherLimit = furtherLimit
+              ? moment(furtherLimit).format("YYYY-MM-DD HH:mm")
+              : "";
+            let arr = [...(appoint || []), ...(unavailable || [])];
+            this.setBusinessHours(calendar);
+            this.initBoard(arr);
+            this.$nextTick(() => {
+              this.calendarOptions.selectConstraint = {
+                start: new Date(),
+                end: this.furtherLimit
+                  ? moment(this.furtherLimit).format("YYYY-MM-DD HH:mm")
+                  : "",
+              };
+            });
+          }
+        });
+      },
+      // 上周点击
+      prevWeekClick() {
+        let calendarApi = this.$refs.fullCalendar.getApi();
+        calendarApi.prev();
+        this.getAppointData();
+      },
+      // 下周点击
+      nextWeekClick() {
+        let calendarApi = this.$refs.fullCalendar.getApi();
+        calendarApi.next();
+        this.getAppointData();
+      },
+      // 今天点击
+      todayWeekClick() {
+        let calendarApi = this.$refs.fullCalendar.getApi();
+        calendarApi.today();
+        this.getAppointData();
+      },
+      /**
+       * 设置工作时间
+       * */
+      // 设置工作时间(可预约时间)
+      setBusinessHours(calendar) {
+        // 获取当前视图所显示的日期范围
+        const viewStart = calendar.view.activeStart;
+        const viewEnd = calendar.view.activeEnd;
+        // 获取今天的日期
+        const today = new Date();
+        // 判断今天是否在当前周里
+        if (today >= viewStart && today <= viewEnd) {
+          // 当前周
+          this.handleSetHours("currentWeek");
+        } else if (today < viewStart) {
+          // 未来的周
+          this.handleSetHours("featureWeek");
+        } else {
+          // 过去的周
+          this.handleSetHours("pastWeek");
+        }
+      },
+      // 设置不同周的工作时间
+      /**
+       * @param {*} weekType 周类型: 当前周 未来周 之前周
+       * @description 设置每周的可预约时间
+       * 优先预约权限的用户 过去周全天不可以预约、未来周全天可预约、当前周今天当前时间之前的时间不可预约
+       * 非优先预约权限的用户 过去周 全天不可以预约 、当前周(包括未来周)的过去时间不可以预约、现在及以后的时间根据提前预约时长设置限制是否可预约
+       * */
+      handleSetHours(weekType) {
+        let businessHours = [];
+        // 获取当前时间
+        const now = moment();
+        // 根据当前天输出三个数组
+        const currentDay = now.day(); //当前天 周四 [4]
+        const lodashRangeDay = currentDay === 0 ? 7 : now.day();
+        const pastDays = _.range(1, lodashRangeDay); //过去的时间 [1,2,3]
+        const futureDays = _.range(lodashRangeDay + 1, 8)
+          .concat()
+          .map((num) => (num === 7 ? 0 : num));
+        //未来的时间 [5,6,0]
+        const startTime = now.format("YYYY-MM-DD HH:mm");
+        const endTime = moment(this.furtherLimit).format("YYYY-MM-DD HH:mm"); //当前时间增加1小时
+        // 仪器可预约时间为从当前时间开始(startTime)往后推到(endTime)
+        const businessDateRange = [startTime, endTime];
+        // 预约日期的周日期
+        const Monday = moment(
+          this.$refs.fullCalendar.getApi().currentData.dateProfile.activeRange
+            .start
+        ).format("YYYY-MM-DD");
+        // 根据当前预约日历的周获取周一
+        const Tuesday = moment(Monday).add(1, "days").format("YYYY-MM-DD"); // 周二日期
+
+        const Wednesday = moment(Monday).add(2, "days").format("YYYY-MM-DD"); // 周三日期
+        const Thursday = moment(Monday).add(3, "days").format("YYYY-MM-DD"); // 周四日期
+        const Friday = moment(Monday).add(4, "days").format("YYYY-MM-DD"); // 周五日期
+        const Saturday = moment(Monday).add(5, "days").format("YYYY-MM-DD"); // 周六日期
+        const Sunday = moment(Monday).add(6, "days").format("YYYY-MM-DD"); // 周日日期
+        const weeks = [
+          Monday,
+          Tuesday,
+          Wednesday,
+          Thursday,
+          Friday,
+          Saturday,
+          Sunday,
+        ];
+        if (weekType == "currentWeek") {
+          if (this.furtherLimit) {
+            businessHours = this.calcBusinessList(weeks, businessDateRange);
+          } else {
+            businessHours = [
+              {
+                daysOfWeek: pastDays,
+                startTime: "00:00",
+                endTime: "00:00",
+              },
+              {
+                daysOfWeek: [currentDay],
+                startTime: this.getCurrentAppointDate().split(" ")[1],
+                endTime: "24:00",
+              },
+              {
+                daysOfWeek: futureDays,
+                startTime: "00:00",
+                endTime: "24:00",
+              },
+            ];
+            console.log("businessHours", businessHours);
+          }
+        } else if (weekType == "pastWeek") {
+          businessHours = [
+            {
+              daysOfWeek: [1, 2, 3, 4, 5, 6, 0],
+              startTime: "00:00",
+              endTime: "00:00",
+            },
+          ];
+        } else if (weekType == "featureWeek") {
+          if (this.furtherLimit) {
+            businessHours = this.calcBusinessList(weeks, businessDateRange);
+          } else {
+            businessHours = [
+              {
+                daysOfWeek: [1, 2, 3, 4, 5, 6, 0],
+                startTime: "00:00",
+                endTime: "24:00",
+              },
+            ];
+          }
+        }
+        let calendarApi = this.$refs["fullCalendar"].getApi();
+        let calendarFunc = calendarApi.view.calendar;
+        calendarFunc.setOption("businessHours", businessHours);
+      },
+      calcBusinessList(weeksRange, businessRange) {
+        const businessWorkList = [];
+        // 预约时间的开始日期是否在当前周
+        const businessStartInCurrentWeek = moment(
+          moment(businessRange[0]).format("YYYY-MM-DD")
+        ).isBetween(moment(weeksRange[0]), moment(weeksRange[6]), null, "[]");
+        // 预约时间的结束日期是否在当前周
+        const businessEndInCurrentWeek = moment(
+          moment(businessRange[1]).format("YYYY-MM-DD")
+        ).isBetween(moment(weeksRange[0]), moment(weeksRange[6]), null, "[]");
+        // 预约时间的结束日期和开始日期是否为同一天
+        const businesStartSameEnd =
+          moment(businessRange[0]).format("YYYY-MM-DD") ==
+          moment(businessRange[1]).format("YYYY-MM-DD");
+        // 可预约开始时间在当前周
+        if (businessStartInCurrentWeek) {
+          /**
+           * 如果可预约的开始时间在当前周
+           * 首先找出是在哪天
+           * 其次找出结束时间是否在当前周,如果不在开始时间之前全部置灰、之后的不置灰
+           */
+          // 可预约结束时间在当前周
+          if (businessEndInCurrentWeek) {
+            // 可预约结束时间和开始时间是同一天
+            if (businesStartSameEnd) {
+              let startIndex = 0;
+              weeksRange.map((item, index) => {
+                // 找出开始时间是当前周的哪一天
+                if (item == moment(businessRange[0]).format("YYYY-MM-DD")) {
+                  startIndex = index;
+                  businessWorkList.push(
+                    // 开始之前
+                    {
+                      daysOfWeek: this.generateNumberArray(1, index),
+                      startTime: "00:00",
+                      endTime: "00:00",
+                    },
+                    // 开始当天
+                    {
+                      daysOfWeek: [index + 1],
+                      startTime: this.getCurrentAppointDate().split(" ")[1],
+                      endTime: moment(businessRange[1]).format("HH:mm"),
+                    }
+                  );
+                }
+              });
+              if (startIndex != 6) {
+                businessWorkList.push({
+                  daysOfWeek: this.generateNumberArray(startIndex + 1, 7),
+                  startTime: "00:00",
+                  endTime: "00:00",
+                });
+              }
+              // END
+            } else {
+              // 可预约结束时间和开始时间不是同一天
+              let startIndex = 0;
+              let endIndex = 0;
+              weeksRange.map((item, index) => {
+                // 找出开始时间是当前周的哪一天
+                if (item == moment(businessRange[0]).format("YYYY-MM-DD")) {
+                  startIndex = index;
+                  businessWorkList.push(
+                    // 开始之前
+                    {
+                      daysOfWeek: this.generateNumberArray(1, index),
+                      startTime: "00:00",
+                      endTime: "00:00",
+                    },
+                    // 开始当天
+                    {
+                      daysOfWeek: [index + 1],
+                      startTime: this.getCurrentAppointDate().split(" ")[1],
+                      endTime: "24:00",
+                    }
+                  );
+                }
+              });
+              weeksRange.map((item, index) => {
+                // 找出开始时间是当前周的哪一天
+                if (item == moment(businessRange[1]).format("YYYY-MM-DD")) {
+                  endIndex = index;
+                  businessWorkList.push(
+                    {
+                      daysOfWeek: this.generateRangeArray(
+                        startIndex + 1,
+                        endIndex + 1
+                      ),
+                      startTime: "00:00",
+                      endTime: "24:00",
+                    },
+                    {
+                      daysOfWeek: [endIndex + 1],
+                      startTime: "00:00",
+                      endTime: moment(businessRange[1]).format("HH:mm"),
+                    }
+                  );
+                }
+              });
+              if (endIndex != 6) {
+                businessWorkList.push({
+                  daysOfWeek: this.generateNumberArray(endIndex + 2, 7),
+                  startTime: "00:00",
+                  endTime: "00:00",
+                });
+              }
+              // END
+            }
+            // END
+          } else if (!businessEndInCurrentWeek) {
+            // 可预约结束时间不在当前周
+            let startIndex = 0;
+            weeksRange.map((item, index) => {
+              // 找出开始时间是当前周的哪一天
+              if (item == moment(businessRange[0]).format("YYYY-MM-DD")) {
+                startIndex = index + 1;
+                businessWorkList.push(
+                  // 开始之前
+                  {
+                    daysOfWeek: this.generateNumberArray(1, index),
+                    startTime: "00:00",
+                    endTime: "00:00",
+                  },
+                  // 开始当天
+                  {
+                    daysOfWeek: [index + 1],
+                    startTime: this.getCurrentAppointDate().split(" ")[1],
+                    endTime: "24:00",
+                  }
+                );
+              }
+            });
+            if (startIndex != 6) {
+              businessWorkList.push({
+                daysOfWeek: this.generateNumberArray(startIndex + 1, 7),
+                startTime: "00:00",
+                endTime: "24:00",
+              });
+            }
+            // END
+          }
+        } else {
+          if (businessEndInCurrentWeek) {
+            // 结束时间在当前周
+            let endIndex = 0;
+            weeksRange.map((item, index) => {
+              // 找出开始时间是当前周的哪一天
+              if (item == moment(businessRange[1]).format("YYYY-MM-DD")) {
+                endIndex = index + 1;
+                businessWorkList.push(
+                  // 结束之前
+                  {
+                    daysOfWeek: this.generateNumberArray(1, index),
+                    startTime: "00:00",
+                    endTime: "24:00",
+                  },
+                  // 结束当天
+                  {
+                    daysOfWeek: [index + 1],
+                    startTime: "00:00",
+                    endTime: moment(businessRange[1]).format("HH:mm"),
+                  }
+                );
+              }
+            });
+            if (endIndex != 6) {
+              businessWorkList.push({
+                daysOfWeek: this.generateNumberArray(endIndex + 1, 7),
+                startTime: "00:00",
+                endTime: "00:00",
+              });
+            }
+            // END
+          } else {
+            return [
+              {
+                daysOfWeek: [1, 2, 3, 4, 5, 6, 0],
+                startTime: "00:00",
+                endTime: "00:00",
+              },
+            ];
+          }
+        }
+        const result = businessWorkList.filter(
+          (item) => item.daysOfWeek.length > 0
+        );
+        return result;
+      },
+      // 获取两个数字的区间数组
+      generateRangeArray(start, end) {
+        if (start > end) {
+          [start, end] = [end, start]; // 如果开始数字大于结束数字,交换它们的值
+        }
+
+        const result = [];
+        for (let i = start + 1; i < end; i++) {
+          result.push(i);
+        }
+        // 找到数字 7 的索引
+        const indexOfSeven = result.indexOf(7);
+        // 如果找到了数字 7 的索引,则替换为 0
+        if (indexOfSeven !== -1) {
+          result[indexOfSeven] = 0;
+        }
+        return result;
+      },
+      // 获取两个数字数组包括本身
+      generateNumberArray(start, end) {
+        const result = [];
+        for (let i = start; i <= end; i++) {
+          result.push(i);
+        }
+        // 找到数字 7 的索引
+        const indexOfSeven = result.indexOf(7);
+
+        // 如果找到了数字 7 的索引,则替换为 0
+        if (indexOfSeven !== -1) {
+          result[indexOfSeven] = 0;
+        }
+        return result;
+      },
+      // 获取最近可预约时间段
+      getCurrentAppointDate() {
+        const interval = this.split_time; // 时间间隔(单位:分钟)
+        const now = moment(); // 当前时间
+
+        // 将当前时间向上取整到最近的 interval 的倍数
+        const roundedNow = moment(
+          Math.ceil(now.valueOf() / (interval * 60 * 1000)) *
+            interval *
+            60 *
+            1000
+        );
+
+        // 如果当前时间已经超过了最近可预约时间,则将最近可预约时间增加一个 interval 的时间间隔
+        if (now.isAfter(roundedNow)) {
+          roundedNow.add(interval, "minutes");
+        }
+
+        // 将最近可预约时间格式化为 HH:mm:ss 格式
+        const nearestAvailableTime = roundedNow.format("YYYY-MM-DD HH:mm");
+        return nearestAvailableTime;
+      },
+      /**
+       * 设置工作时间相关代码 END
+       * */
+      // 更新预约内容视图
+      initBoard(datas) {
+        let data = datas;
+        if (!data) return;
+        let newD = data.map((item, index) => {
+          //根据返回的status判断当前是待审核  已预约的颜色
+          return {
+            id: index,
+            start: item.id ? item.startTime : item.start,
+            end: item.id ? item.endTime : item.end,
+            color:
+              item.appointStatus == "20"
+                ? "skyblue"
+                : !item.appointStatus
+                ? "#ccc"
+                : "",
+            projectName: item.projectName || "",
+            serviceName: item.serviceName || "",
+            tel: item.userContact || "",
+            appointStatus: item.appointStatus || 999,
+            userName: item.userName || "",
+          };
+        });
+        this.pushData(newD);
+      },
+      // 数据填充到日历
+      pushData(newD) {
+        let calendarApi = this.$refs["fullCalendar"].getApi();
+        let calendarFunc = calendarApi.view.calendar;
+        calendarFunc.unselect();
+        let getEvents = calendarFunc.getEvents();
+        if (getEvents && getEvents.length > 0) {
+          //如果日历看板之前有数据,那么删除之前的数据
+          getEvents.map((item) => {
+            calendarFunc.getEventById(item.id).remove();
+          });
+        }
+        newD.map((item) => {
+          calendarFunc.addEvent(item); //数据填充到日历看板中
+        });
+        // this.$nextTick(() => {
+        // 	document.querySelector('.fc-scroller-liquid-absolute').scrollTop = 0;
+        // });
+      },
+      showTitlt(event) {
+        let str = `预约时间:   ${
+          this.parseTime(event.start, "{m}月{d}日") ==
+          this.parseTime(event.end, "{m}月{d}日")
+            ? this.parseTime(event.start, "{h}:{i}")
+            : this.parseTime(event.start, "{m}月{d}日 {h}:{i}")
+        }-${
+          this.parseTime(event.end, "{m}月{d}日") ==
+          this.parseTime(event.start, "{m}月{d}日")
+            ? this.parseTime(event.end, "{h}:{i}")
+            : this.parseTime(event.end, "{m}月{d}日 {h}:{i}")
+        }`;
+        if (
+          event.extendedProps.appointStatus &&
+          event.extendedProps.appointStatus != 999
+        ) {
+          str += `\n课题组/服务:   ${
+            event.extendedProps.projectName || event.extendedProps.serviceName
+          }\n预约人:   ${event.extendedProps.userName}\n联系电话:   ${
+            event.extendedProps.tel
+          }`;
+        }
+        return str;
+      },
+      //判断是否显示全部内容
+      showAllInfo(ev, cnt) {
+        var start = new Date(ev.start);
+        var end = new Date(ev.end);
+        var minutesDiff = (end - start) / (1000 * 60); // 计算分钟差
+        var intervals = Math.floor(minutesDiff / this.split_time); // 计算间隔数量
+        if (cnt <= intervals) {
+          return true;
+        } else {
+          return false;
+        }
+      },
+      // 日期格式化
+      parseTime(time, pattern) {
+        if (arguments.length === 0 || !time) {
+          return null;
+        }
+        const format = pattern || "{y}-{m}-{d} {h}:{i}:{s}";
+        let date;
+        if (typeof time === "object") {
+          date = time;
+        } else {
+          if (typeof time === "string" && /^[0-9]+$/.test(time)) {
+            time = parseInt(time);
+          } else if (typeof time === "string") {
+            time = time.replace(new RegExp(/-/gm), "/");
+          }
+          if (typeof time === "number" && time.toString().length === 10) {
+            time = time * 1000;
+          }
+          date = new Date(time);
+        }
+        const formatObj = {
+          y: date.getFullYear(),
+          m: date.getMonth() + 1,
+          d: date.getDate(),
+          h: date.getHours(),
+          i: date.getMinutes(),
+          s: date.getSeconds(),
+          a: date.getDay(),
+        };
+        const time_str = format.replace(
+          /{(y|m|d|h|i|s|a)+}/g,
+          (result, key) => {
+            let value = formatObj[key];
+            // Note: getDay() returns 0 on Sunday
+            if (key === "a") {
+              return ["日", "一", "二", "三", "四", "五", "六"][value];
+            }
+            if (result.length > 0 && value < 10) {
+              value = "0" + value;
+            }
+            return value || 0;
+          }
+        );
+        return time_str;
+      },
+      // 当选择结束的时候获取开始和结束时间
+      handleDateSelect(info) {
+        this.form.startTime = moment(info.startStr).format(
+          "YYYY-MM-DD HH:mm:ss"
+        );
+        this.form.endTime = moment(info.endStr).format("YYYY-MM-DD HH:mm:ss");
+      },
+      // 关闭
+      dialogClose() {
+        this.visible = false;
+        this.form = { startTime: "", endTime: "", instId: 0, instName: "" };
+        this.show = false;
+      },
+      // 预约下一步
+      nextStep() {
+        this.$refs.appointCreateRef.openDialog(this.form);
+      },
+    },
+  };
+</script>
+
+<style lang="scss" scoped>
+  :deep(.fc-day-disabled) {
+    opacity: 0.5;
+    pointer-events: none;
+  }
+  :deep(.fc-cell-not-allowed) {
+    background-color: #eee;
+  }
+  :deep(.fc-toolbar-chunk) {
+    div {
+      display: flex;
+    }
+  }
+  :deep(.fc-toolbar-title) {
+    font-size: 1.75em;
+    margin: 0;
+    width: 320px;
+    text-align: center;
+  }
+  :deep(.borderBlue) {
+    border-left: 5px solid blue !important;
+    border-radius: 0;
+  }
+  :deep(.borderOrange) {
+    border-left: 5px solid yellow !important;
+    border-radius: 0;
+  }
+  :deep(.fc) {
+    .fc-day-today {
+      background: unset;
+    }
+  }
+  :deep(.fc-past-event) {
+    background-color: #e3e3e3;
+  }
+  :deep(.fc-today-button) {
+    margin-left: 10px;
+  }
+  :deep(.fc .fc-toolbar.fc-header-toolbar) {
+    margin-bottom: 20px;
+  }
+  :deep(.fc .fc-timegrid-slot) {
+    height: 2em;
+  }
+  :deep(.fc-event-main) {
+    overflow: hidden;
+  }
+  .appoint-details {
+    margin-left: 10px;
+    margin-bottom: 3px;
+    line-height: 2em;
+  }
+  :deep(.el-dialog__body) {
+    padding-top: 10px;
+  }
+</style>

+ 90 - 0
src/views/equipment/components/sample-charg.vue

@@ -0,0 +1,90 @@
+<template>
+  <div class="container">
+    <div class="list">
+      <ul>
+        <li v-for="(v, i) in tableData" :key="i">
+          <h4>
+            <div class="title">{{ v.name }}</div>
+          </h4>
+          <div class="price-item">
+            <span class="price">{{ v.price }}</span>
+            <span>元/样</span>
+          </div>
+        </li>
+      </ul>
+    </div>
+  </div>
+</template>
+
+<script>
+  import to from "await-to-js";
+  import { getSampleOption } from "@/api/instr/index";
+  export default {
+    name: "equipment",
+    components: {},
+    data() {
+      return {
+        tableData: [],
+        searchForm: {
+          id: 0,
+        },
+        total: 0,
+      };
+    },
+    created() {},
+
+    mounted() {
+      this.searchForm.id = this.$route.query.id * 1;
+      this.getNoticeListData();
+    },
+
+    methods: {
+      async getNoticeListData() {
+        const [err, res] = await to(getSampleOption(this.searchForm));
+        if (err) return;
+        if (res.code == 200) {
+          this.tableData = res.data;
+        }
+      },
+    },
+  };
+</script>
+
+<style lang="scss" scoped>
+  .container {
+    padding: 10px 0;
+    .list {
+      li {
+        background: #f3f6fb;
+        border-radius: 6px;
+        margin-bottom: 20px;
+        padding: 24px 36px 24px 18px;
+        display: flex;
+        line-height: 42px;
+        font-size: 14px;
+        align-content: center;
+        color: #666;
+        .title {
+          font-weight: 700;
+          width: 285px;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+          padding: 0 10px 0 18px;
+          position: relative;
+          color: #333;
+        }
+        .price {
+          color: #1677ff;
+        }
+      }
+    }
+  }
+  .pagination {
+    width: 100%;
+    height: 50px;
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+  }
+</style>

+ 318 - 271
src/views/equipment/details.vue

@@ -1,308 +1,355 @@
 <template>
-  <section class="section-container">
-    <div class="detail-intro">
-      <div class="img-wrap">
-        <img :src="instrDetails.instPicture" alt="" style="width: 100%;" />
-      </div>
-      <div class="detail-info">
-        <h3 class="instr-name">{{ instrDetails.instName }}</h3>
-        <div class="info-wrap">
-          <ul>
-            <li>
-              <div class="msg">
-                <span class="num">
-                  {{ instrDetails.focusPeopleNum }}
-                </span>
-                /人
-              </div>
-              <em>关注者</em>
-            </li>
-            <li>
-              <div class="msg">
-                <span class="num">
-                  {{ instrDetails.usePeopleNum }}
-                </span>
-                /人
-              </div>
-              <em>使用者</em>
-            </li>
-            <li>
-              <div class="msg">
-                <span class="num">
-                  {{ instrDetails.useTimesNum }}
-                </span>
-                /次
-              </div>
-              <em>总次数</em>
-            </li>
-            <li>
-              <div class="msg">
-                <span class="num">
-                  {{ instrDetails.useDuration }}
-                </span>
-                /小时
-              </div>
-              <em>总时长</em>
-            </li>
-          </ul>
+  <div class="container" style="background: #f8f9fd">
+    <section class="section-container">
+      <div class="detail-intro">
+        <div class="img-wrap">
+          <img :src="instrDetails.instPicture" alt="" style="width: 100%" />
         </div>
-        <div class="text-item">
-          <div class="info-item">
-            <div class="title">
-              收费标准
-            </div>
-            <div class="txt">
-              <div class="sf-box">
-                <div class="d1">机时</div>
-                <div class="d2">0元/小时</div>
-              </div>
-            </div>
+        <div class="detail-info">
+          <h3 class="instr-name">{{ instrDetails.instName }}</h3>
+          <div class="info-wrap">
+            <ul>
+              <li>
+                <div class="msg">
+                  <span class="num">
+                    {{ instrDetails.focusPeopleNum }}
+                  </span>
+                  /人
+                </div>
+                <em>关注者</em>
+              </li>
+              <li>
+                <div class="msg">
+                  <span class="num">
+                    {{ instrDetails.usePeopleNum }}
+                  </span>
+                  /人
+                </div>
+                <em>使用者</em>
+              </li>
+              <li>
+                <div class="msg">
+                  <span class="num">
+                    {{ instrDetails.useTimesNum }}
+                  </span>
+                  /次
+                </div>
+                <em>总次数</em>
+              </li>
+              <li>
+                <div class="msg">
+                  <span class="num">
+                    {{ instrDetails.useDuration }}
+                  </span>
+                  /小时
+                </div>
+                <em>总时长</em>
+              </li>
+            </ul>
           </div>
-          <div class="info-item">
-            <div class="title">
-              设备型号
-            </div>
-            <div class="txt">
-              {{ instrDetails.instNameEn }}
-            </div>
-          </div>
-          <div class="info-item">
-            <div class="title">
-              当前状态
-            </div>
-            <div class="txt">
-              <span v-if="instrDetails.instStatus == '10'">
-                正常
-              </span>
-              <span v-if="instrDetails.instStatus == '20'">
-                故障
-              </span>
-              <span v-if="instrDetails.instStatus == '30'">
-                报废
-              </span>
+          <div class="text-item">
+            <div class="info-item">
+              <div class="title">收费标准</div>
+              <div class="txt">
+                <div class="sf-box" v-if="chargeConfig.appointTimeEnable">
+                  <div class="d1">机时</div>
+                  <div class="d2">
+                    {{ chargeConfig.appointTimePrice }}元/{{
+                      chargeConfig.appointTimePriceMinute
+                    }}分钟
+                  </div>
+                </div>
+                <div class="sf-box" v-if="chargeConfig.useTimeEnable">
+                  <div class="d1">机时</div>
+                  <div class="d2">
+                    {{ chargeConfig.useTimePrice }}元/{{
+                      chargeConfig.useTimePriceMinute
+                    }}分钟
+                  </div>
+                </div>
+                <div class="sf-box" v-if="chargeConfig.usePerEnable">
+                  <div class="d1">机时</div>
+                  <div class="d2">{{ chargeConfig.usePerPrice }}元/每次</div>
+                </div>
+                <!-- <div
+                  class="sf-box ml10"
+                  v-if="
+                    instrDetails.instStatus == '10' &&
+                    instrDetails.isSampleDelivery == '10'
+                  "
+                >
+                  <div class="d1">送样</div>
+                  <div class="d2">详见检测项目</div>
+                </div> -->
+              </div>
             </div>
-          </div>
-          <div class="info-item">
-            <div class="title">
-              负责人
+            <div class="info-item">
+              <div class="title">设备型号</div>
+              <div class="txt">
+                {{ instrDetails.instNameEn }}
+              </div>
             </div>
-            <div class="txt">
-              {{ instrDetails.instHeadName }}
+            <div class="info-item">
+              <div class="title">当前状态</div>
+              <div class="txt">
+                <span v-if="instrDetails.instStatus == '10'">正常</span>
+                <span v-if="instrDetails.instStatus == '20'">故障</span>
+                <span v-if="instrDetails.instStatus == '30'">报废</span>
+              </div>
             </div>
-          </div>
-          <div class="info-item">
-            <div class="title">
-              放置地点
+            <div class="info-item">
+              <div class="title">负责人</div>
+              <div class="txt">
+                {{ instrDetails.instHeadName }}
+              </div>
             </div>
-            <div class="txt">
-              <span :title="instrDetails.placeAddress">
-                {{ instrDetails.placeAddress || "-" }}
-                <span v-if="instrDetails.laboratoryName">
-                  ({{ instrDetails.laboratoryName }})
+            <div class="info-item">
+              <div class="title">放置地点</div>
+              <div class="txt">
+                <span :title="instrDetails.placeAddress">
+                  {{ instrDetails.placeAddress || "-" }}
+                  <span v-if="instrDetails.laboratoryName">
+                    ({{ instrDetails.laboratoryName }})
+                  </span>
                 </span>
-              </span>
+              </div>
             </div>
           </div>
+          <div class="appoint-btn-wrap">
+            <el-button
+              type="primary"
+              size="default"
+              v-if="
+                instrDetails.instStatus == '10' &&
+                instrDetails.isAppointment == '10'
+              "
+              @click=""
+            >
+              使用预约
+            </el-button>
+            <!-- <el-button
+              type="success"
+              size="default"
+              v-if="
+                instrDetails.instStatus == '10' &&
+                instrDetails.isSampleDelivery == '10'
+              "
+              @click=""
+            >
+              送样预约
+            </el-button> -->
+          </div>
         </div>
-        <div class="appoint-btn-wrap">
-          <el-button
-            type="primary"
-            size="default"
+      </div>
+      <div class="tab-container def-tabs-wrap mb20">
+        <el-tabs class="mt10 mb10" v-model="activeName" type="card">
+          <el-tab-pane label="仪器信息" name="info">
+            <EqptDetails
+              ref="eqptRef"
+              :instrDetails="instrDetails"
+            ></EqptDetails>
+          </el-tab-pane>
+          <el-tab-pane label="预约信息" name="appoint">
+            <AppointRecord v-if="activeName == 'appoint'"></AppointRecord>
+          </el-tab-pane>
+          <!-- <el-tab-pane
+            label="检测项目"
             v-if="
               instrDetails.instStatus == '10' &&
-                instrDetails.isAppointment == '10'
+              instrDetails.isSampleDelivery == '10'
             "
-            @click=""
+            name="sample"
           >
-            使用预约
-          </el-button>
-          <el-button
-            type="success"
-            size="default"
-            v-if="
-              instrDetails.instStatus == '10' &&
-                instrDetails.isSampleDelivery == '10'
-            "
-            @click=""
-          >
-            送样预约
-          </el-button>
-        </div>
+            <SampleCharg v-if="activeName == 'sample'"></SampleCharg>
+          </el-tab-pane> -->
+          <el-tab-pane label="附件信息" name="file">
+            <FileTable v-if="activeName == 'file'"></FileTable>
+          </el-tab-pane>
+          <el-tab-pane label="仪器公告" name="notice">
+            <NoticeTable v-if="activeName == 'notice'"></NoticeTable>
+          </el-tab-pane>
+        </el-tabs>
       </div>
-    </div>
-    <div class="tab-container def-tabs-wrap">
-      <el-tabs class="mt10 mb10" v-model="activeName" type="card" @tab-click="">
-        <el-tab-pane label="仪器信息" name="info">
-          <EqptDetails ref="eqptRef" :instrDetails="instrDetails"></EqptDetails>
-        </el-tab-pane>
-        <el-tab-pane label="预约信息" name="second"></el-tab-pane>
-        <el-tab-pane label="附件信息" name="file">
-          <FileTable v-if="activeName == 'file'"></FileTable>
-        </el-tab-pane>
-        <el-tab-pane label="仪器公告" name="notice">
-          <NoticeTable v-if="activeName == 'notice'"></NoticeTable>
-        </el-tab-pane>
-      </el-tabs>
-    </div>
-  </section>
+    </section>
+  </div>
 </template>
 
 <script>
-import to from "await-to-js";
-import { getInstrDetails } from "@/api/instr/index";
-import EqptDetails from "./components/eqpt-details.vue";
-import FileTable from "./components/instr-file.vue";
-import NoticeTable from "./components/instr-notice.vue";
-export default {
-  name: "equipmentDetails",
-  components: { EqptDetails, FileTable, NoticeTable },
-  data() {
-    return {
-      activeName: "info",
-      instrDetails: {},
-    };
-  },
-  created() {},
+  import to from "await-to-js";
+  import { getInstrDetails, getChargeCfg } from "@/api/instr/index";
+  import EqptDetails from "./components/eqpt-details.vue";
+  import FileTable from "./components/instr-file.vue";
+  import NoticeTable from "./components/instr-notice.vue";
+  import SampleCharg from "./components/sample-charg.vue";
+  import AppointRecord from "./components/appoint-record.vue";
+  export default {
+    name: "equipmentDetails",
+    components: {
+      EqptDetails,
+      FileTable,
+      NoticeTable,
+      SampleCharg,
+      AppointRecord,
+    },
+    data() {
+      return {
+        activeName: "info",
+        instrDetails: {},
+        chargeConfig: {},
+      };
+    },
+    created() {},
 
-  mounted() {
-    this.getInstrDetails();
-  },
+    mounted() {
+      this.getInstrDetails();
+    },
 
-  methods: {
-    async getInstrDetails() {
-      console.log(this.$route.query.id);
-      const id = this.$route.query.id * 1;
-      if (!id)
-        return this.$message({
-          message: "缺少必要查询参数",
-          type: "warning",
-        });
-      const [err, res] = await to(getInstrDetails({ id }));
-      if (err) return;
-      if (res.code == 200) {
-        this.instrDetails = res.data;
-      }
+    methods: {
+      async getInstrDetails() {
+        const id = this.$route.query.id * 1;
+        if (!id)
+          return this.$message({
+            message: "缺少必要查询参数",
+            type: "warning",
+          });
+        const [err, res] = await to(getInstrDetails({ id }));
+        if (err) return;
+        if (res.code == 200) {
+          this.instrDetails = res.data;
+          this.getChargeCfg(id);
+        }
+      },
+      async getChargeCfg(id) {
+        const [err, res] = await to(getChargeCfg({ id }));
+        if (err) return;
+        if (res.code == 200) {
+          this.chargeConfig = res.data;
+        }
+      },
     },
-  },
-};
+  };
 </script>
 
 <style lang="scss" scoped>
-.section-container {
-  width: 1200px;
-  margin: 0 auto;
-  .detail-intro {
-    display: flex;
-    .img-wrap {
-      width: 420px;
-      height: 420px;
-      background: #f3f6fb;
-      border: 1px solid #e1e4ee;
-      -webkit-box-sizing: border-box;
-      box-sizing: border-box;
-      float: left;
-      position: relative;
-      overflow: hidden;
-    }
-    .detail-info {
-      margin-left: 28px;
-      .instr-name {
-        font-size: 20px;
-        font-weight: 700;
-        line-height: 26px;
-        padding: 10px 0 16px;
-      }
-      .info-wrap {
-        width: 483px;
-        height: 72px;
-        background: #f8f9fb;
-        border: 1px solid #e8ecf2;
-        border-radius: 4px;
-        margin-bottom: 16px;
-        color: #666;
-        li {
-          position: relative;
-          float: left;
-          width: 25%;
-          text-align: center;
-          font-size: 12px;
-          line-height: 16px;
-          position: relative;
-          .msg {
-            line-height: 18px;
-            font-weight: 700;
-            font-size: 14px;
-            padding: 16px 0 5px;
-            .num {
-              font-size: 20px;
-            }
-          }
-          &:nth-of-type(2)::before,
-          &:nth-of-type(3)::before,
-          &:nth-of-type(4)::before {
-            display: block;
-            content: "";
-            position: absolute;
-            top: 19px;
-            left: 0;
-            width: 1px;
-            height: 36px;
-            background: #e4e9f2;
-          }
-        }
-      }
-      .text-item {
-        min-height: 218px;
-        height: auto;
+  .section-container {
+    width: 1200px;
+    margin: 0 auto;
+    background: #fff;
+    .detail-intro {
+      display: flex;
+      .img-wrap {
+        width: 420px;
+        height: 420px;
+        background: #f3f6fb;
+        border: 1px solid #e1e4ee;
+        -webkit-box-sizing: border-box;
         box-sizing: border-box;
-        padding-bottom: 47px;
-        .info-item {
-          width: 100%;
-          min-height: 28px;
-          font-size: 14px;
-          line-height: 28px;
-          margin-bottom: 6px;
-        }
-        .title {
-          width: 74px;
-          float: left;
+        float: left;
+        position: relative;
+        overflow: hidden;
+      }
+      .detail-info {
+        margin-left: 28px;
+        .instr-name {
+          font-size: 20px;
           font-weight: 700;
-          color: #666;
+          line-height: 26px;
+          padding: 10px 0 16px;
         }
-        .txt {
-          margin-left: 74px;
-          overflow: hidden;
-          text-overflow: ellipsis;
-          white-space: nowrap;
-          max-width: 410px;
-          .sf-box {
-            display: inline-block;
-            border: 1px solid blue;
-            width: 150px;
-            .d1 {
-              display: inline-block;
-              background-color: dodgerblue;
-              padding: 0 8px 0 8px;
-              color: white;
-              border-right: 1px solid blue;
+        .info-wrap {
+          width: 483px;
+          height: 72px;
+          background: #f8f9fb;
+          border: 1px solid #e8ecf2;
+          border-radius: 4px;
+          margin-bottom: 16px;
+          color: #666;
+          li {
+            position: relative;
+            float: left;
+            width: 25%;
+            text-align: center;
+            font-size: 12px;
+            line-height: 16px;
+            position: relative;
+            .msg {
+              line-height: 18px;
+              font-weight: 700;
+              font-size: 14px;
+              padding: 16px 0 5px;
+              .num {
+                font-size: 20px;
+              }
             }
-            .d2 {
+            &:nth-of-type(2)::before,
+            &:nth-of-type(3)::before,
+            &:nth-of-type(4)::before {
+              display: block;
+              content: "";
+              position: absolute;
+              top: 19px;
+              left: 0;
+              width: 1px;
+              height: 36px;
+              background: #e4e9f2;
+            }
+          }
+        }
+        .text-item {
+          min-height: 218px;
+          height: auto;
+          box-sizing: border-box;
+          padding-bottom: 47px;
+          .info-item {
+            width: 100%;
+            min-height: 28px;
+            font-size: 14px;
+            line-height: 28px;
+            margin-bottom: 6px;
+          }
+          .title {
+            width: 74px;
+            float: left;
+            font-weight: 700;
+            color: #666;
+          }
+          .txt {
+            margin-left: 74px;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+            max-width: 410px;
+            .sf-box {
               display: inline-block;
-              color: blue;
-              white-space: nowrap;
-              overflow: hidden;
-              text-overflow: ellipsis;
-              vertical-align: bottom;
-              width: 100px;
-              padding-left: 6px;
+              border: 1px solid blue;
+              width: 150px;
+              .d1 {
+                display: inline-block;
+                background-color: dodgerblue;
+                padding: 0 8px 0 8px;
+                color: white;
+                border-right: 1px solid blue;
+              }
+              .d2 {
+                display: inline-block;
+                color: blue;
+                white-space: nowrap;
+                overflow: hidden;
+                text-overflow: ellipsis;
+                vertical-align: bottom;
+                width: 100px;
+                padding-left: 6px;
+              }
             }
           }
         }
       }
     }
   }
-}
-div {
-  color: #333;
-}
+  div {
+    color: #333;
+  }
+  :deep(.el-tabs__content) {
+    padding: 10px;
+  }
 </style>

+ 319 - 320
src/views/equipment/index.vue

@@ -8,9 +8,9 @@
             v-model="searchForm.searchText"
             placeholder="请输入仪器名称"
             clearable
-            style="height: 48px;"
+            style="height: 48px"
           />
-          <el-button type="primary" style="width: 90px;" @click="globalSearch">
+          <el-button type="primary" style="width: 90px" @click="globalSearch">
             搜索
           </el-button>
         </div>
@@ -143,7 +143,7 @@
           <div v-else>
             <div class="instr-item" v-for="v in instrDataList" :key="v.id">
               <div class="img-item">
-                <img :src="v.instPicture" style="width: 100%;" alt="" />
+                <img :src="v.instPicture" style="width: 100%" alt="" />
               </div>
               <div class="text-item">
                 <p class="name">{{ v.instName }}</p>
@@ -154,15 +154,9 @@
                 <div class="info">
                   <div class="info-title">状态</div>
                   <div class="info-txt">
-                    <span v-if="v.instStatus == '10'">
-                      正常
-                    </span>
-                    <span v-if="v.instStatus == '20'">
-                      故障
-                    </span>
-                    <span v-if="v.instStatus == '30'">
-                      报废
-                    </span>
+                    <span v-if="v.instStatus == '10'">正常</span>
+                    <span v-if="v.instStatus == '20'">故障</span>
+                    <span v-if="v.instStatus == '30'">报废</span>
                   </div>
                 </div>
                 <div class="info">
@@ -187,7 +181,7 @@
                     type="primary"
                     size="default"
                     v-if="v.instStatus == '10' && v.isAppointment == '10'"
-                    @click=""
+                    @click="handleAppoint(v)"
                   >
                     仪器预约
                   </el-button>
@@ -220,353 +214,358 @@
         </div>
       </section>
     </div>
+    <appoint ref="appointRef"></appoint>
   </div>
 </template>
 
 <script>
-import to from "await-to-js";
-import { Loading } from "element-ui";
-import { getInstrList, getInstNameEnCount } from "@/api/instr/index";
-export default {
-  name: "equipment",
-  components: {},
-  data() {
-    return {
-      platFilterExpand: false,
-      instrTypeFilterExpand: false,
-      searchForm: {
-        searchText: "",
-        laboratoryName: "",
-        instClassDesc: "",
-        instNameEn: "",
-        pageNum: 1,
-        pageSize: 10,
-      },
-      total: 0,
-      instrDataList: [],
-      laboratoryNameOptions: [],
-      instClassDescOptions: [],
-      instNameEnOptions: [],
-      options: {
-        text: "正在加载中,请稍后...",
-        background: "rgba(0, 0, 0, 0.6)",
-      },
-    };
-  },
-  created() {},
-
-  mounted() {
-    this.getInstrData();
-  },
-
-  methods: {
-    // 获取型号数量
-    async getInstNameEnCountData() {
-      const params = {
-        instClassDesc: this.searchForm.instClassDesc,
-        laboratoryName: this.searchForm.laboratoryName,
-        searchText: this.searchForm.searchText,
+  import to from "await-to-js";
+  import { Loading } from "element-ui";
+  import { getInstrList, getInstNameEnCount } from "@/api/instr/index";
+  import appoint from "./components/appoint.vue";
+  export default {
+    name: "equipment",
+    components: { appoint },
+    data() {
+      return {
+        platFilterExpand: false,
+        instrTypeFilterExpand: false,
+        searchForm: {
+          searchText: "",
+          laboratoryName: "",
+          instClassDesc: "",
+          instNameEn: "",
+          pageNum: 1,
+          pageSize: 10,
+        },
+        total: 0,
+        instrDataList: [],
+        laboratoryNameOptions: [],
+        instClassDescOptions: [],
+        instNameEnOptions: [],
+        options: {
+          text: "正在加载中,请稍后...",
+          background: "rgba(0, 0, 0, 0.6)",
+        },
       };
-      const [err, res] = await to(getInstNameEnCount(params));
-      if (err) return;
-      if (res.code == 200) {
-        this.instNameEnOptions = res.data;
-      }
-    },
-    // 上方搜索查询
-    async globalSearch() {
-      this.searchForm.pageNum = 1;
-      this.searchForm.instClassDesc = "";
-      this.searchForm.laboratoryName = "";
-      this.searchForm.instNameEn = "";
-      this.getInstrData();
-    },
-    async getInstrData() {
-      Loading.service(this.loadingOption);
-      const [err, res] = await to(getInstrList(this.searchForm));
-      if (err) return;
-      if (res.code == 200) {
-        this.instrDataList = res.data.list;
-        this.total = res.data.total;
-        this.laboratoryNameOptions = this.objTransforArr(
-          res.data.count.laboratoryName
-        );
-        this.instClassDescOptions = this.objTransforArr(
-          res.data.count.instClassDesc
-        );
-        this.getInstNameEnCountData();
-      }
-      setTimeout(() => {
-        Loading.service(this.loadingOption).close();
-      });
-    },
-    // 对象转数组
-    objTransforArr(obj) {
-      const arr = Object.entries(obj).map(([key, value]) => ({
-        name: key,
-        count: value,
-      }));
-      return arr;
-    },
-    // 删除查询条件
-    closeTag(type) {
-      switch (type) {
-        case "laboratoryName":
-          this.searchForm.laboratoryName = "";
-          break;
-        case "instClassDesc":
-          this.searchForm.instClassDesc = "";
-          break;
-        case "instNameEn":
-          this.searchForm.instNameEn = "";
-          break;
-      }
-      this.searchForm.pageNum = 1;
-      this.getInstrData();
-    },
-    // 展开 合并
-    handleExpandFilter(type = "plat" | "instrType") {
-      if (type === "plat") {
-        this.platFilterExpand = !this.platFilterExpand;
-      } else if (type === "instrType") {
-        this.instrTypeFilterExpand = !this.instrTypeFilterExpand;
-      }
-    },
-    // 选择查询条件
-    selectFilterItem(name, val) {
-      switch (val) {
-        case "laboratoryName":
-          if (this.searchForm.laboratoryName != name) {
-            this.searchForm.laboratoryName = name;
-            this.searchForm.pageNum = 1;
-            this.getInstrData();
-          }
-          break;
-        case "instClassDesc":
-          if (this.searchForm.instClassDesc != name) {
-            this.searchForm.instClassDesc = name;
-            this.searchForm.pageNum = 1;
-            this.getInstrData();
-          }
-          break;
-        case "instNameEn":
-          if (this.searchForm.instNameEn != name) {
-            this.searchForm.instNameEn = name;
-            this.searchForm.pageNum = 1;
-            this.getInstrData();
-          }
-          break;
-      }
     },
-    handleSizeChange(val) {
-      this.searchForm.pageSize = val;
-      this.getInstrData();
-    },
-    handleCurrentChange(val) {
-      this.searchForm.pageNum = val;
+    created() {},
+
+    mounted() {
       this.getInstrData();
     },
-    linkToDetails(v) {
-      this.$router.push("/appointment/equipment-details?id=" + v.id);
+
+    methods: {
+      handleAppoint(row) {
+        this.$refs.appointRef.openDialog(row);
+      },
+      // 获取型号数量
+      async getInstNameEnCountData() {
+        const params = {
+          instClassDesc: this.searchForm.instClassDesc,
+          laboratoryName: this.searchForm.laboratoryName,
+          searchText: this.searchForm.searchText,
+        };
+        const [err, res] = await to(getInstNameEnCount(params));
+        if (err) return;
+        if (res.code == 200) {
+          this.instNameEnOptions = res.data;
+        }
+      },
+      // 上方搜索查询
+      async globalSearch() {
+        this.searchForm.pageNum = 1;
+        this.searchForm.instClassDesc = "";
+        this.searchForm.laboratoryName = "";
+        this.searchForm.instNameEn = "";
+        this.getInstrData();
+      },
+      async getInstrData() {
+        Loading.service(this.loadingOption);
+        const [err, res] = await to(getInstrList(this.searchForm));
+        if (err) return;
+        if (res.code == 200) {
+          this.instrDataList = res.data.list;
+          this.total = res.data.total;
+          this.laboratoryNameOptions = this.objTransforArr(
+            res.data.count.laboratoryName
+          );
+          this.instClassDescOptions = this.objTransforArr(
+            res.data.count.instClassDesc
+          );
+          this.getInstNameEnCountData();
+        }
+        setTimeout(() => {
+          Loading.service(this.loadingOption).close();
+        });
+      },
+      // 对象转数组
+      objTransforArr(obj) {
+        const arr = Object.entries(obj).map(([key, value]) => ({
+          name: key,
+          count: value,
+        }));
+        return arr;
+      },
+      // 删除查询条件
+      closeTag(type) {
+        switch (type) {
+          case "laboratoryName":
+            this.searchForm.laboratoryName = "";
+            break;
+          case "instClassDesc":
+            this.searchForm.instClassDesc = "";
+            break;
+          case "instNameEn":
+            this.searchForm.instNameEn = "";
+            break;
+        }
+        this.searchForm.pageNum = 1;
+        this.getInstrData();
+      },
+      // 展开 合并
+      handleExpandFilter(type = "plat" | "instrType") {
+        if (type === "plat") {
+          this.platFilterExpand = !this.platFilterExpand;
+        } else if (type === "instrType") {
+          this.instrTypeFilterExpand = !this.instrTypeFilterExpand;
+        }
+      },
+      // 选择查询条件
+      selectFilterItem(name, val) {
+        switch (val) {
+          case "laboratoryName":
+            if (this.searchForm.laboratoryName != name) {
+              this.searchForm.laboratoryName = name;
+              this.searchForm.pageNum = 1;
+              this.getInstrData();
+            }
+            break;
+          case "instClassDesc":
+            if (this.searchForm.instClassDesc != name) {
+              this.searchForm.instClassDesc = name;
+              this.searchForm.pageNum = 1;
+              this.getInstrData();
+            }
+            break;
+          case "instNameEn":
+            if (this.searchForm.instNameEn != name) {
+              this.searchForm.instNameEn = name;
+              this.searchForm.pageNum = 1;
+              this.getInstrData();
+            }
+            break;
+        }
+      },
+      handleSizeChange(val) {
+        this.searchForm.pageSize = val;
+        this.getInstrData();
+      },
+      handleCurrentChange(val) {
+        this.searchForm.pageNum = val;
+        this.getInstrData();
+      },
+      linkToDetails(v) {
+        this.$router.push("/appointment/equipment-details?id=" + v.id);
+      },
     },
-  },
-};
+  };
 </script>
 
 <style lang="scss" scoped>
-.search-wrap {
-  width: 100%;
-  height: 240px;
-  position: relative;
-  &::after {
-    display: block;
-    content: "";
+  .search-wrap {
     width: 100%;
-    height: 100%;
-    position: absolute;
-    top: 0;
-    left: 0;
-    background: url("../../assets/img/equipment-banner.png") center;
-  }
-  .search-item {
-    width: 800px;
     height: 240px;
-    margin: 0 auto;
     position: relative;
-    z-index: 1;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    .input-item {
+    &::after {
+      display: block;
+      content: "";
       width: 100%;
-      height: 40px;
+      height: 100%;
+      position: absolute;
+      top: 0;
+      left: 0;
+      background: url("../../assets/img/equipment-banner.png") center;
+    }
+    .search-item {
+      width: 800px;
+      height: 240px;
+      margin: 0 auto;
+      position: relative;
+      z-index: 1;
       display: flex;
+      align-items: center;
+      justify-content: center;
+      .input-item {
+        width: 100%;
+        height: 40px;
+        display: flex;
+      }
     }
   }
-}
-.section-container {
-  width: 1200px;
-  margin: 0 auto;
-}
-.filter-wrap {
-  width: 100%;
-  .filter-item {
+  .section-container {
+    width: 1200px;
+    margin: 0 auto;
+  }
+  .filter-wrap {
     width: 100%;
-    min-height: 48px;
-    display: flex;
-    border-bottom: 2px dashed rgba(112, 112, 112, 0.18);
-    &:last-child {
-      border-bottom: none;
+    .filter-item {
+      width: 100%;
+      min-height: 48px;
+      display: flex;
+      border-bottom: 2px dashed rgba(112, 112, 112, 0.18);
+      &:last-child {
+        border-bottom: none;
+      }
     }
-  }
 
-  .left-tit {
-    width: 120px;
-    background: #f1f1f1;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    font-weight: bold;
-  }
-  .right-filter-content {
-    flex: 1;
-    padding: 9px 24px 9px 36px;
-    display: flex;
-    flex-wrap: wrap;
-    position: relative;
-    .list {
-      max-height: 120px;
-      overflow: auto;
-      width: 100%;
+    .left-tit {
+      width: 120px;
+      background: #f1f1f1;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-weight: bold;
+    }
+    .right-filter-content {
+      flex: 1;
+      padding: 9px 24px 9px 36px;
       display: flex;
       flex-wrap: wrap;
-      // &::-webkit-scrollbar {
-      //   display: none;
-      // }
-      .dragger-container {
-        position: absolute;
-        width: 6px;
-        top: 36px;
-        right: 34px;
-        height: 84px;
-        background: #ccc;
-        border-radius: 20px;
-        .dragger-bar {
+      position: relative;
+      .list {
+        max-height: 120px;
+        overflow: auto;
+        width: 100%;
+        display: flex;
+        flex-wrap: wrap;
+        // &::-webkit-scrollbar {
+        //   display: none;
+        // }
+        .dragger-container {
           position: absolute;
           width: 6px;
-          height: 60px;
-          top: 0px;
+          top: 36px;
+          right: 34px;
+          height: 84px;
+          background: #ccc;
           border-radius: 20px;
-          background: rgba(0, 0, 0, 0.75);
+          .dragger-bar {
+            position: absolute;
+            width: 6px;
+            height: 60px;
+            top: 0px;
+            border-radius: 20px;
+            background: rgba(0, 0, 0, 0.75);
+          }
         }
       }
-    }
-    .name {
-      font-size: 14px;
-      margin: 5px 30px 5px 0;
-      cursor: pointer;
-      &:hover {
-        text-decoration: underline;
-        color: #1677ff;
-      }
-      &.avtive {
-        text-decoration: underline;
-        color: #1677ff;
+      .name {
+        font-size: 14px;
+        margin: 5px 30px 5px 0;
+        cursor: pointer;
+        &:hover {
+          text-decoration: underline;
+          color: #1677ff;
+        }
+        &.avtive {
+          text-decoration: underline;
+          color: #1677ff;
+        }
       }
-    }
-    .more-item {
-      position: absolute;
-      top: 12px;
-      right: 0;
-      padding-right: 16px;
-      font-size: 14px;
-      cursor: pointer;
-      z-index: 2;
-      i {
-        color: #b1b1b1;
+      .more-item {
+        position: absolute;
+        top: 12px;
+        right: 0;
+        padding-right: 16px;
+        font-size: 14px;
+        cursor: pointer;
+        z-index: 2;
+        i {
+          color: #b1b1b1;
+        }
       }
     }
   }
-}
-.filter-result {
-  padding: 15px 18px 0 18px;
-  display: flex;
-  border: 1px solid rgba(112, 112, 112, 0.06);
-  position: relative;
-  .title {
-    width: 120px;
-    font-size: 16px;
-    color: #666;
-    font-weight: 700;
-    padding-right: 18px;
-    padding-top: 3px;
-    flex: 0 0 120px;
-  }
-  .filter-wrap {
-    min-height: 45px;
-    padding-right: 80px;
-  }
-}
-.instr-list {
-  background: #fff;
-  border-radius: 6px;
-  margin-top: 20px;
-  min-height: 200px;
-  .instr-item {
-    padding: 24px 30px;
-    margin: 0 30px;
+  .filter-result {
+    padding: 15px 18px 0 18px;
     display: flex;
-    .img-item {
-      width: 180px;
-      height: 180px;
-      border-radius: 6px;
-      overflow: hidden;
-      margin-right: 18px;
-      position: relative;
-      background: #f3f6fb;
+    border: 1px solid rgba(112, 112, 112, 0.06);
+    position: relative;
+    .title {
+      width: 120px;
+      font-size: 16px;
+      color: #666;
+      font-weight: 700;
+      padding-right: 18px;
+      padding-top: 3px;
+      flex: 0 0 120px;
     }
-    .text-item {
-      flex: 1;
-      width: 0;
-      font-size: 14px;
-      padding-top: 6px;
-      .name {
-        font-weight: bold;
-        font-size: 20px;
-        margin-bottom: 12px;
+    .filter-wrap {
+      min-height: 45px;
+      padding-right: 80px;
+    }
+  }
+  .instr-list {
+    background: #fff;
+    border-radius: 6px;
+    margin-top: 20px;
+    min-height: 200px;
+    .instr-item {
+      padding: 24px 30px;
+      margin: 0 30px;
+      display: flex;
+      .img-item {
+        width: 180px;
+        height: 180px;
+        border-radius: 6px;
+        overflow: hidden;
+        margin-right: 18px;
+        position: relative;
+        background: #f3f6fb;
       }
-      .info {
-        color: #666;
-        display: flex;
+      .text-item {
+        flex: 1;
+        width: 0;
         font-size: 14px;
-        margin-bottom: 8px;
-        .info-title {
-          width: 80px;
+        padding-top: 6px;
+        .name {
+          font-weight: bold;
+          font-size: 20px;
+          margin-bottom: 12px;
         }
-        .info-txt {
-          width: 0;
-          flex: 1;
+        .info {
+          color: #666;
+          display: flex;
+          font-size: 14px;
+          margin-bottom: 8px;
+          .info-title {
+            width: 80px;
+          }
+          .info-txt {
+            width: 0;
+            flex: 1;
+          }
         }
       }
+      .appoint-item {
+        width: 120px;
+        padding: 0 0 0 18px;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+      }
     }
-    .appoint-item {
-      width: 120px;
-      padding: 0 0 0 18px;
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      justify-content: center;
-    }
   }
-}
-.pagination {
-  width: 100%;
-  height: 50px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-div {
-  color: #333;
-}
+  .pagination {
+    width: 100%;
+    height: 50px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  div {
+    color: #333;
+  }
 </style>

+ 14 - 16
yarn.lock

@@ -659,7 +659,7 @@
   dependencies:
     preact "~10.12.1"
 
-"@fullcalendar/daygrid@^6.1.15":
+"@fullcalendar/daygrid@^6.1.15", "@fullcalendar/daygrid@~6.1.15":
   version "6.1.15"
   resolved "https://registry.npmmirror.com/@fullcalendar/daygrid/-/daygrid-6.1.15.tgz#91208b0955ba805ddad285a53ee6f53855146963"
   integrity sha512-j8tL0HhfiVsdtOCLfzK2J0RtSkiad3BYYemwQKq512cx6btz6ZZ2RNc/hVnIxluuWFyvx5sXZwoeTJsFSFTEFA==
@@ -669,6 +669,13 @@
   resolved "https://registry.npmmirror.com/@fullcalendar/interaction/-/interaction-6.1.15.tgz#1c685d5c269388d4877b75ab2185e97d7c386cc7"
   integrity sha512-DOTSkofizM7QItjgu7W68TvKKvN9PSEEvDJceyMbQDvlXHa7pm/WAVtAc6xSDZ9xmB1QramYoWGLHkCYbTW1rQ==
 
+"@fullcalendar/timegrid@^6.1.5":
+  version "6.1.15"
+  resolved "https://registry.npmmirror.com/@fullcalendar/timegrid/-/timegrid-6.1.15.tgz#c4630b7c03c813065154c6e3981f8d51d9d692e5"
+  integrity sha512-61ORr3A148RtxQ2FNG7JKvacyA/TEVZ7z6I+3E9Oeu3dqTf6M928bFcpehRTIK6zIA6Yifs7BeWHgOE9dFnpbw==
+  dependencies:
+    "@fullcalendar/daygrid" "~6.1.15"
+
 "@fullcalendar/vue@^6.1.15":
   version "6.1.15"
   resolved "https://registry.npmmirror.com/@fullcalendar/vue/-/vue-6.1.15.tgz#3f8069f63769ebb0eb0fa9f3696f226223965781"
@@ -3576,11 +3583,6 @@ es5-shim@^4.5.1:
   resolved "https://registry.npmmirror.com/es5-shim/-/es5-shim-4.6.7.tgz#bc67ae0fc3dd520636e0a1601cc73b450ad3e955"
   integrity sha512-jg21/dmlrNQI7JyyA2w7n+yifSxBng0ZralnSfVZjoCawgNTCnS+yBCyVM9DL5itm7SUnDGgv7hcq2XCZX4iRQ==
 
-es6-promise@^4.0.5:
-  version "4.2.8"
-  resolved "https://registry.npmmirror.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
-  integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
-
 escalade@^3.1.1, escalade@^3.2.0:
   version "3.2.0"
   resolved "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
@@ -6070,6 +6072,11 @@ mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.6, mkdirp@~0.5.1:
   dependencies:
     minimist "^1.2.6"
 
+moment@^2.29.4:
+  version "2.30.1"
+  resolved "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae"
+  integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==
+
 move-concurrently@^1.0.1:
   version "1.0.1"
   resolved "https://registry.npmmirror.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
@@ -9335,15 +9342,6 @@ vue-eslint-parser@^5.0.0:
     esquery "^1.0.1"
     lodash "^4.17.11"
 
-vue-fullcalendar@^1.0.5, vue-fullcalendar@^1.0.9:
-  version "1.0.9"
-  resolved "https://registry.npmmirror.com/vue-fullcalendar/-/vue-fullcalendar-1.0.9.tgz#a853bb25946252277533b9994ef503cfacb01233"
-  integrity sha512-la9lMTDtn1tJTMdaD9KG37WXFm6wY/XaQAauMztSghGq6w5hRR8Xsg1sNnSd1yOW87OPJ0aLOE16e8tRsbaodA==
-  dependencies:
-    es6-promise "^4.0.5"
-    vue "^2.1.8"
-    vue-fullcalendar "^1.0.5"
-
 vue-hot-reload-api@^2.3.0:
   version "2.3.4"
   resolved "https://registry.npmmirror.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
@@ -9402,7 +9400,7 @@ vue-video-player@^5.0.2:
     videojs-flash "^2.1.0"
     videojs-hotkeys "^0.2.20"
 
-vue@^2.1.8, vue@^2.6.10:
+vue@^2.6.10:
   version "2.7.16"
   resolved "https://registry.npmmirror.com/vue/-/vue-2.7.16.tgz#98c60de9def99c0e3da8dae59b304ead43b967c9"
   integrity sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==