Browse Source

web 端不再通过 Authorization header 携带 token, 而是通过 cookie 传输用户数据, 这样更安全

reghao 3 days ago
parent
commit
fe05a9f156

+ 0 - 4
src/assets/js/const.js

@@ -1,4 +0,0 @@
-// 常量
-
-// 服务器地址
-export const URL = 'http://localhost:8080'

+ 24 - 8
src/assets/js/mixin.js

@@ -2,7 +2,6 @@
  * 混入对象,抽取Vue中公共的部分
  */
 import { getPubkey, getCaptchaCode, getVerifyCode, login, logout, register, forgot } from '@/api/account'
-import { setUserToken, removeAll } from '@/utils/auth'
 import { JSEncrypt } from 'jsencrypt'
 import Vue from 'vue'
 import store from '@/store'
@@ -12,18 +11,23 @@ export const userMixin = {
     return {
       pubkey: '',
       pubkeyR: '',
+      captchaKey: '',
       captchaCode: '',
       userLogin: {
         principal: null,
         credential: null,
+        r: null,
+        captchaKey: null,
         captchaCode: null,
         loginType: 2,
-        plat: 2
+        plat: 1
       },
       userRegistry: {
         principal: null,
         verifyCode: null,
         credential: null,
+        r: null,
+        captchaKey: null,
         captchaCode: null,
         plat: 2
       },
@@ -35,6 +39,8 @@ export const userMixin = {
         principal: null,
         verifyCode: null,
         newCredential: null,
+        r: null,
+        captchaKey: null,
         captchaCode: null
       },
       loginDialog: false,
@@ -76,7 +82,8 @@ export const userMixin = {
     getCaptcha() {
       getCaptchaCode().then(resp => {
         if (resp.code === 0) {
-          this.captchaCode = resp.data
+          this.captchaKey = resp.data.captchaKey
+          this.captchaCode = resp.data.captchaBase64
           this.dialogVisible = true
         } else {
           this.message = '获取图形验证码失败, 请重新刷新页面'
@@ -84,8 +91,11 @@ export const userMixin = {
         }
       })
     },
-    encryptPassword(password, pubkey, pubkeyR) {
+    encryptPassword(password, pubkeyBase64, pubkeyR) {
       var encryptor = new JSEncrypt()
+
+      // 格式化公钥:jsencrypt 库需要标准的 PEM 格式头尾包装
+      const pubkey = `-----BEGIN PUBLIC KEY-----\n${pubkeyBase64}\n-----END PUBLIC KEY-----`
       encryptor.setPublicKey(pubkey)
       return encryptor.encrypt(pubkeyR + password)
     },
@@ -159,6 +169,7 @@ export const userMixin = {
       this.loginBtn()
     },
     loginBtn() {
+      this.userLogin.captchaKey = this.captchaKey
       if (this.userLogin.principal === '' || this.userLogin.principal === null) {
         this.$message.warning('帐号不能为空')
         return
@@ -170,15 +181,13 @@ export const userMixin = {
 
       const credential = this.userLogin.credential
       this.userLogin.credential = this.encryptPassword(credential, this.pubkey, this.pubkeyR)
+      this.userLogin.r = this.pubkeyR
       // 显示加载效果
       this.isLoading = true
       login(this.userLogin).then(resp => {
         if (resp.code === 0) {
           const respData = resp.data
           const userInfo = respData.accountInfo
-          const userToken = respData.accountToken
-          // 保存授权信息到本地缓存
-          setUserToken(userToken)
           store.commit('SET_USER_INFO', userInfo)
 
           // 刷新当前页面
@@ -190,6 +199,8 @@ export const userMixin = {
           this.userLogin = {
             principal: null,
             credential: null,
+            r: null,
+            captchaKey: null,
             captchaCode: null,
             loginType: 2,
             plat: 1
@@ -219,12 +230,14 @@ export const userMixin = {
         this.$message.warning('短信验证码不能为空')
         return
       }
+      this.userRegistry.captchaKey = this.captchaKey
       if (this.userRegistry.captchaCode === '' || this.userRegistry.captchaCode === null) {
         this.$message.warning('图形验证码不能为空')
         return
       }
 
       this.userRegistry.credential = this.encryptPassword(this.userRegistry.credential, this.pubkey, this.pubkeyR)
+      this.userRegistry.r = this.pubkeyR
       // 显示加载效果
       this.isLoading = true
       register(this.userRegistry).then(resp => {
@@ -257,12 +270,14 @@ export const userMixin = {
         this.$message.warning('短信验证码不能为空')
         return
       }
+      this.userForgot.captchaKey = this.captchaKey
       if (this.userForgot.captchaCode === '' || this.userForgot.captchaCode === null) {
         this.$message.warning('图形验证码不能为空')
         return
       }
 
       this.userForgot.newCredential = this.encryptPassword(this.userForgot.newCredential, this.pubkey, this.pubkeyR)
+      this.userForgot.r = this.pubkeyR
       // 显示加载效果
       this.isLoading = true
       forgot(this.userForgot).then(resp => {
@@ -281,6 +296,8 @@ export const userMixin = {
           principal: null,
           verifyCode: null,
           newCredential: null,
+          r: null,
+          captchaKey: null,
           captchaCode: null
         }
       })
@@ -298,7 +315,6 @@ export const userMixin = {
           if (resp.code === 0) {
             Vue.$cookies.remove('token')
             store.commit('USER_LOGOUT')
-            removeAll()
           } else {
             this.$notify.error({
               title: '提示',

+ 1 - 2
src/components/VideoPlayer.vue

@@ -8,7 +8,6 @@ import SocketInstance from '@/utils/ws/socket-instance'
 
 import flvjs from 'flv.js'
 import DPlayer from 'dplayer'
-import { getAccessToken } from '@/utils/auth'
 
 export default {
   name: 'VideoPlayer',
@@ -32,7 +31,7 @@ export default {
     }
   },
   created() {
-    this.userToken = getAccessToken()
+    this.userToken = ''
     this.danmaku.token = this.userToken
   },
   mounted() {

+ 1 - 1
src/components/card/UploaderCard.vue

@@ -35,7 +35,7 @@
 <script>
 import SparkMD5 from 'spark-md5'
 import { prepareUpload, checkSample } from '@/api/file'
-import { hashFile } from '@/utils/functions'
+import { hashFile } from '@/utils/util'
 
 export default {
   name: 'UploaderCard',

+ 1 - 2
src/components/layout/NavBar.vue

@@ -106,7 +106,6 @@
 <script>
 import { userMixin } from 'assets/js/mixin'
 import { keywordSuggest } from '@/api/search'
-import { getAuthedUser } from '@/utils/auth'
 import { getUnreadCount } from '@/api/user'
 
 export default {
@@ -123,7 +122,7 @@ export default {
     }
   },
   created() {
-    const userInfo = getAuthedUser()
+    const userInfo = this.$store.state.userInfo
     if (userInfo !== null) {
       this.user = userInfo
       getUnreadCount().then(resp => {

+ 50 - 17
src/permission.js

@@ -1,32 +1,65 @@
 import router from './router'
-import { getAccessToken, removeAll } from '@/utils/auth'
 import store from '@/store'
 import { addApprovalRequest } from '@/api/user'
 import { Message, MessageBox } from 'element-ui'
 
-router.beforeEach((to, from, next) => {
-  // document.title = to.meta.title
+router.beforeEach(async(to, from, next) => {
   const needAuth = to.meta.needAuth
-  const token = getAccessToken()
-  if (token != null) {
+  // 1. 判断本地是否有登录凭证
+  if (localStorage.getItem('userInfo')) {
     if (to.path === '/login' || to.path === '/register') {
       next({ path: '/' })
     } else {
-      const hasRoles = store.getters.roles.length > 0
-      if (hasRoles) {
-        checkRolePermission(to, from, next)
-        // next()
-      } else {
-        const roles = store.dispatch('getUserRoles')
-        const accessRoutes = store.dispatch('generateRoutes', roles)
-        accessRoutes.then(allRoutes => {
+      // 1. 获取当前用户拥有的角色列表(优先从 Vuex 获取,没有则去本地/异步拉取)
+      let userRoles = store.getters.roles || []
+      if (userRoles.length === 0) {
+        // getUserRoles 内部包含从 localStorage 恢复角色的逻辑
+        userRoles = await store.dispatch('getUserRoles')
+      }
+
+      // 2. 🚀 第二步:判断是否需要动态挂载 /bg 前缀路由
+      // 如果去往以 /bg 开头的路由,且当前 Vuex 还未加载过动态路由
+      const isBgRoute = to.path.startsWith('/bg')
+      const hasLoadedRoutes = store.getters.addRoutes && store.getters.addRoutes.length > 0
+
+      if (isBgRoute && !hasLoadedRoutes) {
+        try {
+          // 根据当前用户的角色,动态生成该角色有权访问的 /bg 路由表
+          const allRoutes = await store.dispatch('generateRoutes', userRoles)
+
+          // 动态注入到 Vue-Router
           router.addRoutes(allRoutes)
-          next({ ...to, replace: true })
-        })
+
+          // 【核心】动态注入后必须 replace 重定向,确保新挂载的路由能被浏览器识别
+          return next({ ...to, replace: true })
+        } catch (error) {
+          console.error('动态加载后台路由失败:', error)
+          Message.error('系统加载后台配置失败,请重新登录')
+          store.commit('USER_LOGOUT')
+          return next('/login')
+        }
       }
+
+      // 3. 🛡️ 第一步:判断当前用户角色是否有权访问该目标 URL
+      // 检查目标路由 meta 中是否配置了 roles 访问限制
+      if (to.meta && to.meta.roles && to.meta.roles.length > 0) {
+        const requiredRoles = to.meta.roles
+
+        // 判断用户角色是否与路由所需角色有交集
+        const hasPermission = userRoles.some(role => requiredRoles.includes(role))
+
+        if (!hasPermission) {
+          // 无权限访问,进入拦截与自动申请流程
+          return checkRolePermission(to, from, next)
+        }
+      }
+
+      // 4. 有权限且路由已加载就绪,安全放行
+      next()
     }
   } else {
-    removeAll()
+    // 无凭证去登录页
+    store.commit('USER_LOGOUT')
     if (needAuth === undefined || needAuth) {
       next('/login')
     } else {
@@ -73,7 +106,7 @@ function manulApplyRole(roleName) {
   const requestData = {
     bizType: 'applyRole',
     // 将申请的目标角色包装进 payload 传给后端
-    bizPayload: JSON.stringify({ role: roleName})
+    bizPayload: JSON.stringify({ role: roleName })
   }
   addApprovalRequest(requestData).then(resp => {
     if (resp.code === 0) {

+ 6 - 6
src/router/background_account.js

@@ -11,7 +11,7 @@ const MyContact = () => import('views/my/MyContact')
 const MyOAuth = () => import('views/my/MyOAuth')
 
 export default {
-  path: '/bg/my',
+  path: '/bg/account',
   redirect: '/bg',
   name: 'Account',
   title: '我的帐号',
@@ -20,7 +20,7 @@ export default {
   meta: { needAuth: true, roles: ['tnb_admin', 'tnb_user', 'tnb_disk'] },
   children: [
     {
-      path: '/bg/my/record',
+      path: '/bg/account/record',
       name: 'MyRecord',
       title: '登入记录',
       icon: 'el-icon-user',
@@ -28,7 +28,7 @@ export default {
       meta: { needAuth: true, roles: ['tnb_admin', 'tnb_user', 'tnb_disk'] }
     },
     {
-      path: '/bg/my/message',
+      path: '/bg/account/message',
       name: 'MyMessage',
       title: '我的消息',
       icon: 'el-icon-user',
@@ -36,7 +36,7 @@ export default {
       meta: { needAuth: true, roles: ['tnb_admin', 'tnb_user', 'tnb_disk'] }
     },
     {
-      path: '/bg/my/approval',
+      path: '/bg/account/approval',
       name: 'MyApproval',
       title: '我的审批',
       icon: 'el-icon-user',
@@ -44,7 +44,7 @@ export default {
       meta: { needAuth: true, roles: ['tnb_admin', 'tnb_user', 'tnb_disk'] }
     },
     {
-      path: '/bg/my/contact',
+      path: '/bg/account/contact',
       name: 'MyContact',
       title: '联系人',
       icon: 'el-icon-user',
@@ -52,7 +52,7 @@ export default {
       meta: { needAuth: true, roles: ['tnb_admin', 'tnb_user', 'tnb_disk'] }
     },
     {
-      path: '/bg/my/oauth',
+      path: '/bg/account/oauth',
       name: 'OAuth',
       title: 'OAuth应用',
       icon: 'el-icon-user',

+ 33 - 32
src/store/index.js

@@ -1,25 +1,30 @@
 import Vuex from 'vuex'
 import Vue from 'vue'
-
 import { asyncRoutes, constantRoutes } from '@/router'
-import { getAuthedUser, setAuthedUser } from '@/utils/auth'
 
 Vue.use(Vuex)
+
+// 💡 辅助方法:安全地从 localStorage 获取初始值
+const getLocalUserInfo = () => {
+  try {
+    return JSON.stringify(localStorage.getItem('userInfo')) ? JSON.parse(localStorage.getItem('userInfo')) : null
+  } catch (e) {
+    return null
+  }
+}
+const initialUser = getLocalUserInfo()
+
 const store = new Vuex.Store({
-  /* modules: {
-    user
-  },*/
   getters: {
-    // 用户登入状态
+    userInfo: state => state.userInfo,
     roles: state => state.roles,
     routes: state => state.routes,
     addRoutes: state => state.addRoutes,
-    // websocket 连接状态
     socketStatus: state => state.socketStatus
   },
   state: {
-    userInfo: null,
-    roles: [],
+    userInfo: initialUser,
+    roles: initialUser ? (initialUser.roles || []) : [], // 🌟 刷新页面时自动从本地恢复角色
     routes: [],
     addRoutes: [],
     socketStatus: 'Offline'
@@ -27,49 +32,45 @@ const store = new Vuex.Store({
   mutations: {
     SET_ROUTES: (state, routes) => {
       state.addRoutes = routes
-      state.routes = constantRoutes.concat(routes) // 组合路由,将原始路由和权限路由组合生成路由表
+      state.routes = constantRoutes.concat(routes)
     },
-    // 更新socket 连接状态
     UPDATE_SOCKET_STATUS(state, status) {
       state.socketStatus = status
     },
-    // 更新用户信息
     SET_USER_INFO(state, userInfo) {
-      // 保存用户信息到缓存
-      setAuthedUser(userInfo)
       state.userInfo = userInfo
-      // state.roles = userInfo.roles
+      state.roles = userInfo.roles || []
+      localStorage.setItem('userInfo', JSON.stringify(userInfo))
     },
     SET_ROLES(state, roles) {
       state.roles = roles
     },
-    // 用户退出登入
     USER_LOGOUT(state) {
       state.userInfo = null
       state.roles = []
+      localStorage.removeItem('userInfo')
     }
   },
-  // 异步操作
   actions: {
-    // 这里就是获取 权限路由 参数roles即是用户信息中返回的roles
+    // 🌟 修复:roles 直接传值(数组),不再传 Promise 对象
     generateRoutes({ commit }, roles) {
       return new Promise(resolve => {
-        roles.then(roles => {
-          const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
-          // 更改state
-          commit('SET_ROUTES', accessedRoutes)
-          resolve(accessedRoutes) // 返回 生成的权限路由表
-        })
+        const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
+        commit('SET_ROUTES', accessedRoutes)
+        resolve(accessedRoutes)
       })
     },
-    getUserRoles({ commit }) {
-      /* const { roles, userId, avatarUrl, username } = getAuthedUser()
-      commit('SET_ROLES', roles)
-      return roles*/
-      return new Promise((resolve, reject) => {
-        const { roles, userId, avatarUrl, username } = getAuthedUser()
-        commit('SET_ROLES', roles)
-        resolve(roles)
+    // 🌟 修复:如果 state 里没角色,尝试从 localStorage 获取
+    getUserRoles({ commit, state }) {
+      return new Promise((resolve) => {
+        if (state.roles && state.roles.length > 0) {
+          resolve(state.roles)
+        } else {
+          const localUser = getLocalUserInfo()
+          const roles = localUser ? (localUser.roles || []) : []
+          commit('SET_ROLES', roles)
+          resolve(roles)
+        }
       })
     }
   }

+ 0 - 120
src/utils/auth.js

@@ -1,120 +0,0 @@
-import Vue from 'vue'
-import FingerprintJS from '@fingerprintjs/fingerprintjs'
-
-const USER_ACCESS_TOKEN = 'ACCESS-TOKEN'
-const USER_REFRESH_TOKEN = 'REFRESH-TOKEN'
-const USER_INFO = 'TNBAPP-USERINFO'
-
-/**
- * 设置已登入用户的 token
- *
- * @param {Object} userToken
- */
-export function setUserToken(userToken) {
-  const accessToken = userToken.accessToken
-  const accessExpireAt = userToken.accessExpireAt
-  localStorage.setItem(USER_ACCESS_TOKEN,
-    JSON.stringify({
-      accessToken,
-      accessExpireAt
-    })
-  )
-
-  const refreshToken = userToken.refreshToken
-  const refreshExpireAt = userToken.refreshExpireAt
-  localStorage.setItem(USER_REFRESH_TOKEN,
-    JSON.stringify({
-      refreshToken,
-      refreshExpireAt
-    })
-  )
-}
-
-/**
- * 获取已登入用户的访问令牌
- */
-export function getAccessToken() {
-  const result = localStorage.getItem(USER_ACCESS_TOKEN)
-  if (result !== undefined && result !== null) {
-    const token = JSON.parse(result)
-    const t = new Date().getTime()
-    if (token.accessExpireAt > t) {
-      return token.accessToken
-    }
-
-    localStorage.removeItem(USER_ACCESS_TOKEN)
-  }
-  return null
-}
-
-/**
- * 获取已登入的刷新令牌
- */
-export function getRefreshToken() {
-  const result = localStorage.getItem(USER_REFRESH_TOKEN)
-  if (result !== undefined && result !== null) {
-    const token = JSON.parse(result)
-    const t = new Date().getTime()
-    if (token.refreshExpireAt > t) {
-      return token.refreshToken
-    }
-
-    localStorage.removeItem(USER_REFRESH_TOKEN)
-  }
-  return null
-}
-
-/**
- * 设置已登入用户信息
- *
- * @param {Object} data
- */
-export function setAuthedUser(data) {
-  localStorage.setItem(USER_INFO, JSON.stringify(data))
-}
-
-/**
- * 更新已登入用户信息
- *
- * @param {Object} data
- */
-export function updateAuthedUser(data) {
-  localStorage.setItem(USER_INFO, JSON.stringify(data))
-}
-
-/**
- * 获取已登入用户信息
- */
-export function getAuthedUser() {
-  const data = localStorage.getItem(USER_INFO) || null
-  return data !== null ? JSON.parse(data) : null
-}
-
-/**
- * 删除已登入用户相关缓存信息
- */
-export function removeAll() {
-  localStorage.removeItem(USER_ACCESS_TOKEN)
-  localStorage.removeItem(USER_REFRESH_TOKEN)
-  localStorage.removeItem(USER_INFO)
-}
-
-/**
- * 浏览器指纹
- */
-export async function getBrowserFingerprint() {
-  // 初始化FingerprintJS
-  const fp = await FingerprintJS.load()
-  // 获取访问者的指纹
-  const result = await fp.get()
-  const {
-    plugins,
-    ...components
-  } = result.components
-  const extendedComponents = {
-    ...components
-  }
-  // const deviceInfo = JSON.stringify(extendedComponents)
-  const fingerprintId = FingerprintJS.hashComponents(extendedComponents)
-  Vue.$cookies.set('fp', fingerprintId, -1)
-}

+ 0 - 144
src/utils/date.js

@@ -1,144 +0,0 @@
-/**
- * 对Date的扩展,将 Date 转化为指定格式的String。
- *
- *  月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符,
- *  年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)。
- *
- *  【示例】:
- *  formatDate(new Date(), 'yyyy-MM-dd hh:mm:ss.S') ==> 2006-07-02 08:09:04.423
- *  formatDate(new Date(), 'yyyy-M-d h:m:s.S')      ==> 2006-7-2 8:9:4.18
- *  formatDate(new Date(), 'hh:mm:ss.S')            ==> 08:09:04.423
- */
-export function formatDate(date, fmt) {
-  const o = {
-    'M+': date.getMonth() + 1, //月份
-    'd+': date.getDate(), //日
-    'h+': date.getHours(), //小时
-    'm+': date.getMinutes(), //分
-    's+': date.getSeconds(), //秒
-    'q+': Math.floor((date.getMonth() + 3) / 3), //季度
-    S: date.getMilliseconds(), //毫秒
-  }
-
-  if (/(y+)/.test(fmt)) {
-    fmt = fmt.replace(
-      RegExp.$1,
-      (date.getFullYear() + '').substr(4 - RegExp.$1.length)
-    )
-  }
-
-  for (let k in o) {
-    if (new RegExp('(' + k + ')').test(fmt)) {
-      let value =
-        RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)
-      fmt = fmt.replace(RegExp.$1, value)
-    }
-  }
-
-  return fmt
-}
-
-/**
- * 仿照微信中的消息时间显示逻辑,将时间戳(单位:毫秒)转换为友好的显示格式.
- *
- * 1)7天之内的日期显示逻辑是:今天、昨天(-1d)、前天(-2d)、星期?(只显示总计7天之内的星期数,即<=-4d);
- * 2)7天之外(即>7天)的逻辑:直接显示完整日期时间。
- *
- * @param {[long]} timestamp 时间戳(单位:毫秒),形如:1550789954260
- * @param {boolean} mustIncludeTime true表示输出的格式里一定会包含“时间:分钟”
- * ,否则不包含(参考微信,不包含时分的情况,用于首页“消息”中显示时)
- *
- * @return {string} 输出格式形如:“刚刚”、“10:30”、“昨天 12:04”、“前天 20:51”、“星期二”、“2019/2/21 12:09”等形式
- */
-export function formatDateShort(timestamp, mustIncludeTime) {
-  // 当前时间
-  let currentDate = new Date()
-  // 目标判断时间
-  let srcDate = new Date(parseInt(timestamp))
-
-  let currentYear = currentDate.getFullYear()
-  let currentMonth = currentDate.getMonth() + 1
-  let currentDateD = currentDate.getDate()
-
-  let srcYear = srcDate.getFullYear()
-  let srcMonth = srcDate.getMonth() + 1
-  let srcDateD = srcDate.getDate()
-
-  let ret = ''
-
-  // 要额外显示的时间分钟
-  let timeExtraStr = mustIncludeTime ? ' ' + formatDate(srcDate, 'hh:mm') : ''
-
-  // 当年
-  if (currentYear == srcYear) {
-    let currentTimestamp = currentDate.getTime()
-    let srcTimestamp = timestamp
-    // 相差时间(单位:毫秒)
-    let deltaTime = currentTimestamp - srcTimestamp
-
-    // 当天(月份和日期一致才是)
-    if (currentMonth == srcMonth && currentDateD == srcDateD) {
-      // 时间相差60秒以内
-      if (deltaTime < 60 * 1000) ret = '刚刚'
-      // 否则当天其它时间段的,直接显示“时:分”的形式
-      else ret = formatDate(srcDate, 'hh:mm')
-    }
-    // 当年 && 当天之外的时间(即昨天及以前的时间)
-    else {
-      // 昨天(以“现在”的时候为基准-1天)
-      let yesterdayDate = new Date()
-      yesterdayDate.setDate(yesterdayDate.getDate() - 1)
-
-      // 前天(以“现在”的时候为基准-2天)
-      let beforeYesterdayDate = new Date()
-      beforeYesterdayDate.setDate(beforeYesterdayDate.getDate() - 2)
-
-      // 用目标日期的“月”和“天”跟上方计算出来的“昨天”进行比较,是最为准确的(如果用时间戳差值
-      // 的形式,是不准确的,比如:现在时刻是2019年02月22日1:00、而srcDate是2019年02月21日23:00,
-      // 这两者间只相差2小时,直接用“deltaTime/(3600 * 1000)” > 24小时来判断是否昨天,就完全是扯蛋的逻辑了)
-      if (
-        srcMonth == yesterdayDate.getMonth() + 1 &&
-        srcDateD == yesterdayDate.getDate()
-      )
-        ret = '昨天' + timeExtraStr
-      // -1d
-      // “前天”判断逻辑同上
-      else if (
-        srcMonth == beforeYesterdayDate.getMonth() + 1 &&
-        srcDateD == beforeYesterdayDate.getDate()
-      )
-        ret = '前天' + timeExtraStr
-      // -2d
-      else {
-        // 跟当前时间相差的小时数
-        let deltaHour = deltaTime / (3600 * 1000)
-
-        // 如果小于或等 7*24小时就显示星期几
-        if (deltaHour <= 7 * 24) {
-          let weekday = new Array(7)
-          weekday[0] = '星期日'
-          weekday[1] = '星期一'
-          weekday[2] = '星期二'
-          weekday[3] = '星期三'
-          weekday[4] = '星期四'
-          weekday[5] = '星期五'
-          weekday[6] = '星期六'
-
-          // 取出当前是星期几
-          let weedayDesc = weekday[srcDate.getDay()]
-          ret = weedayDesc + timeExtraStr
-        }
-        // 否则直接显示完整日期时间
-        else {
-          ret = formatDate(srcDate, 'yyyy/M/d') + timeExtraStr
-        }
-      }
-    }
-  }
-  // 往年
-  else {
-    ret = formatDate(srcDate, 'yyyy/M/d') + timeExtraStr
-  }
-
-  return ret
-}

+ 0 - 569
src/utils/functions.js

@@ -1,569 +0,0 @@
-/** 公共方法类 */
-import CryptoJs from 'crypto-js'
-import encHex from 'crypto-js/enc-hex'
-
-/**
- * 人性化时间显示
- *
- * @param {Object} datetime
- */
-export function formateTime(datetime) {
-  if (datetime === null) return ''
-
-  datetime = datetime.replace(/-/g, '/')
-
-  const time = new Date()
-  let outTime = new Date(datetime)
-  if (/^[1-9]\d*$/.test(datetime)) {
-    outTime = new Date(parseInt(datetime) * 1000)
-  }
-
-  if (
-    time.getTime() < outTime.getTime() ||
-    time.getFullYear() === outTime.getFullYear()
-  ) {
-    return parseTime(outTime, '{y}-{m}-{d} {h}:{i}')
-  }
-
-  if (time.getMonth() !== outTime.getMonth()) {
-    return parseTime(outTime, '{m}-{d} {h}:{i}')
-  }
-
-  if (time.getDate() !== outTime.getDate()) {
-    const day = outTime.getDate() - time.getDate()
-    if (day === -1) {
-      return parseTime(outTime, '昨天 {h}:{i}')
-    }
-
-    if (day === -2) {
-      return parseTime(outTime, '前天 {h}:{i}')
-    }
-
-    return parseTime(outTime, '{m}-{d} {h}:{i}')
-  }
-
-  const diff = time.getTime() - outTime.getTime()
-
-  if (time.getHours() !== outTime.getHours() || diff > 30 * 60 * 1000) {
-    return parseTime(outTime, '{h}:{i}')
-  }
-
-  let minutes = outTime.getMinutes() - time.getMinutes()
-  if (minutes === 0) {
-    return '刚刚'
-  }
-
-  minutes = Math.abs(minutes)
-  return `${minutes}分钟前`
-}
-
-/**
- * 格式化文件大小
- *
- * @param {String} value 文件大小(字节)
- */
-export function formateSize(value) {
-  if (value === null || value === '') {
-    return '0'
-  }
-  const unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
-  let index = 0
-  const srcsize = parseFloat(value)
-  index = Math.floor(Math.log(srcsize) / Math.log(1000))
-  let size = srcsize / Math.pow(1000, index)
-  size = size.toFixed(2) // 保留的小数位数
-  return size + unitArr[index]
-}
-/**
- * 获取文件后缀名
- *
- * @param {String} fileName
- */
-export function getFileExt(fileName) {
-  let ext = fileName.split('.')
-  ext = ext[ext.length - 1] // 获取文件后缀名
-  return ext
-}
-
-/**
- * 根据图片url下载图片
- * @param {String} imgsrc
- * @param {String} name
- */
-export function downloadIamge(imgsrc, name) {
-  // 下载图片地址和图片名
-  const image = new Image()
-  // 解决跨域 Canvas 污染问题
-  image.setAttribute('crossOrigin', 'anonymous')
-  image.onload = function() {
-    const canvas = document.createElement('canvas')
-    canvas.width = image.width
-    canvas.height = image.height
-    const context = canvas.getContext('2d')
-    context.drawImage(image, 0, 0, image.width, image.height)
-    const url = canvas.toDataURL('image/png') // 得到图片的base64编码数据
-    const a = document.createElement('a') // 生成一个a元素
-    const event = new MouseEvent('click') // 创建一个单击事件
-    a.download = name || 'photo' // 设置图片名称
-    a.href = url // 将生成的URL设置为a.href属性
-    a.dispatchEvent(event) // 触发a的单击事件
-  }
-  image.src = imgsrc
-}
-
-/**
- * 通过图片url获取图片大小
- *
- * @param {String} imgsrc 例如图片名: D8x5f13a53dbc4b9_350x345.png
- */
-export function getImageInfo(imgsrc) {
-  const data = {
-    width: 0,
-    height: 0
-  }
-
-  const arr = imgsrc.split('_')
-  if (arr.length === 1) return data
-
-  let info = arr[arr.length - 1].match(/\d+x\d+/g)
-  if (info === null) return data
-
-  info = info[0].split('x')
-  return {
-    width: parseInt(info[0]),
-    height: parseInt(info[1])
-  }
-}
-
-/**
- * 时间格式化方法
- *
- * @param {(Object|string|number)} time
- * @param {String} cFormat
- * @returns {String | null}
- */
-export function parseTime(time, cFormat) {
-  if (arguments.length === 0) {
-    return null
-  }
-
-  let date
-  const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
-
-  if (typeof time === 'object') {
-    date = time
-  } else {
-    if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
-      time = parseInt(time)
-    }
-    if (typeof time === 'number' && time.toString().length === 10) {
-      time = time * 1000
-    }
-
-    date = new Date(time.replace(/-/g, '/'))
-  }
-
-  const formatObj = {
-    y: date.getFullYear(),
-    m: date.getMonth() + 1,
-    d: date.getDate(),
-    h: date.getHours(),
-    i: date.getMinutes(),
-    s: date.getSeconds(),
-    a: date.getDay()
-  }
-
-  const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
-    const value = formatObj[key]
-    // Note: getDay() returns 0 on Sunday
-    if (key === 'a') {
-      return ['日', '一', '二', '三', '四', '五', '六'][value]
-    }
-
-    return value.toString().padStart(2, '0')
-  })
-
-  return time_str
-}
-
-/**
- * 去除字符串控制
- *
- * @param {String} str
- */
-export function trim(str, type = null) {
-  if (type) {
-    return str.replace(/(^\s*)|(\s*$)/g, '')
-  } else if (type === 'l') {
-    return str.replace(/(^\s*)/g, '')
-  } else {
-    return str.replace(/(\s*$)/g, '')
-  }
-}
-
-/**
- * 解析url中参数
- *
- * @param {String} url
- * @returns {Object}
- */
-export function param2Obj(url) {
-  const search = url.split('?')[1]
-
-  if (!search) return {}
-
-  return JSON.parse(
-    '{"' +
-      decodeURIComponent(search)
-        .replace(/"/g, '\\"')
-        .replace(/&/g, '","')
-        .replace(/=/g, '":"')
-        .replace(/\+/g, ' ') +
-      '"}'
-  )
-}
-
-/**
- * @param {Object} json
- * @returns {Array}
- */
-export function param(json) {
-  if (!json) return ''
-  return cleanArray(
-    Object.keys(json).map(key => {
-      if (json[key] === undefined) return ''
-
-      return encodeURIComponent(key) + '=' + encodeURIComponent(json[key])
-    })
-  ).join('&')
-}
-
-/**
- * @param {Array} actual
- * @returns {Array}
- */
-export function cleanArray(actual) {
-  const newArray = []
-  for (let i = 0; i < actual.length; i++) {
-    if (actual[i]) {
-      newArray.push(actual[i])
-    }
-  }
-
-  return newArray
-}
-
-/**
- * @param {HTMLElement} element
- * @param {String} className
- */
-export function toggleClass(element, className) {
-  if (!element || !className) {
-    return
-  }
-
-  let classString = element.className
-  const nameIndex = classString.indexOf(className)
-  if (nameIndex === -1) {
-    classString += '' + className
-  } else {
-    classString =
-      classString.substr(0, nameIndex) +
-      classString.substr(nameIndex + className.length)
-  }
-  element.className = classString
-}
-
-/**
- * Check if an element has a class
- *
- * @param {HTMLElement} elm
- * @param {String} cls
- * @returns {Boolean}
- */
-export function hasClass(ele, cls) {
-  return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'))
-}
-
-/**
- * Add class to element
- *
- * @param {HTMLElement} elm
- * @param {String} cls
- */
-export function addClass(ele, cls) {
-  if (!hasClass(ele, cls)) ele.className += ' ' + cls
-}
-
-/**
- * Remove class from element
- *
- * @param {HTMLElement} elm
- * @param {String} cls
- */
-export function removeClass(ele, cls) {
-  if (hasClass(ele, cls)) {
-    const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)')
-    ele.className = ele.className.replace(reg, ' ')
-  }
-}
-
-/**
- * 通过图片Url获取图片等比例缩放的宽度和高度信息
- *
- * @param {String} src
- * @param {Number} width
- */
-export function imgZoom(src, width = 200) {
-  const info = getImageInfo(src)
-
-  if (info.width < width) {
-    return {
-      width: `${info.width}px`,
-      height: `${info.height}px`
-    }
-  }
-
-  return {
-    width: width + 'px',
-    height: parseInt(info.height / (info.width / width)) + 'px'
-  }
-}
-
-/**
- * 获取浏览器光标选中内容
- *
- * @export
- * @returns
- */
-export function getSelection() {
-  return window.getSelection
-    ? window.getSelection().toString()
-    : document.selection.createRange().text
-}
-
-/**
- * 剪贴板复制功能
- *
- * @param {String} value 复制内容
- * @param {Function} callback 复制成功回调方法
- */
-export const copyTextToClipboard = (value, callback) => {
-  const textArea = document.createElement('textarea')
-  textArea.style.background = 'transparent'
-  textArea.value = value
-
-  document.body.appendChild(textArea)
-
-  textArea.select()
-
-  try {
-    document.execCommand('copy')
-    if (callback) callback()
-  } catch (err) {
-    alert('Oops, unable to copy')
-  }
-
-  document.body.removeChild(textArea)
-}
-
-/**
- * 隐藏用户手机号中间四位
- *
- * @param {String} phone  手机号
- */
-export function hidePhone(phone) {
-  return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
-}
-
-/**
- * 人性化显示时间
- *
- * @param {Object} datetime
- */
-export function beautifyTime(datetime = '') {
-  if (datetime === null) {
-    return ''
-  }
-
-  datetime = datetime.replace(/-/g, '/')
-
-  const time = new Date()
-  let outTime = new Date(datetime)
-  if (/^[1-9]\d*$/.test(datetime)) {
-    outTime = new Date(parseInt(datetime) * 1000)
-  }
-
-  if (time.getTime() < outTime.getTime()) {
-    return parseTime(outTime, '{y}/{m}/{d}')
-  }
-
-  if (time.getFullYear() !== outTime.getFullYear()) {
-    return parseTime(outTime, '{y}/{m}/{d}')
-  }
-
-  if (time.getMonth() !== outTime.getMonth()) {
-    return parseTime(outTime, '{m}/{d}')
-  }
-
-  if (time.getDate() !== outTime.getDate()) {
-    const day = outTime.getDate() - time.getDate()
-    if (day === -1) {
-      return parseTime(outTime, '昨天 {h}:{i}')
-    }
-
-    if (day === -2) {
-      return parseTime(outTime, '前天 {h}:{i}')
-    }
-
-    return parseTime(outTime, '{m}-{d}')
-  }
-
-  if (time.getHours() !== outTime.getHours()) {
-    return parseTime(outTime, '{h}:{i}')
-  }
-
-  let minutes = outTime.getMinutes() - time.getMinutes()
-  if (minutes === 0) {
-    return '刚刚'
-  }
-
-  minutes = Math.abs(minutes)
-  return `${minutes}分钟前`
-}
-
-export function getSort(fn) {
-  return function(a, b) {
-    let ret = 0
-
-    if (fn.call(this, a, b)) {
-      ret = -1
-    } else if (fn.call(this, b, a)) {
-      ret = 1
-    }
-
-    return ret
-  }
-}
-
-/**
- * 批量排序
- *
- * @param {*} arr
- */
-export function getMutipSort(arr) {
-  return function(a, b) {
-    let tmp
-    let i = 0
-
-    do {
-      tmp = arr[i++](a, b)
-    } while (tmp === 0 && i < arr.length)
-
-    return tmp
-  }
-}
-
-/**
- * Url 替换超链接
- *
- * @param {String} text 文本
- * @param {String} color 超链接颜色
- */
-export function textReplaceLink(text, color = '#409eff') {
-  const exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gi
-  return text.replace(
-    exp,
-    `<a href='$1' target="_blank" style="color:${color};text-decoration: revert;">$1</a >`
-  )
-}
-
-/**
- * 防抖
- *
- * @param {*} func
- * @param {*} wait
- * @param {*} immediate
- * @returns
- */
-export function debounce(func, wait, immediate) {
-  let timeout
-
-  return function() {
-    const context = this
-    const args = arguments
-
-    if (timeout) clearTimeout(timeout) // timeout 不为null
-    if (immediate) {
-      const callNow = !timeout // 第一次会立即执行,以后只有事件执行后才会再次触发
-      timeout = setTimeout(function() {
-        timeout = null
-      }, wait)
-      if (callNow) func.apply(context, args)
-    } else {
-      timeout = setTimeout(function() {
-        func.apply(context, args)
-      }, wait)
-    }
-  }
-}
-
-export function sha256sum(file) {
-  const sha256 = CryptoJs.algo.SHA256.create()
-  return new Promise((resolve, reject) => {
-    const reader = new FileReader()
-    reader.onload = ({ target }) => {
-      const wordArray = CryptoJs.lib.WordArray.create(target.result)
-      sha256.update(wordArray)
-      resolve()
-    }
-    reader.readAsArrayBuffer(file)
-  })
-}
-
-export function hashFile(file) {
-  /**
-   * 使用指定的算法计算hash值
-   */
-  function hashFileInternal(file, alog) {
-    // 指定块的大小,这里设置为10MB,可以根据实际情况进行配置
-    const chunkSize = 10 * 1024 * 1024
-    let promise = Promise.resolve()
-    // 使用promise来串联hash计算的顺序。因为FileReader是在事件中处理文件内容的,必须要通过某种机制来保证update的顺序是文件正确的顺序
-    for (let index = 0; index < file.size; index += chunkSize) {
-      promise = promise.then(() => hashBlob(file.slice(index, index + chunkSize)))
-    }
-
-    /**
-     * 更新文件块的hash值
-     */
-    function hashBlob(blob) {
-      return new Promise((resolve, reject) => {
-        const reader = new FileReader()
-        reader.onload = ({ target }) => {
-          const wordArray = CryptoJs.lib.WordArray.create(target.result)
-          // 增量更新计算结果
-          alog.update(wordArray)
-          resolve()
-        }
-        reader.readAsArrayBuffer(blob)
-      })
-    }
-
-    // 使用promise返回最终的计算结果
-    return promise.then(() => encHex.stringify(alog.finalize()))
-  }
-
-  // 同时计算文件的sha256和md5,并使用promise返回
-  /* return Promise.all([hashFileInternal(file, CryptoJs.algo.SHA256.create()),
-    hashFileInternal(file, CryptoJs.algo.MD5.create())])
-    .then(([sha256sum, md5sum]) => ({
-      sha256sum,
-      md5sum
-    }))*/
-  return Promise.all([hashFileInternal(file, CryptoJs.algo.SHA256.create()),
-    hashFileInternal(file, CryptoJs.algo.MD5.create())])
-    .then(([sha256sum]) => ({
-      sha256sum
-    }))
-}

+ 0 - 12
src/utils/icons.js

@@ -1,12 +0,0 @@
-/**
- * Custom icon list
- * All icons are loaded here for easy management
- *
- * 自定义图标加载表
- * 所有图标均从这里加载,方便管理
- */
-import SvgNotFount from '@/icons/svg/not-fount.svg?inline' // path to your '*.svg?inline' file.
-
-export {
-  SvgNotFount,
-}

+ 0 - 94
src/utils/lazy-use.js

@@ -1,94 +0,0 @@
-import Vue from 'vue'
-import 'element-ui/lib/theme-chalk/index.css'
-
-import {
-  Notification,
-  Popover,
-  Switch,
-  Dropdown,
-  DropdownMenu,
-  DropdownItem,
-  Message,
-  Container,
-  Header,
-  Aside,
-  Main,
-  Footer,
-  Menu,
-  Submenu,
-  MenuItem,
-  MenuItemGroup,
-  Button,
-  Image,
-  Loading,
-  Row,
-  Col,
-  MessageBox,
-  Form,
-  FormItem,
-  Input,
-  Divider,
-  Link,
-  Tooltip,
-  Autocomplete,
-  Scrollbar,
-  Avatar,
-  Radio,
-  RadioGroup,
-  Progress,
-  Dialog,
-  Checkbox,
-  Table,
-  TableColumn,
-  Breadcrumb,
-  BreadcrumbItem,
-  Pagination,
-} from 'element-ui'
-
-Vue.use(Popover)
-Vue.use(Switch)
-Vue.use(Dropdown)
-Vue.use(DropdownMenu)
-Vue.use(DropdownItem)
-Vue.use(Container)
-Vue.use(Header)
-Vue.use(Aside)
-Vue.use(Main)
-Vue.use(Footer)
-Vue.use(Menu)
-Vue.use(Submenu)
-Vue.use(MenuItem)
-Vue.use(MenuItemGroup)
-Vue.use(Button)
-Vue.use(Image)
-Vue.use(Row)
-Vue.use(Col)
-Vue.use(Input)
-Vue.use(Form)
-Vue.use(FormItem)
-Vue.use(Divider)
-Vue.use(Link)
-Vue.use(Tooltip)
-Vue.use(Autocomplete)
-Vue.use(Scrollbar)
-Vue.use(Avatar)
-Vue.use(Radio)
-Vue.use(Checkbox)
-Vue.use(RadioGroup)
-Vue.use(Progress)
-Vue.use(Dialog)
-Vue.use(Loading.directive)
-Vue.use(Table)
-Vue.use(TableColumn)
-Vue.use(Breadcrumb)
-Vue.use(BreadcrumbItem)
-Vue.use(Pagination)
-
-Vue.prototype.$notify = Notification
-Vue.prototype.$message = Message
-Vue.prototype.$confirm = MessageBox.confirm
-Vue.prototype.$prompt = MessageBox.prompt
-Vue.prototype.$alert = MessageBox.alert
-Vue.prototype.$dialog = MessageBox.Dialog
-
-process.env.NODE_ENV !== 'production' && console.warn('[Lumen-IM] NOTICE: element-ui use lazy-load.')

+ 8 - 18
src/utils/request.js

@@ -2,7 +2,7 @@ import axios from 'axios'
 import store from '@/store'
 import Vue from 'vue'
 import router from '@/router'
-import { getAccessToken, getBrowserFingerprint, removeAll } from '@/utils/auth'
+import { getBrowserFingerprint } from '@/utils/util'
 
 const instance = axios.create({
   // 域名
@@ -24,11 +24,11 @@ const loading = null
 // 请求拦截器
 instance.interceptors.request.use(config => {
   // const token = store.getters.token
-  const token = getAccessToken()
+  /* const token = getAccessToken()
   if (token) {
     // 在请求的 Authorization 首部添加 token
     config.headers.Authorization = 'Bearer ' + token
-  }
+  }*/
   if (Vue.$cookies.get('fp') === null) {
     getBrowserFingerprint()
   }
@@ -59,13 +59,10 @@ instance.interceptors.response.use(
     if (error.response) {
       switch (error.response.status) {
         case 401:
-          // 返回 401 清除token信息并跳转到登陆页面
-          console.log('401 错误')
-
-          Vue.$cookies.remove('token')
+          // 返回 401 时清除之前的登入信息并跳转到登入页面
+          Vue.$cookies.remove('access_token')
+          Vue.$cookies.remove('refresh_token')
           store.commit('USER_LOGOUT')
-          store.commit('delToken')
-          removeAll()
 
           router.replace({
             path: '/login',
@@ -74,19 +71,12 @@ instance.interceptors.response.use(
             }
           })
           break
-        case 404:
-          console.log('404 错误')
-          break
         default:
-          console.log(error.message)
+          this.$message.error(error.message)
       }
     } else {
       // 请求超时或者网络有问题
-      if (error.message.includes('timeout')) {
-        console.log(error.message)
-      } else {
-        console.log(error.message)
-      }
+      this.$message.error(error.message)
     }
     return Promise.reject(error)
   }

+ 0 - 76
src/utils/sms-lock.js

@@ -1,76 +0,0 @@
-/**
- * 短信倒计时锁
- */
-class SmsLock {
-  // 发送倒计时默认60秒
-  time = null
-
-  // 计时器
-  timer = null
-
-  // 倒计时默认60秒
-  lockTime = 60
-
-  // 锁标记名称
-  lockName = ''
-
-  /**
-   * 实例化构造方法
-   *
-   * @param {String} purpose 唯一标识
-   * @param {Number} time
-   */
-  constructor(purpose, lockTime = 60) {
-    this.lockTime = lockTime
-    this.lockName = `SMSLOCK_${purpose}`
-
-    this.init()
-  }
-
-  // 开始计时
-  start(time = null) {
-    this.time = time == null || time >= this.lockTime ? this.lockTime : time
-
-    this.clearInterval()
-
-    this.timer = setInterval(() => {
-      if (this.time == 0) {
-        this.clearInterval()
-        this.time = null
-        localStorage.removeItem(this.lockName)
-        return
-      }
-
-      this.time--
-
-      // 设置本地缓存
-      localStorage.setItem(this.lockName, this.getTime() + this.time)
-    }, 1000)
-  }
-
-  // 页面刷新初始化
-  init() {
-    let result = localStorage.getItem(this.lockName)
-
-    if (result == null) return
-
-    let time = result - this.getTime()
-    if (time > 0) {
-      this.start(time)
-    } else {
-      localStorage.removeItem(this.lockName)
-    }
-  }
-
-  // 获取当前时间
-  getTime() {
-    return Math.floor(new Date().getTime() / 1000)
-  }
-
-  // 清除计时器
-  clearInterval() {
-    clearInterval(this.timer)
-  }
-}
-
-export default SmsLock

+ 72 - 0
src/utils/util.js

@@ -1,3 +1,75 @@
+import Vue from 'vue'
+import FingerprintJS from '@fingerprintjs/fingerprintjs'
+import CryptoJs from 'crypto-js'
+import encHex from 'crypto-js/enc-hex'
+
+/**
+ * 浏览器指纹
+ */
+export async function getBrowserFingerprint() {
+  // 初始化FingerprintJS
+  const fp = await FingerprintJS.load()
+  // 获取访问者的指纹
+  const result = await fp.get()
+  const {
+    plugins,
+    ...components
+  } = result.components
+  const extendedComponents = {
+    ...components
+  }
+  // const deviceInfo = JSON.stringify(extendedComponents)
+  const fingerprintId = FingerprintJS.hashComponents(extendedComponents)
+  Vue.$cookies.set('fp', fingerprintId, -1)
+}
+
+export function hashFile(file) {
+  /**
+   * 使用指定的算法计算hash值
+   */
+  function hashFileInternal(file, alog) {
+    // 指定块的大小,这里设置为10MB,可以根据实际情况进行配置
+    const chunkSize = 10 * 1024 * 1024
+    let promise = Promise.resolve()
+    // 使用promise来串联hash计算的顺序。因为FileReader是在事件中处理文件内容的,必须要通过某种机制来保证update的顺序是文件正确的顺序
+    for (let index = 0; index < file.size; index += chunkSize) {
+      promise = promise.then(() => hashBlob(file.slice(index, index + chunkSize)))
+    }
+
+    /**
+     * 更新文件块的hash值
+     */
+    function hashBlob(blob) {
+      return new Promise((resolve, reject) => {
+        const reader = new FileReader()
+        reader.onload = ({ target }) => {
+          const wordArray = CryptoJs.lib.WordArray.create(target.result)
+          // 增量更新计算结果
+          alog.update(wordArray)
+          resolve()
+        }
+        reader.readAsArrayBuffer(blob)
+      })
+    }
+
+    // 使用promise返回最终的计算结果
+    return promise.then(() => encHex.stringify(alog.finalize()))
+  }
+
+  // 同时计算文件的sha256和md5,并使用promise返回
+  /* return Promise.all([hashFileInternal(file, CryptoJs.algo.SHA256.create()),
+    hashFileInternal(file, CryptoJs.algo.MD5.create())])
+    .then(([sha256sum, md5sum]) => ({
+      sha256sum,
+      md5sum
+    }))*/
+  return Promise.all([hashFileInternal(file, CryptoJs.algo.SHA256.create()),
+    hashFileInternal(file, CryptoJs.algo.MD5.create())])
+    .then(([sha256sum]) => ({
+      sha256sum
+    }))
+}
+
 /**
  * 时间戳
  * @param {*} timestamp  时间戳

+ 0 - 54
src/utils/validate.js

@@ -1,54 +0,0 @@
-/**
- * 检测是否是字邮箱地址
- *
- * @param {String} value
- */
-export const isEmail = value => {
-  return /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/.test(
-    value
-  )
-}
-
-/**
- * 检测是否是手机号
- *
- * @param {String} value
- */
-export const isMobile = value => {
-  return /^1[0-9]{10}$/.test(value)
-}
-
-/**
- * 检测是否为url
- *
- * @param {String} value
- */
-export const isURL = value => {
-  return /^http[s]?:\/\/.*/.test(value)
-}
-
-/**
- * 检测是否为数字类型
- *
- * @param {*} value
- */
-export const isNumber = value => {
-  return Object.prototype.toString.call(value).slice(8, -1) === 'Number'
-}
-
-/**
- * 检测是否为 Booleanl 类型
- *
- * @param {*} value
- */
-export const isBoolean = value => {
-  return Object.prototype.toString.call(value).slice(8, -1) === 'Boolean'
-}
-
-/**
- * 检测是非是微信浏览器
- */
-export const isWeiXin = () => {
-  let ua = navigator.userAgent.toLowerCase()
-  return ua.match(/microMessenger/i) == 'micromessenger'
-}

+ 0 - 61
src/utils/ws/event/base.js

@@ -1,61 +0,0 @@
-import store from '@/store'
-import router from '@/router'
-import { Notification } from 'element-ui'
-
-class Base {
-  /**
-   * 初始化
-   */
-  constructor() {
-    this.$notify = Notification
-  }
-
-  getStoreInstance() {
-    return store
-  }
-
-  /**
-   * 获取当前登入用户的ID
-   */
-  getAccountId() {
-    return store.state.user.uid
-  }
-
-  getTalkParams() {
-    let { talkType, receiverId, indexName } = store.state.dialogue
-
-    return { talkType, receiverId, indexName }
-  }
-
-  /**
-   * 判断消息是否来自当前对话
-   *
-   * @param {Number} talkType 聊天消息类型[1:私信;2:群聊;]
-   * @param {Number} senderId 发送者ID
-   * @param {Number} receiverId 接收者ID
-   */
-  isTalk(talkType, senderId, receiverId) {
-    let params = this.getTalkParams()
-
-    if (talkType != params.talkType) {
-      return false
-    } else if (
-      params.receiverId == receiverId ||
-      params.receiverId == senderId
-    ) {
-      return true
-    }
-
-    return false
-  }
-
-  /**
-   * 判断用户是否打开对话页
-   */
-  isTalkPage() {
-    let path = router.currentRoute.path
-    return !(path != '/message' && path != '/')
-  }
-}
-
-export default Base

+ 0 - 69
src/utils/ws/event/talk.js

@@ -1,69 +0,0 @@
-import store from '@/store'
-import Base from './base'
-
-/**
- * 好友状态事件
- */
-class Talk extends Base {
-  /**
-   * @var resource 资源
-   */
-  resource
-
-  /**
-   * 发送者ID
-   */
-  senderId = 0
-
-  /**
-   * 接收者ID
-   */
-  receiverId = 0
-
-  /**
-   * 聊天类型[1:私聊;2:群聊;]
-   */
-  talkType = 0
-
-  /**
-   * 初始化构造方法
-   *
-   * @param {Object} resource Socket消息
-   */
-  constructor(resource) {
-    super()
-    this.senderId = resource.senderId
-    this.receiverId = resource.receiverId
-    this.talkType = resource.talkType
-    this.resource = resource
-  }
-
-  /**
-   * 处理 event
-   * @returns
-   */
-  handle() {
-    console.log(this.resource)
-    var eventMessage = JSON.parse(this.resource)
-    var event = eventMessage.event
-    var payload = eventMessage.payload
-
-    const receiverId = payload.receiverId
-    var justify = 'start'
-    if (receiverId === this.userId) {
-      justify = 'end'
-    }
-
-    var msg = {}
-    msg.senderId = payload.senderId
-    msg.content = payload.content
-    var item = {
-      content: msg,
-      justify: justify
-    }
-    console.log(item)
-    store.commit('PUSH_TALK_ITEM', item)
-  }
-}
-
-export default Talk

+ 0 - 13
src/utils/ws/socket-instance.js

@@ -1,6 +1,5 @@
 import store from '@/store'
 import WsSocket from '@/utils/ws/ws-socket'
-import TalkEvent from '@/utils/ws/event/talk'
 
 /**
  * SocketInstance 连接实例
@@ -55,18 +54,6 @@ class SocketInstance {
       console.log(data)
     })
     this.socket.on('event_error', (payload, data) => {})
-    this.socket.on('event_talk', (payload, data) => {
-      new TalkEvent(data).handle()
-      /* const title = payload.title
-      const content = payload.content
-      this.sendNotification(title, content)
-      Notification({
-        title: payload.title,
-        message: payload.content.substring(0, 30),
-        type: 'warning',
-        duration: 5000
-      })*/
-    })
   }
   /* sendNotification(title, content) {
     new Notification(title, {

+ 2 - 3
src/views/admin/Background.vue

@@ -88,7 +88,6 @@
 <script>
 import store from '@/store'
 import { userMixin } from 'assets/js/mixin'
-import { getAuthedUser } from '@/utils/auth'
 import { getUnreadCount } from '@/api/user'
 
 export default {
@@ -103,7 +102,7 @@ export default {
     }
   },
   created() {
-    this.user = getAuthedUser()
+    this.user = this.$store.state.userInfo
     if (this.user !== null) {
       getUnreadCount().then(resp => {
         if (resp.code === 0) {
@@ -128,7 +127,7 @@ export default {
       this.$router.push('/')
     },
     goToMessage() {
-      this.$router.push('/bg/my/message')
+      this.$router.push('/bg/account/message')
     }
   }
 }

+ 5 - 5
src/views/admin/Dashboard.vue

@@ -182,10 +182,10 @@
 
 <script>
 import { userMixin } from 'assets/js/mixin'
-import { getAuthedUser, updateAuthedUser } from '@/utils/auth'
 import { updateAvatar } from '@/api/account' // 假设你的其它更新 API 也在 account 或对应模块里
 import { getAvatarChannelInfo } from '@/api/file'
 import { addApprovalRequest } from '@/api/user'
+import store from '@/store'
 
 export default {
   name: 'Dashboard',
@@ -242,7 +242,7 @@ export default {
   methods: {
     initUserData() {
       document.title = 'Dashboard'
-      const user = getAuthedUser()
+      const user = this.$store.state.userInfo
       if (user) {
         this.loginUser = user
         this.roles = user.roles || []
@@ -293,7 +293,7 @@ export default {
         // 1. 动态同步到本地模型
         this.loginUser[this.editModel.field] = this.editModel.value
         // 2. 持久化到 localStorage/cookie 缓存
-        updateAuthedUser(this.loginUser)
+        store.commit('UPDATE_USER_INFO', this.loginUser)
 
         this.$message.success(`${this.editModel.title}已成功修改`)
         this.editDialogVisible = false
@@ -312,7 +312,7 @@ export default {
     saveSignature() {
       // 触发后端 API 保存签名逻辑
       this.$message.success('个性签名已同步更新')
-      updateAuthedUser(this.loginUser)
+      store.commit('UPDATE_USER_INFO', this.loginUser)
       this.isEditingSignature = false
     },
     cancelEditSignature() {
@@ -400,7 +400,7 @@ export default {
       updateAvatar(postParam).then(resp => {
         if (resp.code === 0) {
           this.loginUser.avatarUrl = resp.data.avatarUrl
-          updateAuthedUser(this.loginUser)
+          store.commit('UPDATE_USER_INFO', this.loginUser)
           this.$message.success('个人头像已成功同步至云端')
           this.avatarDialogVisible = false
           this.clearUploadStates()

+ 1 - 2
src/views/blog/BlogImage.vue

@@ -66,7 +66,6 @@
 
 <script>
 import { getBlogImageList } from '@/api/blog'
-import { getAccessToken } from '@/utils/auth'
 
 export default {
   name: 'BlogImage',
@@ -92,7 +91,7 @@ export default {
     }
   },
   created() {
-    this.imgHeaders.Authorization = 'Bearer ' + getAccessToken()
+    this.imgHeaders.Authorization = 'Bearer '
     const pageNumber = this.$route.query.pn
     if (pageNumber !== undefined && pageNumber !== null) {
       this.currentPage = parseInt(pageNumber)

+ 1 - 2
src/views/chat/ChatDialogue.vue

@@ -219,7 +219,6 @@
 
 <script>
 import { getChatDialogue, getChatRecords, updateChatDialogue } from '@/api/chat'
-import { getAuthedUser } from '@/utils/auth'
 
 export default {
   name: 'ChatDialogue',
@@ -286,7 +285,7 @@ export default {
     }
   },
   created() {
-    this.my = getAuthedUser()
+    this.my = this.$store.state.userInfo
     this.parseRouteParams()
   },
   methods: {

+ 1 - 3
src/views/chat/ChatMe.vue

@@ -46,8 +46,6 @@
 </template>
 
 <script>
-import { getAuthedUser } from '@/utils/auth'
-
 export default {
   name: 'ChatMe',
   data() {
@@ -56,7 +54,7 @@ export default {
     }
   },
   created() {
-    this.my = getAuthedUser()
+    this.my = this.$store.state.userInfo
   },
   methods: {
     goToProfileDetail() {

+ 1 - 2
src/views/disk/Disk.vue

@@ -111,7 +111,6 @@
 
 <script>
 import { userMixin } from 'assets/js/mixin'
-import { getAuthedUser } from '@/utils/auth'
 import { submitActivity } from '@/api/disk'
 
 export default {
@@ -130,7 +129,7 @@ export default {
     }
   },
   created() {
-    this.user = getAuthedUser()
+    this.user = this.$store.state.userInfo
   },
   methods: {
     handleNotifyChange(val) {

+ 1 - 1
src/views/disk/DiskFile.vue

@@ -214,7 +214,7 @@ import {
   getFileDetail,
   getFolderTree, moveFile
 } from '@/api/disk'
-import { hashFile } from '@/utils/functions'
+import { hashFile } from '@/utils/util'
 
 export default {
   name: 'DiskFile',

+ 1 - 2
src/views/disk/DiskMe.vue

@@ -85,7 +85,6 @@
 </template>
 
 <script>
-import { getAuthedUser } from '@/utils/auth'
 // 假设你有一个提交状态的接口
 import { submitActivity } from '@/api/disk'
 
@@ -112,7 +111,7 @@ export default {
     }
   },
   created() {
-    this.user = getAuthedUser()
+    this.user = this.$store.state.userInfo
   },
   methods: {
     handleAction(item) {

+ 1 - 2
src/views/diskm/Disk.vue

@@ -69,7 +69,6 @@
 
 <script>
 import { userMixin } from 'assets/js/mixin'
-import { getAuthedUser } from '@/utils/auth'
 import { submitActivity } from '@/api/disk'
 
 export default {
@@ -93,7 +92,7 @@ export default {
     }
   },
   created() {
-    this.user = getAuthedUser()
+    this.user = this.$store.state.userInfo
   },
   methods: {
     handleTabClick(path) {

+ 1 - 1
src/views/diskm/DiskFile.vue

@@ -164,7 +164,7 @@
 
 <script>
 import { addFile, createFolder, deleteFile, getDiskChannelInfo, getDiskFile, getFileDetail } from '@/api/disk'
-import { hashFile } from '@/utils/functions'
+import { hashFile } from '@/utils/util'
 
 export default {
   name: 'DiskFileMobile',

+ 1 - 3
src/views/diskm/DiskMe.vue

@@ -63,8 +63,6 @@
 </template>
 
 <script>
-import { getAuthedUser } from '@/utils/auth'
-
 export default {
   name: 'DiskMeMobile',
   data() {
@@ -85,7 +83,7 @@ export default {
     }
   },
   created() {
-    this.user = getAuthedUser()
+    this.user = this.$store.state.userInfo
   },
   methods: {
     handleAction(item) {

+ 1 - 2
src/views/exam/Exam.vue

@@ -97,7 +97,6 @@
 
 <script>
 import { userMixin } from 'assets/js/mixin'
-import { getAuthedUser } from '@/utils/auth'
 
 export default {
   name: 'Exam',
@@ -109,7 +108,7 @@ export default {
   },
   created() {
     document.title = '测评系统'
-    this.user = getAuthedUser()
+    this.user = this.$store.state.userInfo
   }
 }
 </script>

+ 1 - 2
src/views/home/ShareVideo.vue

@@ -62,7 +62,6 @@ import DPlayer from 'dplayer'
 import { videoInfo, videoUrl } from '@/api/vod'
 import { getShortVideo } from '@/api/vod'
 import { getUserInfo } from '@/api/user'
-import { getAccessToken } from '@/utils/auth'
 
 export default {
   name: 'ShareVideo',
@@ -85,7 +84,7 @@ export default {
     }
   },
   created() {
-    this.userToken = getAccessToken()
+    this.userToken = ''
     this.shareId = this.$route.params.shareId
     this.getVideoInfo(this.shareId)
   },

+ 1 - 2
src/views/home/Timeline.vue

@@ -99,7 +99,6 @@
 import TextCard from '@/components/card/TextCard'
 import SideVideoCard from '@/components/card/SideVideoCard'
 import { videoTimeline } from '@/api/vod'
-import { getAuthedUser } from '@/utils/auth'
 
 export default {
   name: 'Timeline',
@@ -114,7 +113,7 @@ export default {
     }
   },
   created() {
-    const loginUser = getAuthedUser()
+    const loginUser = this.$store.state.userInfo
     if (!loginUser) {
       this.$router.push('/login')
       return

+ 2 - 3
src/views/home/VideoPage.vue

@@ -131,7 +131,6 @@
 import { similarVideo, videoInfo, downloadVideo, getShortUrl, videoErrorReport } from '@/api/vod'
 import { collectItem, createAlbum, getUserAlbumList } from '@/api/collect'
 import { getUserInfo } from '@/api/user'
-import { getAccessToken, getAuthedUser } from '@/utils/auth'
 import { dislikeVideo } from '@/api/content'
 
 import PermissionDeniedCard from '@/components/card/PermissionDeniedCard'
@@ -173,8 +172,8 @@ export default {
     '$route'() { this.$router.go() }
   },
   created() {
-    this.userToken = getAccessToken()
-    const loginUser = getAuthedUser()
+    this.userToken = ''
+    const loginUser = this.$store.state.userInfo
     if (loginUser) {
       this.currentUser = { userId: loginUser.userId, name: loginUser.screenName, avatar: loginUser.avatarUrl, author: true }
     }