Quellcode durchsuchen

feature(遵义医科大):官网开发

wanglj vor 11 Monaten
Ursprung
Commit
dc11a8dba9

+ 5 - 0
package.json

@@ -8,9 +8,14 @@
     "lint": "vue-cli-service lint"
   },
   "dependencies": {
+    "@fullcalendar/core": "^6.1.15",
+    "@fullcalendar/daygrid": "^6.1.15",
+    "@fullcalendar/interaction": "^6.1.15",
+    "@fullcalendar/vue": "^6.1.15",
     "await-to-js": "^3.0.0",
     "axios": "^0.19.0",
     "core-js": "^2.6.5",
+    "echarts": "^5.6.0",
     "element-ui": "^2.12.0",
     "js-cookie": "2.2.0",
     "sass": "^1.52.2",

BIN
public/favicon.ico


+ 2 - 1
public/index.html

@@ -6,7 +6,8 @@
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
     <link rel="icon" href="<%= BASE_URL %>favicon.ico">
     <script src="config.js"></script>
-    <title>遵义医科大</title>
+    <script src="//api.map.baidu.com/api?type=webgl&v=1.0&ak=5js4F32IlhEUdMjX4GPZbpRYZZQCjA43"></script>
+    <title>遵义医科大学附属医院临床医学公共实验中心</title>
   </head>
   <body>
     <noscript>

+ 42 - 23
src/App.vue

@@ -10,12 +10,13 @@
           <el-menu :default-active="defaultActive"
                    class="el-menu-demo"
                    mode="horizontal"
+                   menu-trigger="click"
                    @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">
+            <el-submenu index="/appointment" popper-class="custom-submenu">
               <template slot="title">预约管理</template>
               <el-menu-item index="2-1">仪器设备</el-menu-item>
               <el-menu-item index="2-2">中心平台</el-menu-item>
@@ -23,28 +24,32 @@
             </el-submenu>
             <el-menu-item index="/product">教育培训</el-menu-item>
             <el-menu-item index="/case">资料下载</el-menu-item>
-            <el-menu-item index="/download">联系我们</el-menu-item>
+            <el-menu-item index="/contact-us">联系我们</el-menu-item>
           </el-menu>
         </div>
         <div class="user"
              v-if="$route.path != '/login'">
-          <el-button type="text"
+          <template v-if="!userInfo.id">
+            <el-button type="text"
                      @click="toLogin">登录</el-button>
-          <el-button type="text"
+            <el-button type="text"
                      @click="onRegister">注册</el-button>
-          <el-button type="primary"
+          </template>
+          <template v-else>
+            <el-button type="primary"
                      size="mini"
                      color="#386AFE"
-                     @click="onRegister">个人中心</el-button>
-          <el-dropdown class="ml12" size="mini">
-            <span class="flex flex-center">
-              <img src="./assets/img/user-avatar.png" />
-              <i class="el-icon-arrow-down el-icon--right"></i>
-            </span>
-            <el-dropdown-menu slot="dropdown">
-              <el-dropdown-item>注销</el-dropdown-item>
-            </el-dropdown-menu>
-          </el-dropdown>
+                     @click="toCenter">个人中心</el-button>
+            <el-dropdown class="ml12" size="medium" @command="handleCommand">
+              <span class="flex flex-center">
+                <img :src="userInfo.avatar" />
+                <i class="el-icon-arrow-down el-icon--right"></i>
+              </span>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item command="logOut">注销</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
+          </template>
         </div>
       </el-header>
       <el-main>
@@ -54,19 +59,14 @@
         版权所有©遵义医科大学附属医院 贵ICP备17033796号-1
       </div>
     </el-container>
-    <Register ref="registerRef" />
   </div>
 </template>
 
 <script>
 import { mapGetters } from "vuex";
-import Register from "@/components/Register";
 export default {
-  components: {
-    Register,
-  },
   computed: {
-    ...mapGetters(["user"]),
+    ...mapGetters(["userInfo"]),
   },
   watch: {
     $route(val) {
@@ -86,11 +86,25 @@ export default {
       window.console.log(this.isShow);
     },
     onRegister() {
-      this.$refs.registerRef.openDialog();
+      this.$router.push("/register");
     },
     toLogin() {
       this.$router.push("/login");
     },
+    toCenter() {
+      this.$router.push("/personal-center");
+    },
+    handleCommand(command) {
+      if(command == 'logOut') {
+        this.$confirm("确定注销吗?", "提示", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.$store.dispatch('logOut')
+        })
+      }
+    }
   },
 };
 </script>
@@ -154,6 +168,11 @@ body {
   .user {
     display: flex;
     align-items: center;
+    img {
+      width: 36px;
+      height: 36px;
+      border-radius: 50%;
+    }
   }
 }
 .el-main {
@@ -162,7 +181,7 @@ body {
 
 .footer {
   width: 100%;
-  height: 64px;
+  height: 32px;
   overflow: hidden;
   background-color: #386afe;
   font-weight: 400;

+ 13 - 104
src/api/login.js

@@ -1,115 +1,24 @@
 /*
  * @Author: wanglj
  * @Date: 2022-04-25 10:38:19
- * @LastEditors: 卢传敏
- * @LastEditTime: 2022-08-14 18:07:44
+ * @LastEditors: wanglj
+ * @LastEditTime: 2025-01-10 09:35:57
  * @Description: file content
- * @FilePath: \biobank_frontend02\src\api\login.js
+ * @FilePath: \labsop_website\src\api\login.js
  */
-import request from "@/utils/request";
-import request2 from "@/utils/micro_request";
+import request from "@/utils/micro_request";
 
 const basePath = $GlobalConfig.VUE_APP_AdminPath;
 
 // 登录方法
-export function login(username, password, code, uuid) {
-  const data = {
-    username,
-    password,
-    idValueC: code,
-    idKeyC: uuid
-  };
-  // return request2.postRequest(basePath, "User", "Login", data);
-  return request2.postRequestWithClientInfo(basePath, "User", "Login", data);
-  // return request2.postRequestWith(basePath, "User", "Login", data);
+export function signIn(data) {
+  return request.postRequestWithClientInfo(basePath, "System", "Login", data);
 }
-
-// 金赛药业
-export function callBack(code) {
-  // return request2.postRequest(basePath, "User", "Login", data);
-  let data = {
-    code:code
-  }
-  return request2.postRequestWithClientInfo(basePath, "sso", "CallBack", data);
-  // return request2.postRequestWith(basePath, "User", "Login", data);
-}
-// 金赛药业
-export function getCheckiam() {
-  return request2.postRequestWithClientInfo(basePath, "sso", "GetCheckiam",{});
-}
-// 海普登录
-export function haipuLogin(token) {
-  return request({
-    url: "haipu/userinfo",
-    method: "get",
-    params: { token: token }
-  });
-}
-//获取用户详细信息
-export function getInfo(query) {
-  return request2.postRequest(
-    basePath,
-    "Permission",
-    "GetRolesAndPermissionByUser",
-    query
-  );
+// 注销
+export function signOut(data) {
+  return request.postRequestWithClientInfo(basePath, "System", "Logout", data);
 }
-
-// 退出方法
-export function logout() {
-  return request2.postRequest(basePath, "User", "LogOut");
-
-  // return request({
-  //   url: 'sys/logout',
-  //   method: 'post'
-  // })
-}
-
-// 获取验证码
-export function getCodeImg() {
-  return request2.postRequest(basePath, "Common", "GetCaptchaImg");
-
-  // return request({
-  //   url: 'common/getcaptchaimg',
-  //   method: 'post'
-  // })
-}
-
-// 登录方法
-// export function login (username, password, code, uuid) {
-//   const data = {
-//     username,
-//     password,
-//     idValueC: code,
-//     idKeyC: uuid
-//   }
-//   return request({
-//     url: 'sys/login',
-//     method: 'post',
-//     data: data
-//   })
-// }
-
-// // 获取用户详细信息
-// export function getInfo () {
-//   return request({
-//     url: 'permission/getrolesandpermissionbyuser',
-//     method: 'get'
-//   })
-// }
-
-// // 退出方法
-// export function logout () {
-//   return request({
-//     url: 'sys/logout',
-//     method: 'post'
-//   })
-// }
-
-// // 获取验证码
-// export function getCodeImg () {
-//   return request({
-//     url: 'common/getcaptchaimg',
-//     method: 'post'
-//   })
-// }
+ // 根据用户名获取用户信息
+export function getUserByUserName(data) {
+  return request.postRequestWithClientInfo(basePath, "User", "GetUserByUserName", data);
+}

BIN
src/assets/img/contact-1.png


BIN
src/assets/img/contact-2.png


BIN
src/assets/img/contact-3.png


BIN
src/assets/img/contact-4.png


BIN
src/assets/img/contact-us.png


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


+ 159 - 4
src/assets/styles/index.scss

@@ -1,3 +1,6 @@
+html, body {
+  font-size: 14px;
+}
 .flex {
   display: flex;
 }
@@ -7,6 +10,26 @@
 .flex-center {
   align-items: center;
 }
+.w100 {
+  width: 100% !important;
+}
+
+.h100 {
+  height: 100% !important;
+}
+
+.vh100 {
+  height: 100vh !important;
+}
+
+.max100vh {
+  max-height: 100vh !important;
+}
+
+.min100vh {
+  min-height: 100vh !important;
+}
+
 /* 外边距、内边距全局样式
 ------------------------------- */
 @for $i from 1 through 35 {
@@ -42,13 +65,23 @@
     padding-left: #{$i}px !important;
   }
 }
-.el-menu.el-menu--popup {
-  padding: 4px 12px;
-  border-radius: 4px;
+.custom-submenu {
+  .el-menu {
+    padding: 0;
+    border-radius: 4px;
+    min-width: 120px;
+  }
   .el-menu-item {
     font-weight: 400;
     font-size: 16px;
-    color: #323232;
+    color: #323232 !important;
+    height: 24px;
+    line-height: 24px;
+    text-align: center;
+    &:hover {
+      background-color: #ebf3ff !important;
+      color: #386afe !important;
+    }
   }
 }
 .el-card {
@@ -71,4 +104,126 @@
     width: 1200px;
     margin: 0 auto;
   }
+}
+.left-tabs {
+  width: 266px;
+  border-radius: 8px;
+  overflow: hidden;
+  background-color: #fff;
+  box-shadow: 0px 3px 6px 1px rgba(1, 64, 100, 0.16);
+  margin-right: 24px;
+  min-height: 550px;
+  display: flex;
+  flex-direction: column;
+  h4 {
+    height: 72px;
+    line-height: 72px;
+    font-weight: bold;
+    font-size: 24px;
+    color: #ffffff;
+    text-align: center;
+    background: linear-gradient(99deg, #1964fe 0%, #19abff 100%);
+  }
+  ul {
+    flex: 1;
+    li {
+      height: 60px;
+      font-weight: 400;
+      font-size: 16px;
+      color: #323232;
+      cursor: pointer;
+      transition: all 0.3s;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      &:before {
+        content: "";
+        display: inline-block;
+        width: 8px;
+        height: 8px;
+        background: transparent;
+        border-radius: 4px;
+        margin-right: 8px;
+      }
+      &:hover,
+      &.active {
+        background-color: #e7f1ff;
+        &:before {
+          background: #2C78FF;
+        }
+      }
+    }
+  }
+}
+.right-content {
+  flex: 1;
+  border-radius: 8px;
+  overflow: hidden;
+  min-height: 550px;
+  box-shadow: 0px 3px 6px 1px rgba(1, 64, 100, 0.16);
+  ::v-deep .el-card__body {
+    height: calc(100% - 100px);
+  }
+  .header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    h4 {
+      font-weight: bold;
+      font-size: 18px;
+      color: #2c405e;
+    }
+  }
+  .article {
+    h4 {
+      text-align: center;
+      font-weight: bold;
+      font-size: 24px;
+      color: #386afe;
+    }
+    .snd-title {
+      font-weight: 400;
+      font-size: 14px;
+      color: #565656;
+      display: flex;
+      justify-content: center;
+      p {
+        margin: 8px;
+      }
+    }
+  }
+  .link-list {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    overflow-y: auto;
+    > ul {
+      flex: 1;
+      li {
+        display: flex;
+        justify-content: space-between;
+        padding: 12px;
+        border-bottom: 1px dashed #ebf1f6;
+        cursor: pointer;
+        transition: all 0.3s;
+        &:hover {
+          background-color: #e7f1ff;
+        }
+        div {
+          font-weight: 400;
+          font-size: 16px;
+          color: #585858;
+        }
+        span {
+          font-weight: 400;
+          font-size: 16px;
+          color: #b5c1d8;
+        }
+      }
+    }
+    .el-pagination {
+      display: flex;
+      justify-content: center;
+    }
+  }
 }

+ 0 - 4
src/components/RightContent.vue

@@ -204,10 +204,6 @@ export default {
         }
       }
     }
-    .el-pagination {
-      display: flex;
-      justify-content: center;
-    }
   }
 }
 </style>

+ 0 - 1
src/main.js

@@ -37,7 +37,6 @@ Vue.use(VueLazyload, {
   //尝试加载几次
   attempt: 1
 });
-
 Vue.config.productionTip = false
 
 //设置超时时间

+ 39 - 4
src/router.js

@@ -1,9 +1,13 @@
 import Vue from 'vue'
 import Router from 'vue-router'
+import { getToken } from "@/utils/auth";
+import store from './store'
+import { getUserByUserName } from '@/api/login'
+import awaitTo from 'await-to-js'
 
 Vue.use(Router)
 
-let kejianrouter = new Router({
+let router = new Router({
   routes: [{
       path: '/',
       name: 'home',
@@ -17,6 +21,9 @@ let kejianrouter = new Router({
     {
       path: '/news',
       name: 'news',
+      meta: {
+        requireAuth: true
+      },
       component: () => import('./views/News.vue'),
     },
     {
@@ -54,6 +61,27 @@ let kejianrouter = new Router({
       name: 'login',
       component: () => import('./views/Login.vue')
     },
+    {
+      path: '/register',
+      name: 'register',
+      component: () => import('./views/Register.vue')
+    },
+    {
+      path: '/personal-center',
+      name: 'personal',
+      meta: {
+        requireAuth: true
+      },
+      component: () => import('./views/PersonalCenter.vue')
+    },
+    {
+      path: '/contact-us',
+      name: 'contactUs',
+      meta: {
+        requireAuth: true
+      },
+      component: () => import('./views/ContactUs.vue')
+    },
     {
       path: '/admin',
       name: 'admin',
@@ -112,11 +140,18 @@ let kejianrouter = new Router({
 })
 
 // 判断是否需要登录权限 以及是否登录
-kejianrouter.beforeEach((to, from, next) => {
+router.beforeEach(async (to, from, next) => {
+  const token = getToken()
+  // 拉取用户信息
+  if(token && !store.getters.userInfo.id) {
+    const [err, res] = await awaitTo(getUserByUserName())
+    if(err) return
+    store.dispatch('setUserInfo', res.data.userInfo)
+  }
   // 判断是否需要登录权限
   if (to.matched.some(res => res.meta.requireAuth)) {
     // 判断是否登录
-    if (sessionStorage.getItem('token')) {
+    if (token) {
       next()
     } else {
       // 没登录则跳转到登录界面
@@ -132,4 +167,4 @@ kejianrouter.beforeEach((to, from, next) => {
   }
 })
 
-export default kejianrouter
+export default router

+ 2 - 1
src/store/getters.js

@@ -1,4 +1,5 @@
 const getters = {
-    user: state => state.user
+  token: state => state.user.token,
+  userInfo: state => state.user.userInfo
 }
 export default getters

+ 46 - 103
src/store/modules/user.js

@@ -2,9 +2,9 @@
  * @Author: wanglj wanglijie@dashoo.cn
  * @Date: 2025-01-08 15:41:12
  * @LastEditors: wanglj
- * @LastEditTime: 2025-01-08 15:55:54
+ * @LastEditTime: 2025-01-10 14:01:24
  * @Description: file content
- * @FilePath: \Shkjem-master\src\store\modules\user.js
+ * @FilePath: \labsop_website\src\store\modules\user.js
  */
 /*
  * @Author: wanglj wanglijie@dashoo.cn
@@ -14,120 +14,72 @@
  * @Description: file content
  * @FilePath: \Shkjem-master\src\store\modules\user.js
  */
-import { login, logout, getInfo, callBack } from '@/api/login'
+import { signOut } from '@/api/login'
 import {
   getToken,
-  setToken,
   removeToken
 } from '@/utils/auth'
 const user = {
   state: {
     token: getToken(),
-    uuid: '',
-    name: '',
-    avatar: '',
-    realname: '',
-    dept: {},
-    roles: [],
-    permissions: []
+    userInfo: {
+      id: 0,
+      userName: "",
+      nickName: "",
+      userType: "",
+      deptId: 0,
+      email: "",
+      phone: "",
+      sex: "",
+      avatar: "",
+      status: "",
+      creditScore: 0,
+      loginIp: "",
+      loginDate: null,
+      createdTime: "",
+      deptName: "",
+      postName: ""
+    }
   },
 
   mutations: {
     SET_TOKEN: (state, token) => {
       state.token = token
     },
-    SET_UUID: (state, uuid) => {
-      state.uuid = uuid
-    },
-    SET_NAME: (state, name) => {
-      state.name = name
-    },
-    SET_AVATAR: (state, avatar) => {
-      state.avatar = avatar
-    },
-    SET_ROLES: (state, roles) => {
-      state.roles = roles
-    },
-    SET_REALNAME: (state, realname) => {
-      state.realname = realname
-    },
-    SET_DEPT: (state, dept) => {
-      state.dept = dept || {}
-    },
-    SET_PERMISSIONS: (state, permissions) => {
-      state.permissions = permissions
+    SET_USER: (state, userInfo) => {
+      state.userInfo = userInfo
     }
   },
 
   actions: {
-    // 登录
-    Login({ commit }, userInfo) {
-      const username = userInfo.username.trim()
-      const password = userInfo.encryptedPW
-      const code = userInfo.code
-      const uuid = userInfo.uuid
-      return new Promise((resolve, reject) => {
-        login(username, password, code, uuid).then(res => {
-          setToken(res.data.token)
-          commit('SET_TOKEN', res.data.token)
-          commit('SET_NAME', username)
-          commit('SET_UUID', res.data.uuid)
-          commit('SET_DEPT', res.data.dept)
-          resolve()
-        })
-          .catch(error => {
-            reject(error)
-          })
-      })
-    },
-    SsoCallBack({ commit }, userInfo) {
-      // const username = userInfo.username.trim();
-      // const password = userInfo.encryptedPW;
-      const code = userInfo.code
-      // const uuid = userInfo.uuid;
-      return new Promise((resolve, reject) => {
-        callBack(code).then(res => {
-          const username = ''
-          setToken(res.data.token)
-          commit('SET_TOKEN', res.data.token)
-          commit('SET_NAME', username)
-          commit('SET_UUID', res.data.uuid)
-          resolve()
-        })
-          .catch(error => {
-            reject(error)
-          })
-      })
-    },
-    // 获取用户信息
-    GetInfo({ commit, state }) {
-      return new Promise((resolve, reject) => {
-        getInfo().then(res => {
-          // console.info("info:", res )
-          // const user = res.data.user
-          // const avatar = user.avatar == "" ? require("@/assets/image/profile.jpg") : process.env.VUE_APP_BASE_API + "/" + user.avatar;
-          if (res.data.user_roles && res.data.user_roles.length > 0) { // 验证返回的roles是否是一个非空数组
-            commit('SET_ROLES', res.data.user_roles)
-            commit('SET_DEPT', res.data.dept)
-            commit('SET_PERMISSIONS', res.data.user_permissions)
-          } else {
-            commit('SET_ROLES', ['ROLE_DEFAULT'])
-          }
-          resolve(res)
-        })
-          .catch(error => {
-            reject(error)
-          })
-      })
+    setUserInfo({ commit }, userInfo) {
+      console.log(userInfo, 'userInfo.....');
+      commit('SET_USER', { ...userInfo })
     },
 
     // 退出系统
-    LogOut({ commit, state }) {
+    logOut({ commit, state }) {
       return new Promise((resolve, reject) => {
-        logout(state.token).then(() => {
+        signOut().then(() => {
           commit('SET_TOKEN', '')
-          commit('SET_ROLES', [])
-          commit('SET_PERMISSIONS', [])
+          commit('SET_USER', {
+            id: 0,
+            userName: "",
+            nickName: "",
+            userType: "",
+            deptId: 0,
+            email: "",
+            phone: "",
+            sex: "",
+            avatar: "",
+            status: "",
+            creditScore: 0,
+            loginIp: "",
+            loginDate: null,
+            createdTime: "",
+            deptName: "",
+            postName: ""
+          })
           removeToken()
           resolve()
         })
@@ -135,15 +87,6 @@ const user = {
             reject(error)
           })
       })
-    },
-
-    // 前端 登出
-    FedLogOut({ commit }) {
-      return new Promise(resolve => {
-        commit('SET_TOKEN', '')
-        removeToken()
-        resolve()
-      })
     }
   }
 }

+ 4 - 12
src/utils/micro_request.js

@@ -1,8 +1,8 @@
 /*
  * @Date: 2021-11-27 15:27:19
  * @LastEditors: wanglj
- * @LastEditTime: 2023-05-23 11:16:50
- * @FilePath: \biobank\src\utils\micro_request.js
+ * @LastEditTime: 2025-01-10 09:40:12
+ * @FilePath: \labsop_website\src\utils\micro_request.js
  * @Description: file content
  */
 import axios from "axios";
@@ -176,16 +176,8 @@ function processResponse(res) {
         type: "warning"
       }
     ).then(() => {
-      store.dispatch("LogOut").then(() => {
-        let callback = Cookies.get("Callback")
-        if ($GlobalConfig.SSO_LOGIN && callback === "1") {
-          sessionStorage.removeItem("SSO_Token");
-          let host = location.origin
-          Cookies.remove("Callback","1")
-          location.href = $GlobalConfig.SSO_HREF+ host+"/#/login";
-        } else {
-          location.reload();
-        }
+      store.dispatch("logOut").then(() => {
+        location.reload();
       });
     });
   } else if (code === 500) {

+ 0 - 125
src/utils/request.js

@@ -1,125 +0,0 @@
-/*
- * @Date: 2021-11-26 17:31:34
- * @LastEditors: wanglj
- * @LastEditTime: 2022-01-13 16:50:13
- * @FilePath: \biobank_frontend02\src\utils\request.js
- * @Description: file content
- */
-import axios from "axios";
-import { Notification, MessageBox, Message } from "element-ui";
-import store from "@/store";
-import { getToken, getProject } from "@/utils/auth";
-import errorCode from "@/utils/errorCode";
-
-import Cookies from "js-cookie";
-
-axios.defaults.headers["Content-Type"] = "application/json;charset=utf-8";
-
-// 创建axios实例
-const service = axios.create({
-  // axios中请求配置有baseURL选项,表示请求URL公共部分
-  baseURL: $GlobalConfig.VUE_APP_API,
-  // 超时
-  timeout: 60000
-});
-// request拦截器
-service.interceptors.request.use(
-  config => {
-    // console.log(config, "config")
-    config.headers["Tenant"] = $GlobalConfig.VUE_APP_TENANT;
-    // if (
-    //   getProject() != "undefined" &&
-    //   getProject() != undefined &&
-    //   getProject() != ""
-    // ) {
-    //   config.headers["Project"] = getProject();
-    // }
-    // 是否需要设置 token
-    const isToken = (config.headers || {}).isToken === false;
-    if (getToken() && !isToken) {
-      config.headers["Authorization"] = "Bearer " + getToken(); // 让每个请求携带自定义token 请根据实际情况自行修改
-    }
-    return config;
-  },
-  error => {
-    console.log(error);
-    Promise.reject(error);
-  }
-);
-
-// 响应拦截器
-service.interceptors.response.use(
-  res => {
-    const urls = res.config.url.split("/");
-    const url = urls[urls.length - 1];
-    if (url == "login") {
-      if (res.data.code == 500) {
-        Message({
-          message: res.data.msg,
-          type: "error",
-          duration: 3 * 1000
-        });
-        return Promise.reject(new Error(message));
-      }
-
-      Cookies.set("Realname", res.data.data.userKey);
-    }
-
-    // 未设置状态码则默认成功状态
-    const code = res.data.code || 200;
-    // debugger
-    // 获取错误信息
-    const message = errorCode[code] || res.data.msg || errorCode["default"];
-    if (code === 401) {
-      MessageBox.confirm(
-        "登录状态已过期,您可以继续留在该页面,或者重新登录",
-        "系统提示",
-        {
-          confirmButtonText: "重新登录",
-          cancelButtonText: "取消",
-          type: "warning"
-        }
-      ).then(() => {
-        store.dispatch("LogOut").then(() => {
-          let callback = Cookies.get("Callback")
-          if ($GlobalConfig.SSO_LOGIN && callback === "1") {
-            sessionStorage.removeItem("SSO_Token");
-            let host = location.origin
-            Cookies.remove("Callback","1")
-            location.href = $GlobalConfig.SSO_HREF+ host+"/#/login";
-          } else {
-            location.reload();
-          }
-        });
-      });
-    } else if (code === 500) {
-      Message({
-        message: message,
-        type: "error"
-      });
-      return Promise.reject(new Error(message));
-    } else if (code !== 200) {
-      Notification.error({
-        title: message
-      });
-      return Promise.reject("error");
-    } else {
-      let type = res.headers["content-type"];
-      if (type.indexOf("application/octet-stream") != -1) {
-        return res;
-      }
-      return res.data;
-    }
-  },
-  error => {
-    console.log("err" + error);
-    Message({
-      message: error.message,
-      type: "error",
-      duration: 5 * 1000
-    });
-    return Promise.reject(error);
-  }
-);
-
-export default service;

+ 112 - 0
src/views/ContactUs.vue

@@ -0,0 +1,112 @@
+<template>
+  <div class="news">
+    <Banner :img="require('@/assets/img/contact-us.png')" />
+    <div class="common-container">
+      <el-container>
+        <el-card>
+          <div class="flex">
+            <ul class="contact-list">
+              <li>
+                <img src="../assets/img/contact-1.png"
+                     alt="">
+                <div class="text">
+                  <p>地址</p>
+                  <span>贵州省遵义市汇川区大连路149号</span>
+                </div>
+              </li>
+              <li>
+                <img src="../assets/img/contact-2.png"
+                     alt="">
+                <div class="text">
+                  <p>电话</p>
+                  <span>(0851)-28608418</span>
+                </div>
+              </li>
+              <li>
+                <img src="../assets/img/contact-3.png"
+                     alt="">
+                <div class="text">
+                  <p>邮箱</p>
+                  <span>123456@ujs.edu.cn</span>
+                </div>
+              </li>
+              <li>
+                <img src="../assets/img/contact-4.png"
+                     alt="">
+                <div class="text">
+                  <p>办公地址</p>
+                  <span>科研楼4层402</span>
+                </div>
+              </li>
+            </ul>
+            <div id="container"></div>
+          </div>
+        </el-card>
+      </el-container>
+    </div>
+  </div>
+</template>
+
+<script>
+import Banner from "../components/Banner";
+export default {
+  name: "ContactUs",
+  components: {
+    Banner,
+  },
+  data() {
+    return {};
+  },
+  mounted() {
+    this.initMap();
+  },
+  methods: {
+    initMap() {
+      var map = new BMapGL.Map("container"); // 创建Map实例
+      map.centerAndZoom(new BMapGL.Point(106.951933, 27.713082), 18); // 初始化地图,设置中心点坐标和地图级别
+      var marker1 = new BMapGL.Marker(new BMapGL.Point(106.951933, 27.713082));
+      map.enableScrollWheelZoom(true); // 开启鼠标滚轮缩放
+      map.addOverlay(marker1);
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.el-card {
+  flex: 1;
+  #container {
+    width: 700px;
+    height: 500px;
+  }
+  .contact-list {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-around;
+    list-style: none;
+    flex: 1;
+    padding: 60px 0 60px 60px;
+    li {
+      display: flex;
+      align-items: center;
+      img {
+        height: 48px;
+        width: 48px;
+      }
+      .text {
+        margin-left: 12px;
+        p {
+          font-weight: bold;
+          font-size: 18px;
+          color: #323232;
+        }
+        span {
+          font-weight: 400;
+          font-size: 16px;
+          color: #656565;
+        }
+      }
+    }
+  }
+}
+</style>

+ 2 - 15
src/views/Home.vue

@@ -1,6 +1,5 @@
 <template>
-  <div class="home"
-       v-loading="loading">
+  <div class="home">
     <swiper id="swiperBox"
             v-bind:options="swiperOption"
             ref="mySwiper">
@@ -120,18 +119,6 @@ export default {
     },
   },
   mounted() {
-    this.$http
-      .all([
-        this.$http.get("Cases/GetCasesAll"),
-        this.$http.get(`News?type=1&num=3`),
-      ])
-      .then(
-        this.$http.spread((responseCases, responseNews) => {
-          this.caseList = responseCases.data;
-          this.newsList = responseNews.data;
-          this.loading = false;
-        })
-      );
   },
 };
 </script>
@@ -162,7 +149,7 @@ export default {
     width: 1200px;
     margin-top: -50px;
     background: #ffffff;
-    box-shadow: 0px 3px 12px 1px rgba(1, 64, 100, 0.16);
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
     border-radius: 16px;
     position: relative;
     z-index: 999;

+ 53 - 57
src/views/Login.vue

@@ -1,25 +1,32 @@
 <template>
   <div class="login">
     <el-card class="box-card">
-      <div slot="header" class="clearfix">
+      <div slot="header"
+           class="clearfix">
         <span>遵义医科大学附属医院登录入口</span>
       </div>
       <div>
-        <el-form
-          class="demo-ruleForm"
-          ref="lform"
-          :model="loginform"
-          :rules="rules"
-          label-width="80px"
-        >
-          <el-form-item label="用户名" prop="name">
-            <el-input name="name" v-model="loginform.name"></el-input>
+        <el-form class="demo-ruleForm"
+                 ref="lform"
+                 :model="loginform"
+                 :rules="rules"
+                 label-width="80px">
+          <el-form-item label="用户名"
+                        prop="username">
+            <el-input name="username"
+                      v-model="loginform.username"></el-input>
           </el-form-item>
-          <el-form-item label="密码" prop="pass">
-            <el-input name="password" type="password" v-model="loginform.pass" autocomplete="off"></el-input>
+          <el-form-item label="密码"
+                        prop="password">
+            <el-input name="password"
+                      type="password"
+                      v-model="loginform.password"
+                      autocomplete="off"></el-input>
           </el-form-item>
           <el-form-item>
-            <el-button type="primary" @click="login">登录</el-button>
+            <el-button type="primary"
+                       :loading="loading"
+                       @click="login">登录</el-button>
             <el-button type="text">
               <router-link to="/">回到首页</router-link>
             </el-button>
@@ -31,68 +38,55 @@
 </template>
 
 <script>
+import crypto from "sm-crypto";
+import { setToken } from "@/utils/auth";
+import { signIn } from "@/api/login";
+import to from 'await-to-js'
 export default {
   data() {
     return {
       labelPosition: "right",
       loginform: {
-        name: "",
-        pass: ""
+        username: "",
+        password: "",
       },
       rules: {
-        name: [{ required: true, message: "请输入用户名", trigger: "blur" }],
-        pass: [
+        username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
+        password: [
           { required: true, message: "请输入密码", trigger: "blur" },
           {
             min: 5,
             max: 20,
             message: "密码长度在 5 到 20 个字符",
-            trigger: "blur"
-          }
-        ]
-      }
+            trigger: "blur",
+          },
+        ],
+      },
+      loading: false,
     };
   },
   methods: {
     login() {
-      this.$refs.lform.validate(valid => {
+      this.$refs.lform.validate(async (valid) => {
         if (valid) {
-          this.$http
-            .get(
-              `User/Login?strUser=${this.loginform.name}&strPwd=${this.loginform.pass}`
-            )
-            .then(response => {
-              window.console.log(response);
-              if (response.data.bRes) {
-                this.$message({
-                  message: "登录成功了呢",
-                  type: "success"
-                });
-                sessionStorage.setItem("token", response.data.Ticket);
-                this.$router.push({ name: "admin" });
-              } else {
-                this.$message({
-                  message: "账号或密码错误",
-                  type: "error"
-                });
-              }
-            })
-            .catch(e => {
-              this.$message({
-                message: "网络或程序异常!" + e,
-                type: "error"
-              });
-            });
-        } else {
-          this.$message({
-            message: "请输入合法的值",
-            type: "error"
-          });
-          return false;
+          const params = JSON.parse(JSON.stringify(this.loginform));
+          params.password = crypto.sm3(params.password);
+          const [err, res] = await to(signIn(params));
+          if (err) {
+            this.loading = false;
+            return;
+          }
+          this.$message.success("登录成功");
+          setToken(res.data.token);
+          if(this.$route.query.redirect) {
+            this.$router.push(this.$route.query.redirect);
+          } else {
+            this.$router.push("/");
+          }
         }
       });
-    }
-  }
+    },
+  },
 };
 </script>
 
@@ -104,10 +98,12 @@ a {
 
 .login {
   width: 100%;
-  height: calc(100vh - 148px);
+  height: calc(100vh - 116px);
   display: flex;
   align-items: center;
   justify-content: center;
+  background: url('../assets/img/login-bg.png') center no-repeat;
+  background-size: 100% 100%;
 }
 
 .text {

+ 436 - 0
src/views/PersonalCenter.vue

@@ -0,0 +1,436 @@
+<template>
+  <div class="news">
+    <Banner :img="require('@/assets/img/news.png')" />
+    <div class="common-container">
+      <el-container>
+        <div class="left-tabs">
+          <h4>个人中心</h4>
+          <ul>
+            <li v-for="(item, index) in options"
+                :key="index"
+                :class="{ active: index === active }"
+                @click="tabSelect(item, index)">
+              {{ item.label }}
+            </li>
+          </ul>
+        </div>
+        <div class="w100">
+          <el-row class="flex mt12"
+                  :gutter="12">
+            <el-col :span="12">
+              <el-card header="科研仪器">
+
+              </el-card>
+            </el-col>
+            <el-col :span="12">
+              <el-card header="通知公告">
+
+              </el-card>
+            </el-col>
+          </el-row>
+          <el-row class="flex mt12"
+                  :gutter="12">
+            <el-col :span="12">
+              <el-card header="科研平台">
+                <ul class="platform-list">
+                  <li>
+                    <div class="text">
+                      <header>
+                        <p>分子生物平台</p>
+                        <span>单价:¥100/小时</span>
+                      </header>
+                      <p>位置:7栋6层-分子生物平台中心-12号终端</p>
+                      <p>有效时间:1月1日  12:00 -- 1月15日 12:00</p>
+                    </div>
+                    <div class="btn">
+                      <header>
+                        <p>剩余时长</p>
+                        <p>2天18小时</p>
+                      </header>
+                      小计:¥300
+                    </div>
+                  </li>
+                  <li>
+                    <div class="text">
+                      <header>
+                        <p>分子生物平台</p>
+                        <span>单价:¥100/小时</span>
+                      </header>
+                      <p>位置:7栋6层-分子生物平台中心-12号终端</p>
+                      <p>有效时间:1月1日  12:00 -- 1月15日 12:00</p>
+                    </div>
+                    <div class="btn">
+                      <header>
+                        <p>剩余时长</p>
+                        <p>2天18小时</p>
+                      </header>
+                      小计:¥300
+                    </div>
+                  </li>
+                </ul>
+              </el-card>
+              <el-card header="笼位管理(共3个)"
+                       class="mt12">
+                <ul class="cage-list">
+                  <li>
+                    <header>
+                      <p>实验用小鼠-3只</p>
+                      <el-tag type="primary"
+                              size="mini">正常</el-tag>
+                    </header>
+                    <p>位置:7栋6层-602室 4区12号架 8号笼</p>
+                    <p>到期时间:2月1日(剩余10天)</p>
+                  </li>
+                  <li>
+                    <header>
+                      <p>实验用小鼠-3只-笼位申请</p>
+                      <el-tag type="warning"
+                              size="mini">待分配</el-tag>
+                    </header>
+                    <p>位置:待分配</p>
+                    <p>申请时间:2月1日</p>
+                  </li>
+                  <li>
+                    <header>
+                      <p>实验用小鼠-3只</p>
+                      <el-tag type="primary"
+                              size="mini">正常</el-tag>
+                    </header>
+                    <p>位置:7栋6层-602室 4区12号架 8号笼</p>
+                    <p>到期时间:2月1日(剩余10天)</p>
+                  </li>
+                </ul>
+              </el-card>
+            </el-col>
+            <el-col :span="12">
+              <el-card header="日程安排"
+                       class="calendar">
+                <FullCalendar class="fullCalendar"
+                              ref="fullCalendar" :options="calendarOptions" />
+                <ul>
+                  <li>仪器:需要进行第二期临床实验使用仪器</li>
+                  <li>平台:需要使用平台查找相应的案例</li>
+                  <li>笼位:进行第二次动物实验</li>
+                  <li>技术:约李老师进行第三次技术咨询</li>
+                </ul>
+              </el-card>
+              <el-card header="费用统计"
+                       class="mt12 cost">
+                <div class="chart-container">
+                  <div class="text">
+                    <header>课题组:人血球免疫蛋白应用研究课题组</header>
+                    <ul>
+                      <li>费用账单:6条待确认</li>
+                      <li>本月支出:¥567.89</li>
+                      <li>累计支出:¥1234.56</li>
+                    </ul>
+                  </div>
+                  <div id="chart"></div>
+                </div>
+              </el-card>
+            </el-col>
+          </el-row>
+          <el-row class="flex mt12"
+                  :gutter="12">
+            <el-col :span="24">
+              <el-card header="日程安排">
+
+              </el-card>
+            </el-col>
+          </el-row>
+        </div>
+      </el-container>
+    </div>
+  </div>
+</template>
+
+<script>
+import Banner from "../components/Banner";
+import LeftTabs from "@/components/LeftTabs";
+import RightContent from "@/components/RightContent";
+import * as echarts from "echarts";
+import FullCalendar from '@fullcalendar/vue'
+import dayGridPlugin from '@fullcalendar/daygrid'
+import interactionPlugin from '@fullcalendar/interaction'
+export default {
+  name: "PersonalCenter",
+  components: {
+    Banner,
+    LeftTabs,
+    RightContent,
+    FullCalendar,
+  },
+  data() {
+    return {
+      active: -1,
+      loading: true,
+      selectTab: {},
+      options: [
+        {
+          label: "工作台",
+        },
+        {
+          label: "预约信息",
+        },
+        {
+          label: "通知模块",
+        },
+        {
+          label: "财务中心",
+        },
+      ],
+      routeList: [
+        {
+          name: "首页",
+          path: "/",
+        },
+        {
+          name: "个人中心",
+        },
+      ],
+      breadList: [],
+      calendarOptions: {
+        height: 300,
+        plugins: [dayGridPlugin, interactionPlugin], // 需要用哪个插件引入后放到这个数组里
+        initialDate: new Date(), // 日历第一次加载时显示的初始日期。可以解析为Date的任何职包括ISO8601日期字符串,例如"2014-02-01"。
+        initialView: "dayGridMonth", // 日历加载时的初始视图,默认值为'dayGridMonth',可以为任何可用视图的值,如例如'dayGridWeek','timeGridDay','listWeek'
+        locale: "zh-cn", // 设置日历的语言,中文为 “zh-cn”
+        firstDay: "1", // 设置每周的第一天,默认值取决于当前语言环境,该值为代表星期几的数字,且值必须为整数,星期日=0
+        weekNumberCalculation: "ISO", // 指定"ISO"结果为ISO8601周数。指定"ISO"将firstDay的默认值更改为1(Monday)
+        buttonText: {
+          // 文本将显示在headerToolbar / footerToolbar的按钮上。不支持HTML注入。所有特殊字符将被转义。
+          today: "今天",
+          month: "月",
+          week: "周",
+          day: "天",
+        },
+        headerToolbar: {
+          // 在日历顶部定义按钮和标题。将headerToolbar选项设置为false不会显示任何标题工具栏。可以为对象提供属性start/center/end或left/center/right。这些属性包含带有逗号/空格分隔值的字符串。用逗号分隔的值将相邻显示。用空格分隔的值之间会显示一个很小的间隙。
+          right: "today prev,next",
+          center: "title",
+          left: "dayGridMonth,dayGridWeek,dayGridDay",
+        },
+        eventTimeFormat: {
+          // 在每个事件上显示的时间的格式
+          hour: "numeric",
+          minute: "2-digit",
+          hour12: false,
+        },
+        events: [],
+        dateClick: this.handleDateClick, // 当用户单击日期或时间时,触发该回调,触发此回调,您必须加载interaction插件
+      },
+    };
+  },
+  mounted() {
+    this.tabSelect(this.options[0], 0);
+    this.initChart();
+  },
+  methods: {
+    // 选择tab
+    tabSelect(row, index) {
+      this.active = index;
+      this.selectTab = { ...row };
+      this.breadList = [...this.routeList, { name: this.selectTab.label }];
+    },
+    initChart() {
+      let chart = echarts.init(document.getElementById("chart"));
+      const options = {
+        series: [
+          {
+            type: "gauge",
+            startAngle: 180,
+            endAngle: 0,
+            center: ["50%", "75%"],
+            radius: "90%",
+            min: 0,
+            max: 1,
+            splitNumber: 8,
+            axisLine: {
+              lineStyle: {
+                width: 6,
+                color: [
+                  [0.25, "#FF6E76"],
+                  [0.5, "#FDDD60"],
+                  [0.75, "#58D9F9"],
+                  [1, "#7CFFB2"],
+                ],
+              },
+            },
+            pointer: {
+              icon: "path://M12.8,0.7l12,40.1H0.7L12.8,0.7z",
+              length: "12%",
+              width: 20,
+              offsetCenter: [0, "-60%"],
+              itemStyle: {
+                color: "auto",
+              },
+            },
+            axisTick: {
+              length: 12,
+              lineStyle: {
+                color: "auto",
+                width: 2,
+              },
+            },
+            splitLine: {
+              length: 20,
+              lineStyle: {
+                color: "auto",
+                width: 5,
+              },
+            },
+            axisLabel: {
+              color: "#464646",
+              fontSize: 14,
+              distance: -40,
+              rotate: "tangential",
+              formatter: function (value) {
+                if (value === 0.875) {
+                  return "充足";
+                } else if (value === 0.625) {
+                  return "可控";
+                } else if (value === 0.375) {
+                  return "紧张";
+                } else if (value === 0.125) {
+                  return "危险";
+                }
+                return "";
+              },
+            },
+            title: {
+              offsetCenter: [0, "-10%"],
+              fontSize: 14,
+            },
+            detail: {
+              fontSize: 20,
+              offsetCenter: [0, "-35%"],
+              valueAnimation: true,
+              formatter: function (value) {
+                return Math.round(value * 100) + "";
+              },
+              color: "inherit",
+            },
+            data: [
+              {
+                value: 0.7,
+                name: "危险预算余额",
+              },
+            ],
+          },
+        ],
+      };
+      chart.setOption(options);
+    },
+    handleDateClick(dateClickInfo) {
+      console.log(dateClickInfo)
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.flex {
+  width: 100%;
+  flex-wrap: wrap;
+  .el-card {
+    flex: 0 0 calc(50% - 14px);
+    height: 400px;
+    ::v-deep .el-card__body {
+      padding: 10px;
+      height: calc(100% - 79px);
+    }
+    &.calendar {
+      height: 500px;
+      ul {
+        display: flex;
+        flex-direction: column;
+        justify-content: space-around;
+        height: 130px;
+        li {
+          font-size: 14px;
+          display: flex;
+          align-items: center;
+          &:before {
+            content: '';
+            display: inline-block;
+            width: 6px;
+            height: 6px;
+            border-radius: 3px;
+            background-color: #a30014;
+            margin-right: 4px;
+          }
+        }
+      }
+    }
+    &.cost {
+      height: 300px;
+    }
+  }
+}
+.cage-list {
+  display: flex;
+  flex-wrap: wrap;
+  list-style: none;
+  li {
+    height: 138px;
+    width: 198px;
+    font-size: 14px;
+    border: 1px solid #ebeef5;
+    border-radius: 6px;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-around;
+    padding: 10px;
+    &:nth-child(2n) {
+      margin-left: 4px;
+    }
+    &:nth-child(n + 3) {
+      margin-top: 4px;
+    }
+    header {
+      display: flex;
+      justify-content: space-between;
+      p {
+        color: #1d66dc;
+      }
+    }
+  }
+}
+.platform-list {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  li {
+    flex: 1;
+    display: flex;
+  }
+}
+.chart-container {
+  display: flex;
+  .text {
+    flex: 1;
+    font-size: 14px;
+    display: flex;
+    flex-direction: column;
+    header {
+      color: #1d66dc;
+    }
+    ul {
+      margin-top: 12px;
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+      justify-content: space-around;
+    }
+    li {
+      background-color: #e4b5bb;
+      border-radius: 4px;
+      padding: 4px 8px;
+    }
+  }
+  #chart {
+    width: 233px;
+    height: 220px;
+  }
+}
+</style>

+ 355 - 0
src/views/Register.vue

@@ -0,0 +1,355 @@
+<template>
+  <div class="login">
+    <el-card class="box-card">
+      <div slot="header"
+           class="clearfix">
+        <span>注册</span>
+      </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>
+        </ul>
+        <el-steps :space="200"
+                  :active="active"
+                  finish-status="success"
+                  align-center
+                  class="mb20">
+          <el-step title="登录信息"></el-step>
+          <el-step title="个人信息"></el-step>
+          <el-step title="其它信息"></el-step>
+        </el-steps>
+        <el-form ref="loginInfoRef"
+                 size="mini"
+                 v-show="active == 0"
+                 :model="form"
+                 :rules="rules"
+                 label-width="80px">
+          <el-form-item label="登录账号"
+                        prop="username">
+            <el-input name="username"
+                      placeholder="请输入登录账号"
+                      v-model="form.username"></el-input>
+          </el-form-item>
+          <el-form-item label="密码"
+                        prop="password">
+            <el-input name="password"
+                      type="password"
+                      placeholder="请输入密码"
+                      v-model="form.password"
+                      autocomplete="off"></el-input>
+          </el-form-item>
+          <el-form-item label="确认密码"
+                        prop="confirmPassword">
+            <el-input name="confirmPassword"
+                      type="password"
+                      placeholder="请输入密码"
+                      v-model="form.confirmPassword"
+                      autocomplete="off"></el-input>
+          </el-form-item>
+        </el-form>
+        <el-form ref="personInfoRef"
+                 size="mini"
+                 v-show="active == 1"
+                 :model="form"
+                 :rules="rules"
+                 label-width="80px">
+          <el-form-item label="姓名"
+                        prop="name">
+            <el-input placeholder="请输入姓名"
+                      v-model="form.name"></el-input>
+          </el-form-item>
+          <el-form-item label="性别"
+                        prop="sex">
+            <el-radio-group placeholder="请"
+                            v-model="form.sex">
+              <el-radio v-for="item in userSexList"
+                        :key="item.dictValue"
+                        :label="item.dictValue">{{ item.dictLabel }}</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="用户类型"
+                        prop="userType">
+            <el-radio-group v-model="form.userType"
+                            placeholder="请选择用户类型"
+                            style="width: 100%"
+                            clearable>
+              <el-radio v-for="item in userTypeList"
+                        :key="item.id"
+                        :label="item.dictValue">{{ item.dictLabel }}</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="组织部门"
+                        prop="deptId">
+            <el-cascader ref="casc"
+                         :options="deptData"
+                         @change="deptChange"
+                         :props="{ checkStrictly: true, emitPath: false, value: 'id', label: 'deptName' }"
+                         placeholder="请选择组织部门"
+                         clearable
+                         class="w100"
+                         v-model="form.deptId">
+              <template #default="{ node, data }">
+                <span>{{ data.deptName }}</span>
+                <span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
+              </template>
+            </el-cascader>
+          </el-form-item>
+          <el-form-item label="证件号"
+                        prop="idCode">
+            <el-input placeholder="请输入证件号"
+                      v-model="form.idCode"
+                      class="w100 input-with-select">
+              <el-select slot="prepend"
+                         v-model="form.idType"
+                         placeholder="请选择证件类型">
+                <el-option v-for="item in userCertList"
+                           :key="item.dictValue"
+                           :label="item.dictLabel"
+                           :value="item.dictValue" />
+              </el-select>
+            </el-input>
+          </el-form-item>
+          <el-form-item label="课题组"
+                        prop="projectId">
+            <el-select v-model="form.projectId"
+                       placeholder="请选择课题组"
+                       class="w100">
+              <el-option v-for="item in pjtList"
+                         :key="item.id"
+                         :label="item.pgName"
+                         :value="item"></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="所在时间"
+                        prop="projectId">
+            <el-date-picker v-model="form.projectDate"
+                            type="daterange"
+                            class="w100"
+                            value-format="yyyy-MM-dd"
+                            range-separator="至"
+                            start-placeholder="开始日期"
+                            end-placeholder="结束日期" />
+          </el-form-item>
+        </el-form>
+        <div class="buttons">
+          <el-button size="mini"
+                     v-if="active > 0"
+                     @click="nextStep">上一步</el-button>
+          <el-button size="mini"
+                     type="primary"
+                     @click="nextStep">下一步</el-button>
+        </div>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import crypto from "sm-crypto";
+import { setToken } from "@/utils/auth";
+import { signIn } from "@/api/login";
+import to from "await-to-js";
+import {
+  getDictDataByType,
+  getDeptTree,
+  getProjectGroupList,
+} from "@/api/dict";
+export default {
+  data() {
+    const checkPassword = (rule, value, callback) => {
+      if (value === "") {
+        callback(new Error("请再次输入密码"));
+      } else if (value !== this.form.password) {
+        callback(new Error("两次输入密码不一致!"));
+      } else {
+        callback();
+      }
+    };
+    return {
+      active: 0,
+      type: "person",
+      labelPosition: "right",
+      form: {
+        id: 0,
+        userName: "", // 账户名称
+        nickName: "", // 用户姓名
+        userType: "10", // 关联角色
+        deptId: null,
+        deptName: "", // 单位名称
+        postIds: [],
+        roleIds: [],
+        groupIds: [],
+        phone: "", // 手机号
+        email: "", // 邮箱
+        sex: "", // 性别
+        password: "", // 账户密码
+        confirmPassword: "",
+        status: "10", // 用户状态
+        describe: "", // 用户描述
+        avatar: "",
+        idType: "10", //证件类型
+        idCode: "", // 证件号
+        personnelType: "", // 人员类型
+        projectId: null,
+        projectName: "",
+        projectDate: [],
+      },
+      rules: {
+        username: [
+          { required: true, message: "请输入用户名", trigger: "blur" },
+        ],
+        password: [
+          { required: true, message: "请输入密码", trigger: "blur" },
+          {
+            min: 5,
+            max: 20,
+            message: "密码长度在 5 到 20 个字符",
+            trigger: "blur",
+          },
+        ],
+        confirmPassword: [
+          { required: true, validator: checkPassword, trigger: "blur" },
+        ],
+      },
+      loading: false,
+      userTypeList: [],
+      userSexList: [],
+      userCertList: [],
+      deptData: [],
+      projectList: [],
+    };
+  },
+  mounted() {
+    this.getDicts();
+  },
+  methods: {
+    getDicts() {
+      Promise.all([
+        getDictDataByType("sys_user_type"),
+        getDictDataByType("sys_com_sex"),
+        getDictDataByType("sys_user_certificate"),
+        getDeptTree(),
+        // 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 || [];
+      });
+    },
+    changeType(val) {
+      this.type = val;
+    },
+    // 部门选择
+    deptChange() {
+      if (this.form.userType === "20") return;
+      const nodes = this.$refs.casc.getCheckedNodes();
+      this.form.deptName = nodes[0].label;
+    },
+    nextStep() {
+      if (this.active < 3) {
+        let form = "loginInfoRef";
+        if (this.active == 1) {
+          form = "personInfoRef";
+        }
+        this.$refs[form].validate(async (valid) => {
+          if (valid) {
+            this.active++;
+          }
+        });
+      }
+    },
+    login() {
+      if (this.active < 3) {
+        this.active++;
+      } else {
+      }
+      this.$refs.lform.validate(async (valid) => {});
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+a {
+  text-decoration: none;
+  color: #409eff;
+}
+
+.login {
+  width: 100%;
+  height: calc(100vh - 116px);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: url('../assets/img/login-bg.png') center no-repeat;
+  background-size: 100% 100%;
+}
+
+.text {
+  font-size: 14px;
+}
+
+.item {
+  margin-bottom: 18px;
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: "";
+}
+.clearfix:after {
+  clear: both;
+}
+
+.box-card {
+  width: 500px;
+  margin: 0 auto;
+}
+.el-form-item__content {
+  text-align: start;
+}
+.choose-type {
+  display: flex;
+  list-style: none;
+  margin-bottom: 12px;
+  li {
+    height: 80px;
+    width: 270px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    border-radius: 8px;
+    border: 1px solid #ebeef5;
+    cursor: pointer;
+    transition: all 0.3s;
+    color: #8c8888;
+    & + li {
+      margin-left: 12px;
+    }
+    &:hover,
+    &.active {
+      border-color: #409eff;
+      color: #409eff;
+    }
+    i {
+      font-size: 30px;
+      margin-bottom: 12px;
+    }
+  }
+}
+.buttons {
+  display: flex;
+  justify-content: flex-end;
+}
+::v-deep .input-with-select .el-input-group__prepend {
+  background-color: #fff;
+  width: 40%;
+}
+</style>

+ 62 - 1
yarn.lock

@@ -652,6 +652,28 @@
     "@babel/helper-string-parser" "^7.25.9"
     "@babel/helper-validator-identifier" "^7.25.9"
 
+"@fullcalendar/core@^6.1.15":
+  version "6.1.15"
+  resolved "https://registry.npmmirror.com/@fullcalendar/core/-/core-6.1.15.tgz#6c3f5259fc4589870228853072131219bb533f6e"
+  integrity sha512-BuX7o6ALpLb84cMw1FCB9/cSgF4JbVO894cjJZ6kP74jzbUZNjtwffwRdA+Id8rrLjT30d/7TrkW90k4zbXB5Q==
+  dependencies:
+    preact "~10.12.1"
+
+"@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==
+
+"@fullcalendar/interaction@^6.1.15":
+  version "6.1.15"
+  resolved "https://registry.npmmirror.com/@fullcalendar/interaction/-/interaction-6.1.15.tgz#1c685d5c269388d4877b75ab2185e97d7c386cc7"
+  integrity sha512-DOTSkofizM7QItjgu7W68TvKKvN9PSEEvDJceyMbQDvlXHa7pm/WAVtAc6xSDZ9xmB1QramYoWGLHkCYbTW1rQ==
+
+"@fullcalendar/vue@^6.1.15":
+  version "6.1.15"
+  resolved "https://registry.npmmirror.com/@fullcalendar/vue/-/vue-6.1.15.tgz#3f8069f63769ebb0eb0fa9f3696f226223965781"
+  integrity sha512-ptTyhJMwY0kgUQSQtR7Nh8AMdUOdEYUmQu7aU604m776glDh80U6bfm/xLdZKG7EmlAbFAtXnymOUSSxnsGhEQ==
+
 "@hapi/address@2.x.x":
   version "2.1.4"
   resolved "https://registry.npmmirror.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
@@ -3331,6 +3353,14 @@ ecc-jsbn@~0.1.1:
     jsbn "~0.1.0"
     safer-buffer "^2.1.0"
 
+echarts@^5.6.0:
+  version "5.6.0"
+  resolved "https://registry.npmmirror.com/echarts/-/echarts-5.6.0.tgz#2377874dca9fb50f104051c3553544752da3c9d6"
+  integrity sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==
+  dependencies:
+    tslib "2.3.0"
+    zrender "5.6.1"
+
 ee-first@1.1.1:
   version "1.1.1"
   resolved "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -3546,6 +3576,11 @@ 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"
@@ -7193,6 +7228,11 @@ postcss@^8.4.14:
     picocolors "^1.1.1"
     source-map-js "^1.2.1"
 
+preact@~10.12.1:
+  version "10.12.1"
+  resolved "https://registry.npmmirror.com/preact/-/preact-10.12.1.tgz#8f9cb5442f560e532729b7d23d42fd1161354a21"
+  integrity sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==
+
 prelude-ls@~1.1.2:
   version "1.1.2"
   resolved "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@@ -8797,6 +8837,11 @@ tryer@^1.0.1:
   resolved "https://registry.npmmirror.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
   integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==
 
+tslib@2.3.0:
+  version "2.3.0"
+  resolved "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
+  integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
+
 tslib@^1.9.0:
   version "1.14.1"
   resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
@@ -9290,6 +9335,15 @@ 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"
@@ -9348,7 +9402,7 @@ vue-video-player@^5.0.2:
     videojs-flash "^2.1.0"
     videojs-hotkeys "^0.2.20"
 
-vue@^2.6.10:
+vue@^2.1.8, vue@^2.6.10:
   version "2.7.16"
   resolved "https://registry.npmmirror.com/vue/-/vue-2.7.16.tgz#98c60de9def99c0e3da8dae59b304ead43b967c9"
   integrity sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==
@@ -9754,3 +9808,10 @@ yorkie@^2.0.0:
     is-ci "^1.0.10"
     normalize-path "^1.0.0"
     strip-indent "^2.0.0"
+
+zrender@5.6.1:
+  version "5.6.1"
+  resolved "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz#e08d57ecf4acac708c4fcb7481eb201df7f10a6b"
+  integrity sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==
+  dependencies:
+    tslib "2.3.0"