From e06044d2db5e57dade2aec6a2ac398129f61d0f2 Mon Sep 17 00:00:00 2001
From: zhongrj <646384940@qq.com>
Date: Mon, 01 Jun 2026 19:00:15 +0800
Subject: [PATCH] feat: 增加登录页面,注释测试监控的播放

---
 src/router/page/index.js      |   22 
 src/config/website.js         |   43 ++
 src/permission.js             |   61 +-
 src/store/modules/user.js     |  274 ++++++++++++++-
 vue.config.js                 |   76 ----
 src/api/user.js               |   81 ++++
 src/views/login/index.vue     |   58 +++
 src/router/axios.js           |   72 ++-
 src/styles/login.scss         |  157 +++++++++
 src/utils/tool.js             |   39 +-
 src/views/login/userlogin.vue |   82 ++++
 src/views/home/index.vue      |    2 
 src/store/getters.js          |   19 
 src/styles/index.scss         |    1 
 14 files changed, 784 insertions(+), 203 deletions(-)

diff --git a/src/api/user.js b/src/api/user.js
new file mode 100644
index 0000000..f9ae61f
--- /dev/null
+++ b/src/api/user.js
@@ -0,0 +1,81 @@
+import request from '@/router/axios';
+import website from "@/config/website";
+
+export const loginByUsername = (tenantId, username, password, type, key, code) => request({
+  url: '/api/blade-auth/oauth/token',
+  method: 'post',
+  headers: {
+    'Tenant-Id': tenantId,
+    'Captcha-Key': key,
+    'Captcha-Code': code,
+  },
+  params: {
+    tenantId,
+    username,
+    password,
+    grant_type: (website.captchaMode ? "captcha" : "password"),
+    scope: "all",
+    type
+  }
+});
+
+export const loginBySocial = (tenantId, source, code, state) => request({
+  url: '/api/blade-auth/oauth/token',
+  method: 'post',
+  headers: {
+    'Tenant-Id': tenantId
+  },
+  params: {
+    tenantId,
+    source,
+    code,
+    state,
+    grant_type: "social",
+    scope: "all",
+  }
+})
+
+export const refreshToken = (refresh_token, tenantId) => request({
+  url: '/api/blade-auth/oauth/token',
+  method: 'post',
+  headers: {
+    'Tenant-Id': tenantId
+  },
+  params: {
+    tenantId,
+    refresh_token,
+    grant_type: "refresh_token",
+    scope: "all",
+  }
+});
+
+export const getButtons = () => request({
+  url: '/api/blade-system/menu/buttons',
+  method: 'get'
+});
+
+export const getCaptcha = () => request({
+  url: '/api/blade-auth/oauth/captcha',
+  method: 'get'
+});
+
+export const logout = () => request({
+  url: '/api/blade-auth/oauth/logout',
+  method: 'get'
+});
+
+export const getUserInfo = () => request({
+  url: '/api/blade-auth/oauth/user-info',
+  method: 'get'
+});
+
+export const sendLogs = (list) => request({
+  url: '/api/blade-auth/oauth/logout',
+  method: 'post',
+  data: list
+});
+
+export const clearCache = () => request({
+  url: '/api/blade-auth/oauth/clear-cache',
+  method: 'get'
+});
\ No newline at end of file
diff --git a/src/config/website.js b/src/config/website.js
new file mode 100644
index 0000000..4e011b5
--- /dev/null
+++ b/src/config/website.js
@@ -0,0 +1,43 @@
+/**
+ * 全局配置文件
+ */
+
+var fistPage = {
+    label: "首页",
+    value: "/layout/home",
+    params: {},
+    query: {},
+    meta: {
+        i18n: 'dashboard'
+    },
+    group: [],
+    close: false
+}
+
+export default {
+    title: "保安员考试大屏",
+    logo: "S",
+    key: 'saber',
+    indexTitle: '保安员考试大屏',
+    clientId: 'saber',
+    clientSecret: 'saber_secret',
+    tenantMode: true,
+    tenantId: "000000",
+    captchaMode: false,
+    lockPage: '/lock',
+    tokenTime: 3000,
+    tokenHeader: 'Blade-Auth',
+    statusWhiteList: [],
+    isFirstPage: false,
+    fistPage: fistPage,
+    menu: {
+        iconDefault: 'iconfont icon-caidan',
+        props: {
+            label: 'name',
+            path: 'path',
+            icon: 'source',
+            children: 'children'
+        }
+    },
+    authUrl: 'http://localhost/blade-auth/oauth/render',
+}
diff --git a/src/permission.js b/src/permission.js
index a696a51..8d8cd90 100644
--- a/src/permission.js
+++ b/src/permission.js
@@ -1,40 +1,35 @@
-/*
- * @Author: shuishen 1109946754@qq.com
- * @Date: 2022-08-18 16:16:10
- * @LastEditors: shuishen 1109946754@qq.com
- * @LastEditTime: 2023-05-04 11:09:35
- * @FilePath: \web\bigScreen\src\permission.js
- * @Description: 路由守卫
- *
- * Copyright (c) 2022 by shuishen 1109946754@qq.com, All Rights Reserved.
- */
 import router from "@/router/page/index";
+import store from './store'
 import { getToken } from "./utils/auth";
+import NProgress from 'nprogress'
+import 'nprogress/nprogress.css'
+
+NProgress.configure({showSpinner: false});
 
 router.beforeEach((to, from, next) => {
-  // window._axiosPromiseArr && window._axiosPromiseArr.length && window._axiosPromiseArr.forEach((ele, index) => {
-  //     ele.cancel()
-  //     delete window._axiosPromiseArr[index]
-  //     console.clear()
-  // })
-
   const meta = to.meta || {};
-  next();
 
-  // if (getToken()) {
-  //     if (to.path === '/login') { // 如果登录成功访问登录页跳转到主页
-  //         next({
-  //             path: '/'
-  //         })
-  //     } else {
-  //         next()
-  //     }
-  // } else {
-  //     // 判断是否需要认证,没有登录访问去登录页
-  //     if (meta.isAuth === false) {
-  //         next()
-  //     } else {
-  //         window.open('https://sk.hubeishuiyi.cn/business/', "_self")
-  //     }
-  // }
+  if (getToken()) {
+    if (to.path === '/login') {
+      next({ path: '/layout/home' })
+    } else {
+      if (store.getters.token.length === 0) {
+        store.dispatch('FedLogOut').then(() => {
+          next({ path: '/login' })
+        })
+      } else {
+        next()
+      }
+    }
+  } else {
+    if (meta.isAuth === false) {
+      next()
+    } else {
+      next('/login')
+    }
+  }
 });
+
+router.afterEach(() => {
+  NProgress.done();
+});
\ No newline at end of file
diff --git a/src/router/axios.js b/src/router/axios.js
index 9314419..d57ab12 100644
--- a/src/router/axios.js
+++ b/src/router/axios.js
@@ -1,40 +1,42 @@
-/*
- * @Author: shuishen 1109946754@qq.com
- * @Date: 2022-07-29 15:19:13
- * @LastEditors: shuishen 1109946754@qq.com
- * @LastEditTime: 2023-04-21 10:16:58
- * @FilePath: \web\bigScreen\src\router\axios.js
- * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
- */
-/**
- * 全站http配置
- *
- * axios参数说明
- * isSerialize是否开启form表单提交
- * isToken是否需要token
- */
 import axios from "axios";
+import store from '@/store/';
+import router from '@/router/page';
+import { getToken } from '@/utils/auth';
+import { MessageBox } from 'element-ui';
+import website from '@/config/website';
+import { Base64 } from 'js-base64';
+import NProgress from 'nprogress';
+import 'nprogress/nprogress.css';
 
-// 调用后台管理的
 const service = axios.create({
-  timeout: 600000, // request timeout
+  timeout: 600000,
 });
 
-// 返回其他状态码
 service.defaults.validateStatus = function (status) {
   return status >= 200 && status <= 500;
 };
 
-// 跨域请求,允许保存cookie
-// service.defaults.withCredentials = true
+service.defaults.withCredentials = true;
 
-// http request拦截
+NProgress.configure({
+  showSpinner: false
+});
+
 service.interceptors.request.use(
   (config) => {
-    if (config.apiKey) {
-      config.headers["apikey"] =
-        "F1DBECD719108635189480CF60E6553ADB3109616426BD537F25A430DFC613B491A025C4A51E77FD08C6E5B7CBE05917A461286E7B6D69F1AB1B14F946149D2065B0C675F8FEDF4B9B05C1496881BC5A";
+    NProgress.start();
+    const meta = (config.meta || {});
+    const isToken = meta.isToken === false;
+    config.headers['Authorization'] = `Basic ${Base64.encode(`${website.clientId}:${website.clientSecret}`)}`;
+    
+    if (getToken() && !isToken) {
+      config.headers[website.tokenHeader] = 'bearer ' + getToken()
     }
+    
+    if (config.text === true) {
+      config.headers["Content-Type"] = "text/plain";
+    }
+    
     config.cancelToken = new axios.CancelToken((cancel) => {
       window._axiosPromiseArr.push({ cancel });
     });
@@ -46,10 +48,25 @@
   }
 );
 
-// http response 拦截
 service.interceptors.response.use(
   (res) => {
-    const code = res.data.code;
+    NProgress.done();
+    const status = res.data.code || res.status;
+    const statusWhiteList = website.statusWhiteList || [];
+    const message = res.data.msg || res.data.error_description || '未知错误';
+    
+    if (statusWhiteList.includes(status)) return Promise.reject(res);
+    
+    if (status === 401) {
+      store.dispatch('FedLogOut').then(() => {
+        router.push({ path: '/login' });
+      });
+    }
+    
+    if (status !== 200 && status !== 201) {
+      MessageBox.alert(message);
+      return Promise.reject(new Error(message))
+    }
 
     if (res.data.resultList && res.data.resultList.length > 0) {
       res.data.resultList = res.data.resultList.map((item) => {
@@ -68,8 +85,9 @@
     return res;
   },
   (error) => {
+    NProgress.done();
     return Promise.reject(new Error(error));
   }
 );
 
-export default service;
+export default service;
\ No newline at end of file
diff --git a/src/router/page/index.js b/src/router/page/index.js
index 68e104c..4ed01e3 100644
--- a/src/router/page/index.js
+++ b/src/router/page/index.js
@@ -1,10 +1,3 @@
-/*
- * @Author: Morpheus
- * @Date: 2021-04-30 14:12:09
- * @Last Modified by: Morpheus
- * @Last Modified time: 2022-07-29 09:15:23
- */
-
 import Vue from 'vue'
 import VueRouter from 'vue-router'
 
@@ -14,11 +7,20 @@
 const home = () => import('../../views/home/index.vue')
 const home_02 = () => import('../../views/home/index_02.vue')
 const home_03 = () => import('../../views/home/index_03.vue')
+const login = () => import('../../views/login/index.vue')
 
 const routes = [
     {
         path: '/',
-        redirect: '/layout'
+        redirect: '/login'
+    },
+    {
+        path: '/login',
+        meta: {
+            title: '登录',
+            isAuth: false
+        },
+        component: login,
     },
     {
         path: '/layout',
@@ -52,7 +54,6 @@
             }
         ]
     },
-
     {
         path: '/ssjk',
         meta: {
@@ -60,7 +61,6 @@
         },
         component: () => import('../../views/ywsys/index.vue'),
     },
-
     {
         path: '/csmodel',
         meta: {
@@ -75,4 +75,4 @@
     routes
 })
 
-export default router
+export default router
\ No newline at end of file
diff --git a/src/store/getters.js b/src/store/getters.js
index e266fa4..c0bfcf0 100644
--- a/src/store/getters.js
+++ b/src/store/getters.js
@@ -1,16 +1,13 @@
-/*
- * @Author: shuishen 1109946754@qq.com
- * @Date: 2023-03-02 16:36:47
- * @LastEditors: shuishen 1109946754@qq.com
- * @LastEditTime: 2023-04-04 10:54:12
- * @FilePath: \srs-police-affairs\src\store\getters.js
- * @Description: 
- * 
- * Copyright (c) 2023 by ${git_name_email}, All Rights Reserved. 
- */
 const getters = {
     examId: (state) => state.user.examId,
     isShowVideoDialog: (state) => state.user.isShowVideoDialog,
+    token: (state) => state.user.token,
+    userInfo: (state) => state.user.userInfo,
+    tenantId: (state) => state.user.tenantId,
+    permission: (state) => state.user.permission,
+    roles: (state) => state.user.roles,
+    menu: (state) => state.user.menu,
+    menuAll: (state) => state.user.menuAll,
 }
 
-export default getters
+export default getters
\ No newline at end of file
diff --git a/src/store/modules/user.js b/src/store/modules/user.js
index a47c8a6..0941f4a 100644
--- a/src/store/modules/user.js
+++ b/src/store/modules/user.js
@@ -1,31 +1,249 @@
-/*
- * @Author: shuishen 1109946754@qq.com
- * @Date: 2022-12-27 09:33:01
- * @LastEditors: shuishen 1109946754@qq.com
- * @LastEditTime: 2023-04-02 12:18:26
- * @FilePath: \srs-police-affairs\src\store\modules\user.js
- * @Description: 
- * 
- * Copyright (c) 2023 by ${git_name_email}, All Rights Reserved. 
- */
+import { setToken, setRefreshToken, removeToken, removeRefreshToken } from '@/utils/auth'
+import { Message } from 'element-ui'
+import { setStore, getStore } from '@/utils/store'
+import { isURL, validatenull } from '@/utils/validate'
+import { deepClone } from '@/utils/tool'
+import website from '@/config/website'
+import { loginByUsername, loginBySocial, getUserInfo, logout, refreshToken, getButtons } from '@/api/user'
+import md5 from 'js-md5'
 
-const user = {
-    state: {
-        examId: '',
-        isShowVideoDialog: ''
-    },
-    actions: {
-
-    },
-    mutations: {
-        SET_EXAMID: (state, active) => {
-            state.examId = active
-        },
-
-        SET_ISSHOWVIDEODIALOG: (state, active) => {
-            state.isShowVideoDialog = active
-        },
-    }
+function addPath(ele, first) {
+  const menu = website.menu;
+  const propsConfig = menu.props;
+  const propsDefault = {
+    label: propsConfig.label || 'name',
+    path: propsConfig.path || 'path',
+    icon: propsConfig.icon || 'icon',
+    children: propsConfig.children || 'children'
+  }
+  const icon = ele[propsDefault.icon];
+  ele[propsDefault.icon] = validatenull(icon) ? menu.iconDefault : icon;
+  const isChild = ele[propsDefault.children] && ele[propsDefault.children].length !== 0;
+  if (!isChild) ele[propsDefault.children] = [];
+  if (!isChild && first && !isURL(ele[propsDefault.path])) {
+    ele[propsDefault.path] = ele[propsDefault.path] + '/index'
+  } else {
+    ele[propsDefault.children].forEach(child => {
+      addPath(child);
+    })
+  }
 }
 
-export default user
+const user = {
+  state: {
+    tenantId: getStore({ name: 'tenantId' }) || '',
+    userInfo: getStore({ name: 'userInfo' }) || [],
+    permission: getStore({ name: 'permission' }) || {},
+    roles: [],
+    menuId: {},
+    menu: getStore({ name: 'menu' }) || [],
+    menuAll: getStore({ name: 'menuAll' }) || [],
+    token: getStore({ name: 'token' }) || '',
+    refreshToken: getStore({ name: 'refreshToken' }) || '',
+    hideHome: false,
+    examId: '',
+    isShowVideoDialog: ''
+  },
+  actions: {
+    LoginByUsername({ commit }, userInfo) {
+      return new Promise((resolve, reject) => {
+        loginByUsername(userInfo.tenantId, userInfo.username, md5(userInfo.password), userInfo.type, userInfo.key, userInfo.code).then(res => {
+          const data = res.data;
+          if (data.error_description) {
+            Message({
+              message: data.error_description,
+              type: 'error'
+            })
+          } else {
+            commit('SET_TOKEN', data.access_token);
+            commit('SET_REFRESH_TOKEN', data.refresh_token);
+            commit('SET_TENANT_ID', data.tenant_id);
+            commit('SET_USER_INFO', data);
+            commit('DEL_ALL_TAG');
+            commit('CLEAR_LOCK');
+          }
+          resolve(data);
+        }).catch(error => {
+          reject(error);
+        })
+      })
+    },
+    LoginByPhone({ commit }, userInfo) {
+      return new Promise((resolve) => {
+        loginByUsername(userInfo.phone, userInfo.code).then(res => {
+          const data = res.data.data;
+          commit('SET_TOKEN', data);
+          commit('DEL_ALL_TAG');
+          commit('CLEAR_LOCK');
+          resolve();
+        })
+      })
+    },
+    LoginBySocial({ commit }, userInfo) {
+      return new Promise((resolve) => {
+        loginBySocial(userInfo.tenantId, userInfo.source, userInfo.code, userInfo.state).then(res => {
+          const data = res.data;
+          if (data.error_description) {
+            Message({
+              message: data.error_description,
+              type: 'error'
+            })
+          } else {
+            commit('SET_TOKEN', data.access_token);
+            commit('SET_REFRESH_TOKEN', data.refresh_token);
+            commit('SET_USER_INFO', data);
+            commit('DEL_ALL_TAG');
+            commit('CLEAR_LOCK');
+          }
+          resolve();
+        })
+      })
+    },
+    GetUserInfo({ commit }) {
+      return new Promise((resolve, reject) => {
+        getUserInfo().then((res) => {
+          const data = res.data.data;
+          commit('SET_ROLES', data.roles);
+          resolve(data);
+        }).catch(err => {
+          reject(err);
+        })
+      })
+    },
+    refreshToken({ state, commit }) {
+      return new Promise((resolve, reject) => {
+        refreshToken(state.refreshToken, state.tenantId).then(res => {
+          const data = res.data;
+          commit('SET_TOKEN', data.access_token);
+          commit('SET_REFRESH_TOKEN', data.refresh_token);
+          resolve();
+        }).catch(error => {
+          reject(error)
+        })
+      })
+    },
+    LogOut({ commit }) {
+      return new Promise((resolve, reject) => {
+        logout().then(() => {
+          commit('SET_TOKEN', '');
+          commit('SET_MENU', []);
+          commit('SET_MENU_ALL_NULL', []);
+          commit('SET_ROLES', []);
+          commit('SET_TAG_LIST', []);
+          commit('DEL_ALL_TAG');
+          commit('CLEAR_LOCK');
+          removeToken();
+          removeRefreshToken();
+          resolve();
+        }).catch(error => {
+          reject(error)
+        })
+      })
+    },
+    FedLogOut({ commit }) {
+      return new Promise(resolve => {
+        commit('SET_TOKEN', '');
+        commit('SET_MENU_ALL_NULL', []);
+        commit('SET_MENU', []);
+        commit('SET_ROLES', []);
+        commit('SET_TAG_LIST', []);
+        commit('DEL_ALL_TAG');
+        commit('CLEAR_LOCK');
+        removeToken();
+        removeRefreshToken();
+        resolve();
+      })
+    },
+    GetButtons({ commit }) {
+      return new Promise((resolve) => {
+        getButtons().then(res => {
+          const data = res.data.data;
+          commit('SET_PERMISSION', data);
+          resolve();
+        })
+      })
+    },
+  },
+  mutations: {
+    set_hideHome: (state, val) => {
+      state.hideHome = val;
+    },
+    SET_TOKEN: (state, token) => {
+      setToken(token);
+      state.token = token;
+      setStore({ name: 'token', content: state.token })
+    },
+    SET_MENU_ID(state, menuId) {
+      state.menuId = menuId;
+    },
+    SET_MENU_ALL: (state, menuAll) => {
+      let menu = state.menuAll;
+      menuAll.forEach(ele => {
+        if (!menu.find(item => item.label === ele.label && item.path === ele.path)) {
+          menu.push(ele);
+        }
+      })
+      state.menuAll = menu
+      setStore({ name: 'menuAll', content: state.menuAll })
+    },
+    SET_MENU_ALL_NULL: (state) => {
+      state.menuAll = []
+      setStore({ name: 'menuAll', content: state.menuAll })
+    },
+    SET_MENU: (state, menu) => {
+      state.menu = menu
+      setStore({ name: 'menu', content: state.menu })
+    },
+    SET_REFRESH_TOKEN: (state, refreshToken) => {
+      setRefreshToken(refreshToken)
+      state.refreshToken = refreshToken;
+      setStore({ name: 'refreshToken', content: state.refreshToken })
+    },
+    SET_TENANT_ID: (state, tenantId) => {
+      state.tenantId = tenantId;
+      setStore({ name: 'tenantId', content: state.tenantId })
+    },
+    SET_USER_INFO: (state, userInfo) => {
+      if (validatenull(userInfo.avatar)) {
+        userInfo.avatar = "http://61.131.136.25:2081/zhba/upload/picture/mrtx.png";
+      }
+      state.userInfo = userInfo;
+      setStore({ name: 'userInfo', content: state.userInfo })
+    },
+    SET_ROLES: (state, roles) => {
+      state.roles = roles;
+    },
+    SET_PERMISSION: (state, permission) => {
+      let result = [];
+
+      function getCode(list) {
+        list.forEach(ele => {
+          if (typeof (ele) === 'object') {
+            const chiildren = ele.children;
+            const code = ele.code;
+            if (chiildren) {
+              getCode(chiildren)
+            } else {
+              result.push(code);
+            }
+          }
+        })
+      }
+
+      getCode(permission);
+      state.permission = {};
+      result.forEach(ele => {
+        state.permission[ele] = true;
+      });
+      setStore({ name: 'permission', content: state.permission })
+    },
+    SET_EXAMID: (state, active) => {
+      state.examId = active
+    },
+    SET_ISSHOWVIDEODIALOG: (state, active) => {
+      state.isShowVideoDialog = active
+    },
+  }
+}
+
+export default user
\ No newline at end of file
diff --git a/src/styles/index.scss b/src/styles/index.scss
index 8bcdd61..29e8b7d 100644
--- a/src/styles/index.scss
+++ b/src/styles/index.scss
@@ -3,6 +3,7 @@
 @import "./icon/index.scss";
 @import "./tool/index.scss";
 @import "./element-ui/element-ui.scss";
+@import "./login.scss";
 
 @font-face {
     font-family: Roboto-Bold;
diff --git a/src/styles/login.scss b/src/styles/login.scss
new file mode 100644
index 0000000..e616f24
--- /dev/null
+++ b/src/styles/login.scss
@@ -0,0 +1,157 @@
+html, body, #app {
+  height: 100%;
+  margin: 0;
+  padding: 0;
+}
+
+.login-container {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  position: relative;
+  width: 100%;
+  height: 100%;
+  background-image: url("/images/pro-bg.png");
+  background-size: cover;
+  background-position: center;
+}
+
+.login-weaper {
+  display: flex;
+  width: 800px;
+  box-shadow: -4px 5px 20px rgba(0, 0, 0, 0.3);
+  border-radius: 8px;
+  overflow: hidden;
+}
+
+.login-left,
+.login-border {
+  min-height: 450px;
+  display: flex;
+  align-items: center;
+}
+
+.login-left {
+  border-top-left-radius: 8px;
+  border-bottom-left-radius: 8px;
+  justify-content: center;
+  flex-direction: column;
+  background: linear-gradient(135deg, #409eff 0%, #67b8ff 100%);
+  color: #fff;
+  width: 45%;
+  padding: 30px;
+  box-sizing: border-box;
+}
+
+.login-time {
+  position: absolute;
+  top: 30px;
+  left: 30px;
+  color: rgba(255, 255, 255, 0.9);
+  font-weight: 300;
+  font-size: 16px;
+}
+
+.login-left .img {
+  width: 120px;
+  margin-bottom: 20px;
+}
+
+.login-left .title {
+  text-align: center;
+  color: #fff;
+  font-weight: 400;
+  letter-spacing: 3px;
+  font-size: 22px;
+  margin: 0;
+}
+
+.login-border {
+  border-top-right-radius: 8px;
+  border-bottom-right-radius: 8px;
+  background-color: #fff;
+  width: 55%;
+  padding: 40px 30px;
+  box-sizing: border-box;
+}
+
+.login-main {
+  width: 100%;
+}
+
+.login-title {
+  color: #333;
+  margin-bottom: 8px;
+  font-weight: 500;
+  font-size: 20px;
+  text-align: center;
+  letter-spacing: 2px;
+}
+
+.login-title-child {
+  color: #999;
+  margin-bottom: 30px;
+  font-weight: 300;
+  font-size: 12px;
+  text-align: center;
+  letter-spacing: 1px;
+}
+
+.login-form {
+  margin: 0;
+
+  .el-form-item {
+    margin-bottom: 20px;
+  }
+
+  .el-input {
+    input {
+      padding: 12px 15px;
+      border: 1px solid #e4e7ed;
+      border-radius: 6px;
+      font-size: 14px;
+      transition: border-color 0.3s;
+      
+      &:focus {
+        border-color: #409eff;
+        outline: none;
+      }
+    }
+
+    .el-input__prefix {
+      i {
+        color: #909399;
+        font-size: 16px;
+      }
+    }
+
+    .el-input__suffix {
+      i {
+        color: #909399;
+        cursor: pointer;
+      }
+    }
+  }
+}
+
+.login-submit {
+  width: 100%;
+  height: 42px;
+  background: linear-gradient(135deg, #409eff 0%, #67b8ff 100%);
+  border: none;
+  border-radius: 6px;
+  font-size: 16px;
+  letter-spacing: 2px;
+  color: #fff;
+  cursor: pointer;
+  margin-top: 10px;
+  transition: opacity 0.3s;
+  
+  &:hover {
+    opacity: 0.9;
+  }
+  
+  &:active {
+    opacity: 0.8;
+  }
+}
\ No newline at end of file
diff --git a/src/utils/tool.js b/src/utils/tool.js
index 2610f3f..ba4b18a 100644
--- a/src/utils/tool.js
+++ b/src/utils/tool.js
@@ -1,42 +1,45 @@
-/*
- * @Author: shuishen 1109946754@qq.com
- * @Date: 2022-10-17 09:34:19
- * @LastEditors: shuishen 1109946754@qq.com
- * @LastEditTime: 2022-10-17 09:45:20
- * @FilePath: \srs-police-affairs\src\utils\tool.js
- * @Description: 
- * 
- * Copyright (c) 2022 by shuishen 1109946754@qq.com, All Rights Reserved. 
- */
 function executeFunction (callbackArray, amount) {
     return new Promise(function (resolve, reject) {
         let index = 0
-
         let currentIndex = 1
 
         function execute () {
-
             try {
                 callbackArray[index]()
                 index++
             } catch (e) {
-
                 return new Error(e)
-
             } finally {
-
                 if (currentIndex == callbackArray.length) {
                     resolve
                 }
-
                 execute()
                 currentIndex++
             }
-
         }
-
         execute()
     })
 }
 
+export function deepClone(obj) {
+    if (obj === null || typeof obj !== 'object') {
+        return obj
+    }
+    if (obj instanceof Date) {
+        return new Date(obj.getTime())
+    }
+    if (obj instanceof Array) {
+        return obj.map(item => deepClone(item))
+    }
+    if (typeof obj === 'object') {
+        const clonedObj = {}
+        for (let key in obj) {
+            if (Object.prototype.hasOwnProperty.call(obj, key)) {
+                clonedObj[key] = deepClone(obj[key])
+            }
+        }
+        return clonedObj
+    }
+}
+
 export default executeFunction
\ No newline at end of file
diff --git a/src/views/home/index.vue b/src/views/home/index.vue
index 94ef738..bf51829 100644
--- a/src/views/home/index.vue
+++ b/src/views/home/index.vue
@@ -235,7 +235,7 @@
 
         // 点击播放视频
         openVideoPlayer () {
-            this.$store.commit("SET_ISSHOWVIDEODIALOG", true)
+            // this.$store.commit("SET_ISSHOWVIDEODIALOG", true)
         },
     },
 
diff --git a/src/views/login/index.vue b/src/views/login/index.vue
new file mode 100644
index 0000000..ac7b1c2
--- /dev/null
+++ b/src/views/login/index.vue
@@ -0,0 +1,58 @@
+<template>
+  <div class="login-container" ref="login" @keyup.enter.native="handleLogin">
+    <div class="login-weaper">
+      <div class="login-left">
+        <div class="login-time">
+          {{ time }}
+        </div>
+        <img class="img" src="/images/logo.png" alt="" />
+        <p class="title">保安员考试大屏</p>
+      </div>
+      <div class="login-border">
+        <div class="login-main">
+          <h4 class="login-title">
+            系统登录
+          </h4>
+          <h4 class="login-title-child">Security Guard Exam System</h4>
+          <userLogin></userLogin>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import userLogin from "./userlogin";
+import { dateFormat } from "@/utils/date";
+
+export default {
+  name: "login",
+  components: {
+    userLogin,
+  },
+  data() {
+    return {
+      time: "",
+    };
+  },
+  created() {
+    document.title = "保安员考试大屏"
+    this.$store.commit("SET_WEBTITLE", '保安员考试大屏')
+    this.getTime();
+  },
+  mounted() {},
+  props: [],
+  methods: {
+    getTime() {
+      setInterval(() => {
+        this.time = dateFormat(new Date());
+      }, 1000);
+    },
+    handleLogin() {
+    },
+  },
+};
+</script>
+
+<style lang="scss">
+@import "@/styles/login.scss";
+</style>
\ No newline at end of file
diff --git a/src/views/login/userlogin.vue b/src/views/login/userlogin.vue
new file mode 100644
index 0000000..d123477
--- /dev/null
+++ b/src/views/login/userlogin.vue
@@ -0,0 +1,82 @@
+<template>
+  <el-form class="login-form" status-icon :rules="loginRules" ref="loginForm" :model="loginForm" label-width="0">
+    <el-form-item prop="username">
+      <el-input size="small" @keyup.enter.native="handleLogin" v-model="loginForm.username" auto-complete="off"
+                placeholder="请输入用户名">
+        <i slot="prefix" class="fa fa-user"/>
+      </el-input>
+    </el-form-item>
+    <el-form-item prop="password">
+      <el-input size="small" @keyup.enter.native="handleLogin" :type="passwordType" v-model="loginForm.password"
+                auto-complete="off" placeholder="请输入密码">
+        <i class="el-icon-view el-input__icon" slot="suffix" @click="showPassword"/>
+        <i slot="prefix" class="fa fa-lock"/>
+      </el-input>
+    </el-form-item>
+    <el-form-item>
+      <el-button type="primary" size="small" @click.native.prevent="handleLogin" class="login-submit">登录
+      </el-button>
+    </el-form-item>
+  </el-form>
+</template>
+
+<script>
+import {Message} from "element-ui";
+
+export default {
+  name: "userlogin",
+  data() {
+    return {
+      loginForm: {
+        tenantId: "000000",
+        username: "",
+        password: "",
+        type: "account",
+      },
+      loginRules: {
+        username: [
+          {required: true, message: "请输入用户名", trigger: "blur"},
+        ],
+        password: [
+          {required: true, message: "请输入密码", trigger: "blur"},
+          {min: 6, message: "密码长度最少为6位", trigger: "blur"},
+        ],
+      },
+      passwordType: "password",
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    showPassword() {
+      this.passwordType = this.passwordType === "password" ? "" : "password"
+    },
+    handleLogin() {
+      this.$refs.loginForm.validate((valid) => {
+        if (valid) {
+          const loading = this.$loading({
+            lock: true,
+            text: "登录中,请稍后...",
+            spinner: "el-icon-loading",
+          })
+          this.$store
+            .dispatch("LoginByUsername", this.loginForm)
+            .then((data) => {
+              if (data.error_description) {
+                loading.close()
+              }else{
+                this.$router.push({path: '/layout/home'})
+                loading.close()
+              }
+            })
+            .catch(() => {
+              loading.close()
+            })
+        }
+      })
+    },
+  },
+}
+</script>
+
+<style></style>
\ No newline at end of file
diff --git a/vue.config.js b/vue.config.js
index 23c92f8..d1a4338 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -29,36 +29,6 @@
             scss: {
                 prependData: `@import "~@/styles/scssFile.scss";`,
             },
-            // postcss: {
-            //     plugins: [   //配置px转rem进行适配
-            //         require('postcss-px2rem')({
-            //             // 根标签字号大小根据设计稿宽度/flexible.js里的份数得到,这里假定设计稿宽度为3840 3840/24 = 160px
-            //             remUnit: 80
-            //         }),
-            //     ]
-            // }
-            // postcss: {
-            //     //给postcss-loader传递选项
-            //     plugins: [
-            //         new pxtovw({
-            //             unitToConvert: 'px', //需要转换的单位,默认为"px";
-            //             viewportWidth: 1920, //设计稿的视口宽度
-            //             viewportHeight: 1080, //设计稿的视口宽度
-            //             unitPrecision: 5, //单位转换后保留的小数位数
-            //             propList: ['*'], //要进行转换的属性列表,*表示匹配所有,!表示不转换
-            //             viewportUnit: 'vw', //转换后的视口单位
-            //             fontViewportUnit: 'vw', //转换后字体使用的视口单位
-            //             selectorBlackList: [], //不进行转换的css选择器,继续使用原有单位
-            //             minPixelValue: 1, //设置最小的转换数值
-            //             mediaQuery: false, //设置媒体查询里的单位是否需要转换单位
-            //             replace: true, //是否直接更换属性值,而不添加备用属性
-            //             exclude: [/node_modules/], //忽略某些文件夹下的文件
-            //             // landscape: true,
-            //             // landscapeUnit: 'vw',
-            //             // landscapeWidth: 1920
-            //         })
-            //     ]
-            // }
         },
     },
 
@@ -94,11 +64,6 @@
                 }
             ],
         ])
-
-        // config.plugin("define").tap((args) => {
-        //     args[0]["process"] = { ...args[0]["process.env"] }
-        //     return args
-        // })
     },
 
     configureWebpack: {
@@ -125,56 +90,19 @@
                 maxChunks: 5,
                 minChunkSize: 100,
             }),
-            // 提供带 Content-Encoding 编码的压缩版的资源
-            // new CompressionPlugin({
-            //     algorithm: 'gzip',
-            //     test: /\.js$|\.html$|\.css/, // 匹配文件名
-            //     // test: /\.(js|css)$/,
-            //     threshold: 10240, // 对超过10k的数据压缩
-            //     deleteOriginalAssets: false, // 不删除源文件
-            //     minRatio: 0.8 // 压缩比
-            // })
         ],
     },
 
     devServer: {
-        // open: false, // 编译完成是否打开网页
-        // host: "0.0.0.0", // 指定使用地址,默认localhost,0.0.0.0代表可以被外界访问
-        // // port: 82, // 访问端口
-        // https: false, // 编译失败时刷新页面
-        // hot: true, // 开启热加载
-        // hotOnly: false,
-        // proxy: {
-        //     [process.env.VUE_APP_API]: {//拦截器(拦截链接中有/api)
-        //         target: process.env.VUE_API_DEV_TARGET,//'http://192.168.1.114:3000', //process.env.VUE_API_DEV_TARGET, //API服务器的地址
-        //         changeOrigin: true,
-        //         pathRewrite: {
-        //             //'^/api': '/'
-        //             [`^${process.env.VUE_APP_API}`]: '',
-        //             //[`^${process.env.VUE_APP_API}`]: '/', //配置出来的接口没有 /api
-        //         }
-        //     }
-        // }
         proxy: {
-
-            // 中台接口
-            "/services": {
-                target: "https://sk.hubeishuiyi.cn",
-                changeOrigin: true, //开启代理跨域
-                // pathRewrite: {
-                //     "^/services": "/",
-                // },
-            },
             // 业务平台接口
             "/api": {
                 // target用于配置你允许访问数据的计算机名称,即是你的api接口的服务器地址
-                target: "http://60.220.177.113:2081",
+                target: "http://124.164.10.118:2081",
                 // target: "http://192.168.0.102:81",
                 // ws: true, //启用webSocket6566
                 changeOrigin: true, //开启代理跨域
-                // pathRewrite: {//本地地址没有api需要去掉
-                //     '^/api': '/'
-                // }
+                pathRewrite: {}//后端接口包含/api前缀,不需要重写
             },
         },
     },

--
Gitblit v1.9.3