li.shaoyi hace 2 años
padre
commit
edd5f4264d

+ 0 - 123
src/business/auth/index.ts

@@ -1,123 +0,0 @@
-import { ref } from 'vue'
-import { v4 } from 'uuid'
-import { timerTask } from '@/utils/timer'
-import { enumStore, errorInfoStore, loginStore, userStore, futuresStore, accountStore } from '@/stores'
-import { checkTokenLoop } from '@/business/common'
-import socket from '@/services/socket'
-import cryptojs from 'crypto-js'
-
-// AES加密处理
-const encrypt = (data: string, key: string) => {
-    const { enc: { Utf8 }, AES } = cryptojs
-    // 统一将传入的字符串转成UTF8编码
-    const dataHex = Utf8.parse(data) // 需要加密的数据
-    const keyHex = Utf8.parse(key) // 秘钥
-    const ivHex = Utf8.parse(key) // 偏移量
-    const encrypted = AES.encrypt(dataHex, keyHex, { iv: ivHex, })
-    return encrypted.ciphertext.toString() //  返回加密后的值
-}
-
-// AES解密处理
-const decrypt = (encryptedData: string, key: string) => {
-    const { enc: { Utf8, Hex, Base64 }, AES } = cryptojs
-    // 统一将传入的字符串转成UTF8编码
-    const encryptedHexStr = Hex.parse(encryptedData)
-    const srcs = Base64.stringify(encryptedHexStr)
-    const keyHex = Utf8.parse(key) // 秘钥
-    const ivHex = Utf8.parse(key) // 偏移量
-    const decrypt = AES.decrypt(srcs, keyHex, { iv: ivHex })
-    return decrypt.toString(Utf8).toString()
-}
-
-function useAutoLogin() {
-    // 获取自动登录数据
-    const getAutoLoginData = (autoLogin = false) => {
-        const autologinKey = localStorage.getItem('thj_autologin_key') // 自动登录密钥
-        const autologinData = localStorage.getItem('thj_autologin_data') // 自动登录数据
-        const loginData: Proto.LoginReq = {
-            LoginID: '',
-            LoginPWD: '',
-            GUID: '',
-            LoginType: 0,
-            ClientType: 4,
-            Version: '2.0.0.0',
-            DeviceID: ''
-        }
-        if (autologinKey && autologinData) {
-            const decryptedData = decrypt(autologinData, autologinKey)
-            if (decryptedData) {
-                const loginReq: Proto.LoginReq = JSON.parse(decryptedData)
-                // 自动登录
-                if (autoLogin) {
-                    return loginReq
-                } else {
-                    loginData.LoginID = loginReq.LoginID
-                }
-            }
-        }
-        return loginData
-    }
-
-    // 保存自动登录数据
-    const setAutoLoginData = (data?: Proto.LoginReq) => {
-        const loginKey = v4() // 随机密钥
-        const loginData = data ?? getAutoLoginData(false)
-        const encryptedData = encrypt(JSON.stringify(loginData), loginKey)
-        localStorage.setItem('thj_autologin_key', loginKey)
-        localStorage.setItem('thj_autologin_data', encryptedData)
-    }
-
-    return {
-        getAutoLoginData,
-        setAutoLoginData
-    }
-}
-
-export function useAuth(autoLogin = false) {
-    const { getAutoLoginData, setAutoLoginData } = useAutoLogin()
-    const loading = ref(false)
-    const user = ref<Proto.LoginReq>(getAutoLoginData(autoLogin))
-
-    // 用户登录
-    const login = async () => {
-        try {
-            loading.value = true
-            await enumStore.actions.getAllEnumList()
-            await errorInfoStore.actions.getErrorInfoList()
-
-            const { LoginID, LoginPWD } = user.value
-            if (LoginID && LoginPWD) {
-                await loginStore.actions.userLogin(user.value)
-                await Promise.all([userStore.actions.getUserData(), futuresStore.actions.getGoodsList()])
-
-                accountStore.actions.getAccountList()
-                setAutoLoginData(user.value)
-                checkTokenLoop()
-            } else {
-                throw '登录失败'
-            }
-        } catch (err) {
-            logout(() => loading.value = false)
-            return Promise.reject(err)
-        }
-    }
-
-    // 用户登出
-    const logout = (callback?: () => void) => {
-        const { setAutoLoginData } = useAutoLogin()
-
-        setAutoLoginData()
-        socket.closeAll()
-        timerTask.clearAll()
-        loginStore.actions.reset()
-        accountStore.actions.reset()
-        callback && callback()
-    }
-
-    return {
-        loading,
-        user,
-        login,
-        logout,
-    }
-}

+ 5 - 22
src/business/common/index.ts

@@ -1,27 +1,10 @@
 import { timerTask } from '@/utils/timer'
-import { enumStore, errorInfoStore, loginStore, userStore, futuresStore, menuStore, accountStore } from '@/stores'
+import { loginStore } from '@/stores'
 import { tokenCheck } from '@/services/api/account'
 import eventBus from '@/services/bus'
 
 /**
- * 初始化业务数据(暂无用,后期优化废除)
- */
-export async function initBaseData() {
-    await enumStore.actions.getAllEnumList()
-    await errorInfoStore.actions.getErrorInfoList()
-
-    if (loginStore.getters.token) {
-        await Promise.all([
-            userStore.actions.getUserData(),
-            menuStore.actions.getUserMenuList(),
-            futuresStore.actions.getGoodsList(),
-        ])
-        accountStore.actions.getAccountList()
-    }
-}
-
-/**
- * 令牌效验
+ * 令牌校验
  */
 export function checkToken() {
     const { loginId, token } = loginStore.$mapGetters()
@@ -35,17 +18,17 @@ export function checkToken() {
 }
 
 /**
- * 轮询验令牌
+ * 轮询验令牌
  */
 export function checkTokenLoop() {
-    const delay = 1 * 60 * 1000 // 每1分钟验一次令牌
+    const delay = 1 * 60 * 1000 // 每1分钟验一次令牌
     timerTask.setTimeout(() => {
         checkToken().then(() => checkTokenLoop())
     }, delay, 'checkToken')
 }
 
 /**
- * 停止令牌
+ * 停止令牌
  */
 export function stopCheckToken() {
     timerTask.clearTimeout('checkToken')

+ 125 - 0
src/business/login/index.ts

@@ -0,0 +1,125 @@
+import { reactive } from 'vue'
+import { v4 } from 'uuid'
+import { encryptAES, decryptAES } from '@/utils/crypto'
+import { timerTask } from '@/utils/timer'
+import { queryLoginId, login } from '@/services/api/account'
+import { sessionData, localData } from '@/stores/storage'
+import { loginStore, enumStore, errorInfoStore, userStore, futuresStore, accountStore } from '@/stores'
+import { checkToken, checkTokenLoop } from '@/business/common'
+import service from '@/services'
+import socket from '@/services/socket'
+import eventBus from '@/services/bus'
+import cryptojs from 'crypto-js'
+
+export function useLogin() {
+    const { logining } = loginStore.$mapState()
+    const { token } = loginStore.$mapGetters()
+
+    const formData = reactive<Proto.LoginReq>({
+        LoginID: localStorage.getItem('thj_loginId') ?? '',
+        LoginPWD: '',
+        GUID: v4(),
+        LoginType: 0,
+        ClientType: 4,
+        Version: '2.0.0.0',
+        DeviceID: ''
+    })
+
+    const aa = async () => {
+        // 等待服务初始化
+        await service.onReady()
+        await Promise.all([
+            errorInfoStore.actions.getErrorInfoList(),
+            enumStore.actions.getAllEnumList(),
+        ])
+    }
+
+    const bb = async () => {
+        await checkToken() // 令牌校验
+        await userStore.actions.getUserData()
+        futuresStore.actions.getGoodsList()
+        accountStore.actions.getAccountList()
+        checkTokenLoop()
+    }
+
+    const loginAction = async (params: Proto.LoginReq) => {
+        params.GUID = v4()
+        await login({
+            data: params,
+            success: async (res) => {
+                const encryptedData = encryptAES(JSON.stringify(params)) // 数据加密
+                localData.setValue('autoLoginEncryptedData', encryptedData)
+                loginStore.$setState((state) => {
+                    state.loginInfo = res
+                })
+                localStorage.setItem('thj_loginId', formData.LoginID) // 记住登录ID
+            }
+        })
+        await bb()
+        eventBus.$emit('LoginNotify') // 登录成功通知
+    }
+
+    // 初始化业务数据
+    const initBaseData = async (autoLogin = false) => {
+        logining.value = true
+        try {
+            // 等待加载业务数据
+            await aa()
+            // 自动登录
+            if (autoLogin) {
+                const encryptedData = localData.getValue('autoLoginEncryptedData')
+                if (encryptedData) {
+                    try {
+                        const decryptedString = decryptAES(encryptedData)
+                        return await loginAction(JSON.parse(decryptedString))
+                    } catch (err) {
+                        console.error(err)
+                        eventBus.$emit('LogoutNotify')
+                    }
+                }
+            } else if (token.value) {
+                await bb()
+            }
+        } finally {
+            logining.value = false
+        }
+    }
+
+    // 用户登录
+    const userLogin = async () => {
+        logining.value = true
+        try {
+            const params = { ...formData }
+            await aa()
+            await queryLoginId({
+                data: {
+                    username: formData.LoginID
+                },
+                success: (res) => {
+                    params.LoginID = res.data
+                    params.LoginPWD = cryptojs.SHA256(res.data + formData.LoginPWD).toString()
+                }
+            })
+            return await loginAction(params)
+        } finally {
+            logining.value = false
+        }
+    }
+
+    // 用户登出
+    const userLogout = (callback?: () => void) => {
+        socket.closeAll()
+        timerTask.clearAll()
+        sessionData.reset()
+        localData.reset('autoLoginEncryptedData')
+        callback && callback()
+    }
+
+    return {
+        logining,
+        formData,
+        initBaseData,
+        userLogin,
+        userLogout,
+    }
+}

+ 3 - 4
src/hooks/navigation/index.ts

@@ -66,11 +66,10 @@ export function useNavigation() {
     // 返回主页
     const backHomePage = <T extends object>(params?: T) => {
         const { state } = animateRouter
-        const routeHistory = state.history.length - 1
-
+        const delta = state.history.length - 1
         setGlobalUrlParams(params ?? {})
-        if (routeHistory) {
-            router.go(-routeHistory)
+        if (delta) {
+            router.go(-delta)
         }
     }
 

+ 3 - 3
src/packages/mobile/App.vue

@@ -6,10 +6,10 @@
 import { shallowRef, nextTick } from 'vue'
 import { useNavigation } from '@/hooks/navigation'
 import { dialog } from '@/utils/vant'
-import { useAuth } from '@/business/auth'
+import { useLogin } from '@/business/login'
 import eventBus from '@/services/bus'
 
-const { logout } = useAuth()
+const { userLogout } = useLogin()
 const { backHomePage } = useNavigation()
 const isRouterAlive = shallowRef(true) // 用于页面重新渲染
 
@@ -23,7 +23,7 @@ const quit = (showLogin = false) => {
 
 // 接收用户登出通知
 eventBus.$on('LogoutNotify', (msg) => {
-  logout(() => {
+  userLogout(() => {
     if (msg) {
       dialog({
         message: msg as string,

+ 0 - 82
src/packages/mobile/views/boot/index.backup.vue

@@ -1,82 +0,0 @@
-<template>
-  <div class="boot">
-    <Swipe class="boot__guide" :loop="false" v-if="guidePage">
-      <SwipeItem>
-        <img src="@mobile/assets/images/boot-1080p.png" />
-      </SwipeItem>
-      <SwipeItem>
-        <img src="@mobile/assets/images/guide-1.png" />
-      </SwipeItem>
-      <SwipeItem>
-        <img src="@mobile/assets/images/guide-2.png" @click="skip" />
-      </SwipeItem>
-    </Swipe>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import { reactive } from 'vue'
-import { useRoute, useRouter } from 'vue-router'
-import { Swipe, SwipeItem } from 'vant'
-import { initBaseData } from '@/business/common'
-import service from '@/services'
-import socket from '@/services/socket'
-import plus from '@/utils/h5plus'
-
-const route = useRoute()
-const router = useRouter()
-const guidePage = localStorage.getItem('thj_app_guide') === 'false' ? false : true // 是否显示引导页
-const countdown = 1  // 倒计时秒数
-
-const state = reactive({
-  loading: true,
-  second: countdown, // 剩余秒数
-  currentRate: 100, // 当前进度
-  rate: 100, // 目标进度
-})
-
-// 倒计时
-const timer = window.setInterval(() => {
-  state.second--
-  state.rate = (100 / countdown) * state.second
-  if (state.second <= 0) {
-    // 判断是否首次打开应用
-    if (guidePage) {
-      localStorage.setItem('thj_app_guide', 'false')
-    } else {
-      skip()
-    }
-  }
-}, 1000)
-
-// 跳过广告
-const skip = () => {
-  clearInterval(timer)
-  plus.exitFullSreen()
-  // 判断服务是否初始化完成
-  if (state.loading) {
-    router.replace('/user/login')
-  } else {
-    const redirect = route.query.redirect
-    if (redirect) {
-      router.replace(redirect.toString())
-    } else {
-      router.replace('/')
-    }
-  }
-}
-
-plus.setFullSreen()
-// 等待服务初始化
-service.onReady().then(async () => {
-  // 等待连接交易服务
-  await socket.connectTrade()
-  // 等待业务数据初始化
-  await initBaseData()
-  state.loading = false
-})
-</script>
-
-<style lang="less" scoped>
-@import './index.less';
-</style>

+ 24 - 25
src/packages/mobile/views/boot/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="boot">
-    <Swipe class="boot__guide" :loop="false" v-if="guidePage">
+    <Swipe class="boot__guide" :loop="false" v-if="state.showGuide">
       <SwipeItem>
         <img src="@mobile/assets/images/boot-1080p.png" />
       </SwipeItem>
@@ -19,39 +19,23 @@ import { reactive } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import { Swipe, SwipeItem } from 'vant'
 import { showLoading } from '@/utils/vant'
-import { useAuth } from '@/business/auth'
+import { useLogin } from '@/business/login'
 import plus from '@/utils/h5plus'
 
+const { logining, initBaseData } = useLogin()
 const route = useRoute()
 const router = useRouter()
-const { login } = useAuth(true)
-const guidePage = localStorage.getItem('thj_app_guide') === 'false' ? false : true // 是否显示引导页
 const countdown = 1  // 倒计时秒数
 
 const state = reactive({
+  showGuide: localStorage.getItem('thj_app_showguide') === 'false' ? false : true, // 是否显示引导页
   second: countdown, // 剩余秒数
   currentRate: 100, // 当前进度
   rate: 100, // 目标进度
 })
 
-// 等待加载数据
-const autoLogin = (loading = false) => {
-  const toast = loading ? showLoading() : undefined
-  login().then(() => {
-    const redirect = route.query.redirect
-    if (redirect) {
-      router.replace(redirect.toString())
-    } else {
-      router.replace({ name: 'Home' })
-    }
-  }).catch(() => {
-    router.replace({ name: 'Home' })
-  }).finally(() => {
-    toast?.close()
-    localStorage.setItem('thj_app_guide', 'false')
-    plus.exitFullSreen()
-  })
-}
+// 初始化数据
+const onLoad = initBaseData(true)
 
 // 倒计时
 const timer = window.setInterval(() => {
@@ -60,8 +44,8 @@ const timer = window.setInterval(() => {
   if (state.second <= 0) {
     clearInterval(timer)
     // 判断是否首次打开应用
-    if (!guidePage) {
-      autoLogin()
+    if (!state.showGuide) {
+      skip()
     }
   }
 }, 1000)
@@ -69,7 +53,22 @@ const timer = window.setInterval(() => {
 // 跳过广告
 const skip = () => {
   clearInterval(timer)
-  autoLogin(true)
+  localStorage.setItem('thj_app_showguide', 'false')
+  const toast = logining.value ? showLoading() : undefined
+
+  onLoad.then(() => {
+    const redirect = route.query.redirect
+    if (redirect) {
+      router.replace(redirect.toString())
+    } else {
+      router.replace({ name: 'Home' })
+    }
+  }).catch(() => {
+    router.replace({ name: 'Home' })
+  }).finally(() => {
+    toast?.close()
+    plus.exitFullSreen()
+  })
 }
 
 plus.setFullSreen()

+ 5 - 5
src/packages/mobile/views/user/login/index.vue

@@ -4,9 +4,9 @@
     <div class="login-logo"></div>
     <Form class="login-form" @submit="formSubmit">
       <CellGroup>
-        <Field v-model="user.LoginID" name="account" label="用户名" size="large" placeholder="请输入用户名"
+        <Field v-model="formData.LoginID" name="account" label="用户名" size="large" placeholder="请输入用户名"
           :rules="[{ required: true, message: '请输入用户名' }]" />
-        <Field v-model="user.LoginPWD" name="password" type="password" label="密码" size="large" placeholder="请输入密码"
+        <Field v-model="formData.LoginPWD" name="password" type="password" label="密码" size="large" placeholder="请输入密码"
           :rules="[{ required: true, message: '请输入密码' }]" autocomplete="off" />
       </CellGroup>
       <div class="button-link">
@@ -37,13 +37,13 @@
 import { shallowRef } from 'vue'
 import { Button, Field, CellGroup, Form, Checkbox, showFailToast, showSuccessToast, showToast } from 'vant'
 import { fullloading } from '@/utils/vant'
-import { useAuth } from '@/business/auth'
+import { useLogin } from '@/business/login'
 import { useNavigation } from '@/hooks/navigation'
 import service from '@/services'
 import plus from '@/utils/h5plus'
 
 const { routerBack, routerTo } = useNavigation()
-const { user, login } = useAuth()
+const { formData, userLogin } = useLogin()
 const checked = shallowRef(false) // 是否同意协议管理
 const appVersion = shallowRef('') // 应用版本号
 
@@ -62,7 +62,7 @@ const navigationTo = (name: string) => {
 const formSubmit = () => {
   if (checked.value) {
     fullloading((hideLoading) => {
-      login().then(() => {
+      userLogin().then(() => {
         hideLoading()
         routerBack()
         showSuccessToast('登录成功')

+ 3 - 3
src/packages/pc/App.vue

@@ -15,20 +15,20 @@ export default {
 import { ref, watch } from 'vue'
 import { useRouter, useRoute } from 'vue-router'
 import { ElMessageBox } from 'element-plus'
-import { useAuth } from '@/business/auth'
+import { useLogin } from '@/business/login'
 import { loginStore } from '@/stores'
 import zhCn from 'element-plus/lib/locale/lang/zh-cn'
 import eventBus from '@/services/bus'
 
 const { token } = loginStore.$mapGetters()
-const { logout } = useAuth()
+const { userLogout } = useLogin()
 const route = useRoute()
 const router = useRouter()
 const hasLogin = ref(false)
 
 // 接收用户登出通知
 eventBus.$on('LogoutNotify', (msg) => {
-  logout(() => {
+  userLogout(() => {
     if (msg) {
       ElMessageBox.alert(msg as string)
     }

+ 4 - 0
src/packages/pc/components/base/table/index.less

@@ -1,4 +1,8 @@
 .app-table {
+    &:not(:first-child) {
+        margin-top: 20px;
+    }
+
     &__header {
         display: flex;
         align-items: center;

+ 10 - 2
src/packages/pc/components/base/table/index.vue

@@ -31,7 +31,7 @@
             :sortable="item.sortable" show-overflow-tooltip v-if="item.show ?? true">
             <template #default="{ row, $index }">
               <slot :name="item.prop" :row="row" :value="row[item.prop]" :index="$index">
-                {{ handleNoneValue(row[item.prop]) }}
+                {{ handleValue(row, item) }}
               </slot>
             </template>
           </el-table-column>
@@ -113,6 +113,14 @@ export default defineComponent({
       emit('rowClick', row)
     }
 
+    const handleValue = (row: { [key: string]: unknown }, column: Model.TableColumn) => {
+      const value = row[column.prop]
+      if (Number.isFinite(value) && column.decimal) {
+        return Number(value).toFixed(column.decimal)
+      }
+      return handleNoneValue(value)
+    }
+
     // 暴露组件属性
     expose({
       elTable: tableRef
@@ -124,7 +132,7 @@ export default defineComponent({
       showTableSetting,
       onSelect,
       onRowClick,
-      handleNoneValue,
+      handleValue,
       refresh,
       updateColumn,
     }

+ 18 - 14
src/packages/pc/views/auth/login/index.vue

@@ -1,11 +1,11 @@
 <template>
   <sign-layout class="user-login" title="账号登录">
-    <el-form ref="formRef" :model="user" :rules="formRules">
+    <el-form ref="formRef" :model="formData" :rules="formRules">
       <el-form-item prop="LoginID">
-        <el-input placeholder="用户名/账号/手机号" v-model="user.LoginID"></el-input>
+        <el-input placeholder="用户名/账号/手机号" v-model="formData.LoginID"></el-input>
       </el-form-item>
       <el-form-item prop="LoginPWD">
-        <el-input type="password" placeholder="请输入您的登录密码" v-model="user.LoginPWD">
+        <el-input type="password" placeholder="请输入您的登录密码" v-model="formData.LoginPWD">
         </el-input>
       </el-form-item>
       <!-- <el-form-item>
@@ -27,14 +27,15 @@ import { shallowRef } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import { ElMessage } from 'element-plus'
 import type { FormInstance, FormRules } from 'element-plus'
-import { useAuth } from '@/business/auth'
+import { useLogin } from '@/business/login'
 import { menuStore } from '@/stores'
 import SignLayout from '../components/layout/index.vue'
 
-const { loading, user, login } = useAuth()
+const { formData, userLogin } = useLogin()
 const route = useRoute()
 const router = useRouter()
 const formRef = shallowRef<FormInstance>()
+const loading = shallowRef(false)
 //const remember = shallowRef(false) // 记住账号
 
 const formRules: FormRules = {
@@ -47,19 +48,22 @@ const formRules: FormRules = {
 }
 
 const formSubmit = () => {
-  formRef.value?.validate((valid) => {
+  loading.value = true
+  formRef.value?.validate(async (valid) => {
     if (valid) {
-      login().then(async () => {
-        await menuStore.actions.getUserMenuList().catch(() => {
-          loading.value = false
-        })
-        const redirect = route.query.redirect;
+      try {
+        await userLogin()
+        await menuStore.actions.getUserMenuList()
+        const redirect = route.query.redirect
         if (redirect) {
-          router.replace(redirect.toString());
+          router.replace(redirect.toString())
         } else {
-          router.replace('/');
+          router.replace('/')
         }
-      }).catch((err) => ElMessage.error(err))
+      } catch (err) {
+        loading.value = false
+        ElMessage.error(err as string)
+      }
     }
   })
 }

+ 4 - 25
src/packages/pc/views/boot/index.vue

@@ -1,37 +1,16 @@
 <template>
-  <div class="boot" v-loading="loading"></div>
+  <div class="boot" v-loading="logining"></div>
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
-import { initBaseData, checkToken, checkTokenLoop } from '@/business/common'
-import { enumStore, errorInfoStore } from '@/stores'
-import service from '@/services'
-import socket from '@/services/socket'
+import { useLogin } from '@/business/login'
 
+const { logining, initBaseData } = useLogin()
 const route = useRoute()
 const router = useRouter()
-const loading = ref(true)
 
-// 初始化数据
-const onLoad = (async () => {
-  // 等待服务初始化
-  await service.onReady()
-  // 等待请求枚举
-  await enumStore.actions.getAllEnumList()
-  // 等待请求系统错误信息
-  await errorInfoStore.actions.getErrorInfoList()
-  // 等待连接交易服务
-  await socket.connectTrade()
-  // 等待令牌效验
-  await checkToken()
-  // 等待业务数据初始化
-  await initBaseData()
-})()
-
-onLoad.then(() => {
-  checkTokenLoop()
+initBaseData().then(() => {
   const redirect = route.query.redirect
   if (redirect) {
     router.replace(redirect.toString())

+ 1 - 0
src/services/bus/interface.ts

@@ -4,6 +4,7 @@
 export enum EventKey {
     QuotePushNotify, // 行情推送通知
     QuoteServerReconnectNotify, // 行情服务重连成功通知
+    LoginNotify, // 用户登入通知
     LogoutNotify, // 用户登出通知
     MoneyChangedNotify, // 资金变动通知
     UserChangeNtf,       // 账户变更通知

+ 2 - 2
src/services/socket/index.ts

@@ -71,12 +71,12 @@ export default new (class {
         }
 
         this.tradeServer.onBeforeReconnect = () => {
-            // 停止令牌
+            // 停止令牌
             stopCheckToken();
         }
 
         this.tradeServer.onReconnect = () => {
-            // 重新进行令牌
+            // 重新进行令牌
             checkToken().then(() => checkTokenLoop());
         }
     }

+ 5 - 3
src/services/subscribe/index.ts

@@ -69,9 +69,11 @@ export default new (class {
         const value = this.quoteSubscribeMap.get(uuid) ?? []
 
         const start = () => {
-            // 对相同 key 订阅的商品进行合并处理
-            this.quoteSubscribeMap.set(uuid, [...value, ...goodsCodes])
-            this.quoteSubscribe()
+            if (token.value) {
+                // 对相同 key 订阅的商品进行合并处理
+                this.quoteSubscribeMap.set(uuid, [...value, ...goodsCodes])
+                this.quoteSubscribe()
+            }
         }
 
         return {

+ 1 - 43
src/stores/modules/login.ts

@@ -1,8 +1,5 @@
-import { v4 } from 'uuid'
-import { login, queryLoginId } from '@/services/api/account' // 引入可能会引起 Cannot access 'loginStore' before initialization
 import { createStore } from '../base'
 import { sessionData } from '../storage'
-import cryptojs from 'crypto-js'
 
 /**
  * 登录存储对象
@@ -10,7 +7,7 @@ import cryptojs from 'crypto-js'
 export const loginStore = createStore({
     state() {
         return {
-            loading: false,
+            logining: false,
             loginInfo: sessionData.getRef('loginInfo'),
         }
     },
@@ -34,48 +31,9 @@ export const loginStore = createStore({
         },
     },
     actions: {
-        // 用户登录
-        userLogin(param: Proto.LoginReq) {
-            this.state.loading = true
-            return new Promise<Proto.LoginRsp>((resolve, reject) => {
-                queryLoginId({
-                    data: {
-                        username: param.LoginID
-                    },
-                    success: (res) => {
-                        login({
-                            data: {
-                                ...param,
-                                GUID: v4(),
-                                LoginID: res.data,
-                                LoginPWD: cryptojs.SHA256(res.data + param.LoginPWD).toString(),
-                            },
-                            success: (res) => {
-                                this.state.loginInfo = res
-                                resolve(res)
-                            },
-                            fail: (err) => {
-                                reject(err)
-                            },
-                            complete: () => {
-                                this.state.loading = false
-                            }
-                        })
-                    },
-                    fail: (err) => {
-                        this.state.loading = false
-                        reject(err)
-                    }
-                })
-            })
-        },
         // 获取用户登录信息
         getLoginInfo<K extends keyof Proto.LoginRsp>(key: K) {
             return this.state.loginInfo[key]
         },
-        // 重置数据
-        reset() {
-            sessionData.reset('loginInfo')
-        }
     }
 })

+ 1 - 0
src/stores/storage.ts

@@ -6,6 +6,7 @@ function createLocalData() {
     return {
         appLanguage: Language.ZhCN,
         appTheme: AppTheme.Default,
+        autoLoginEncryptedData: '', // 自动登录加密数据
     }
 }
 

+ 1 - 0
src/types/model/table.d.ts

@@ -8,5 +8,6 @@ declare namespace Model {
         sortable?: boolean;
         show?: boolean;
         fixed?: string;
+        decimal?: number; // 保留小数点位数
     }
 }

+ 58 - 0
src/utils/crypto/index.ts

@@ -0,0 +1,58 @@
+import cryptojs from 'crypto-js'
+
+/**
+ * 利用 canvas 创建浏览器唯一标识
+ * 由于不同客户端绘制 canvas 时渲染参数、抗锯齿等算法不同,因此绘制成图片数据的 CRC 校验也不一样
+ * @param text 
+ * @returns 
+ */
+export function getClientUUID(text = 'canvas') {
+    const canvas = document.createElement('canvas')
+    canvas.width = 100
+    canvas.height = 100
+
+    const ctx = canvas.getContext('2d')
+    if (ctx) {
+        ctx.font = '14px Arial'
+        ctx.fillStyle = 'black'
+        ctx.textAlign = 'center'
+        ctx.textBaseline = 'middle'
+        ctx.fillText(text, canvas.width / 2, canvas.height / 2) // 文本水平垂直居中
+    }
+
+    const base64Url = canvas.toDataURL()
+    return cryptojs.MD5(base64Url).toString()
+}
+
+/**
+ * AES加密
+ * @param data 
+ * @param key 
+ * @returns 
+ */
+export function encryptAES(data: string, key = getClientUUID(), iv?: string) {
+    const { enc: { Utf8 }, AES } = cryptojs
+    // 统一将传入的字符串转成UTF8编码
+    const _data = Utf8.parse(data) // 需要加密的数据
+    const _key = Utf8.parse(key) // 秘钥
+    const _iv = Utf8.parse(iv ?? key) // 偏移量
+    const encrypted = AES.encrypt(_data, _key, { iv: _iv, })
+    return encrypted.ciphertext.toString() //  返回加密后的值
+}
+
+/**
+ * AES解密
+ * @param encryptedData 
+ * @param key 
+ * @returns 
+ */
+export function decryptAES(encryptedData: string, key = getClientUUID(), iv?: string) {
+    const { enc: { Utf8, Hex, Base64 }, AES } = cryptojs
+    // 统一将传入的字符串转成UTF8编码
+    const wordarray = Hex.parse(encryptedData)
+    const _data = Base64.stringify(wordarray)
+    const _key = Utf8.parse(key) // 秘钥
+    const _iv = Utf8.parse(iv ?? key) // 偏移量
+    const decrypted = AES.decrypt(_data, _key, { iv: _iv })
+    return decrypted.toString(Utf8)
+}

+ 1 - 1
src/utils/h5plus/index.ts

@@ -225,7 +225,7 @@ export default new (class {
     }
 
     /**
-     * 打开本地文件(安卓正式包可能无效)
+     * 打开本地文件(安卓生产包可能无效)
      * https://www.html5plus.org/doc/zh_cn/runtime.html#plus.runtime.openFile
      * @param filePath 
      */

+ 4 - 4
src/utils/timer/index.ts

@@ -109,7 +109,7 @@ export const timerInterceptor = new (class {
     private throttleMap = new Map<string, () => void>();
 
     /**
-     * 函数防抖(等待触发)
+     * 函数防抖(手动触发)
      * @param callback 回调函数
      * @param delay 延迟毫秒数,默认100毫秒
      * @returns 
@@ -125,7 +125,7 @@ export const timerInterceptor = new (class {
     }
 
     /**
-     * 函数节流(间隔触发)
+     * 函数节流(手动触发)
      * @param callback 回调函数
      * @param delay 延迟毫秒数,默认100毫秒
      * @returns 
@@ -143,7 +143,7 @@ export const timerInterceptor = new (class {
     }
 
     /**
-     * 函数防抖(等待触发)
+     * 函数防抖(立即触发)
      * @param callback 回调函数
      * @param delay 延迟毫秒数,默认100毫秒
      * @param timerId 计时器ID
@@ -163,7 +163,7 @@ export const timerInterceptor = new (class {
     }
 
     /**
-     * 函数节流(间隔触发)
+     * 函数节流(立即触发)
      * @param callback 回调函数
      * @param delay 延迟毫秒数,默认100毫秒
      * @param timerId 计时器ID