li.shaoyi 3 jaren geleden
bovenliggende
commit
c89912c325

+ 91 - 20
src/business/auth/index.ts

@@ -1,14 +1,42 @@
-import { ref, reactive } from 'vue'
+import { ref } from 'vue'
 import { v4 } from 'uuid'
-import { useLoginStore } from '@/stores'
-import { initBaseData } from '@/business/common'
-import eventBus from '@/services/bus'
+import { timerTask } from '@/utils/timer'
+import { useStore } from '@/stores'
+import { initBaseData, checkTokenLoop } from '@/business/common'
+import socket from '@/services/socket'
+import cryptojs from 'crypto-js'
 
-export function useAuth() {
-    const { userLogin } = useLoginStore()
+// 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()
+}
+
+export function useAuth(autoLogin = false) {
+    const { loginStore } = useStore()
+    const autologinKey = localStorage.getItem('thj_autologin_key') // 自动登录密钥
+    const autologinData = localStorage.getItem('thj_autologin_data') // 自动登录数据
     const loading = ref(false)
-    const user = reactive<Proto.LoginReq>({
-        LoginID: localStorage.getItem('thj_app_loginid') ?? '',
+
+    const user = ref<Proto.LoginReq>({
+        LoginID: '',
         LoginPWD: '',
         GUID: '',
         LoginType: 0,
@@ -17,24 +45,67 @@ export function useAuth() {
         DeviceID: ''
     })
 
-    // 用户登录
-    const login = async () => {
-        loading.value = true
-        const catchError = (err: string) => {
-            loading.value = false
-            return Promise.reject(err)
+    if (autologinKey && autologinData) {
+        const decryptedData = decrypt(autologinData, autologinKey)
+        if (decryptedData) {
+            const loginReq: Proto.LoginReq = JSON.parse(decryptedData)
+            // 自动登录
+            if (autoLogin) {
+                user.value = loginReq
+            } else {
+                user.value.LoginID = loginReq.LoginID
+            }
+        }
+    }
+
+    // 设置自动登录数据
+    const setAutoLoginData = (loginKey: string | null, password = '') => {
+        if (loginKey) {
+            user.value.LoginPWD = password
+            const encryptedData = encrypt(JSON.stringify(user.value), loginKey)
+            localStorage.setItem('thj_autologin_key', loginKey)
+            localStorage.setItem('thj_autologin_data', encryptedData)
         }
+    }
 
-        await userLogin({ ...user, GUID: v4() }).then(() => {
-            localStorage.setItem('thj_app_loginid', user.LoginID)
-        }).catch((err) => catchError(err))
+    // 用户登录
+    const login = () => {
+        const { LoginID, LoginPWD } = user.value
+        if (LoginID && LoginPWD) {
+            return new Promise<void>((resolve, reject) => {
+                loading.value = true
+                const catchError = (err: string) => {
+                    loading.value = false
+                    reject(err)
+                }
 
-        await initBaseData().catch((err) => catchError(err))
+                loginStore.userLogin(user.value).then(() => {
+                    initBaseData().then(() => {
+                        const loginKey = v4() // 每次成功登录后更新密钥
+                        setAutoLoginData(loginKey, LoginPWD)
+                        checkTokenLoop()
+                        resolve()
+                    }).catch((err) => {
+                        catchError(err)
+                    })
+                }).catch((err) => {
+                    setAutoLoginData(autologinKey)
+                    catchError(err)
+                })
+            })
+        }
+        return Promise.reject('登录失败')
     }
 
     // 用户登出
-    const logout = () => {
-        eventBus.$emit('LogoutNotify')
+    const logout = (callback?: () => void) => {
+        const { loginStore, accountStore } = useStore()
+        setAutoLoginData(autologinKey)
+        socket.closeAll()
+        timerTask.clearAll()
+        loginStore.reset()
+        accountStore.reset()
+        callback && callback()
     }
 
     return {

+ 0 - 15
src/business/common/index.ts

@@ -2,19 +2,6 @@ import { timerTask } from '@/utils/timer'
 import { useStore } from '@/stores'
 import { tokenCheck } from '@/services/api/account'
 import eventBus from '@/services/bus'
-import socket from '@/services/socket'
-
-/**
- * 退出登录
- */
-export function logout(callback?: () => void) {
-    const { loginStore, accountStore } = useStore()
-    socket.closeAll()
-    timerTask.clearAll()
-    loginStore.reset()
-    accountStore.reset()
-    callback && callback()
-}
 
 /**
  * 初始化业务数据
@@ -25,13 +12,11 @@ export async function initBaseData() {
     await errorInfoStore.getErrorInfoList()
 
     if (loginStore.getToken()) {
-        await checkToken().then(() => checkTokenLoop())
         await Promise.all([
             userStore.getUserData(),
             menuStore.getUserMenuList(),
             futuresStore.getGoodsList(),
         ])
-
         accountStore.getAccountList()
     }
 }

+ 0 - 1
src/business/order/index.ts

@@ -11,7 +11,6 @@ import {
     queryTHJPurchaseTransferOrderDetail
 } from '@/services/api/order'
 import { useLoginStore } from '@/stores'
-import { formatDate } from '@/filters'
 
 // 查询仓单持有记录
 export function useQueryHoldLB() {

+ 2 - 1
src/packages/mobile/App.vue

@@ -5,9 +5,10 @@
 <script lang="ts" setup>
 import { useRouter } from 'vue-router'
 import { dialog } from '@/utils/vant'
-import { logout } from '@/business/common'
+import { useAuth } from '@/business/auth'
 import eventBus from '@/services/bus'
 
+const { logout } = useAuth()
 const router = useRouter()
 
 // 接收用户登出通知

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

@@ -0,0 +1,82 @@
+<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>

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

@@ -18,33 +18,72 @@
 import { reactive } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import { Swipe, SwipeItem } from 'vant'
+import { showLoading } from '@/utils/vant'
 import { initBaseData } from '@/business/common'
+import { useAuth } from '@/business/auth'
 import service from '@/services'
 import socket from '@/services/socket'
 import plus from '@/utils/h5plus'
 
 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({
-  loading: true,
   second: countdown, // 剩余秒数
   currentRate: 100, // 当前进度
   rate: 100, // 目标进度
 })
 
+// 初始化数据
+const loadData = (async () => {
+  // 等待服务初始化
+  await service.onReady()
+  // 等待连接交易服务
+  await socket.connectTrade()
+  // 等待业务数据初始化
+  await initBaseData()
+})()
+
+// 等待加载数据
+const autoLogin = async (loading = false) => {
+  const toast = loading ? showLoading() : undefined
+  const complete = () => {
+    toast?.clear()
+    localStorage.setItem('thj_app_guide', 'false')
+    plus.exitFullSreen()
+  }
+
+  await loadData.catch(() => {
+    complete()
+    router.replace({ name: 'UserLogin' })
+  })
+
+  login().then(() => {
+    const redirect = route.query.redirect
+    if (redirect) {
+      router.replace(redirect.toString())
+    } else {
+      router.replace({ name: 'Home' })
+    }
+  }).catch(() => {
+    router.replace({ name: 'UserLogin' })
+  }).finally(() => {
+    complete()
+  })
+}
+
 // 倒计时
 const timer = window.setInterval(() => {
   state.second--
   state.rate = (100 / countdown) * state.second
   if (state.second <= 0) {
+    clearInterval(timer)
     // 判断是否首次打开应用
-    if (guidePage) {
-      localStorage.setItem('thj_app_guide', 'false')
-    } else {
-      skip()
+    if (!guidePage) {
+      autoLogin()
     }
   }
 }, 1000)
@@ -52,29 +91,10 @@ const timer = window.setInterval(() => {
 // 跳过广告
 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('/')
-    }
-  }
+  autoLogin(true)
 }
 
 plus.setFullSreen()
-// 等待服务初始化
-service.onReady().then(async () => {
-  // 等待连接交易服务
-  await socket.connectTrade()
-  // 等待业务数据初始化
-  await initBaseData()
-  state.loading = false
-})
 </script>
 
 <style lang="less" scoped>

+ 3 - 3
src/packages/mobile/views/contract/details/index.vue

@@ -104,10 +104,10 @@ const deliveryMonths = computed(() => {
     const list = details.value.deliverydate ?? []
     const monthMap = new Map<string, { label: string, value: string; }>()
     // 日期去重
-    list.forEach(({ enddate }) => {
+    list.forEach(({ enddate, endmonth }) => {
         if (!monthMap.has(enddate)) {
             monthMap.set(enddate, {
-                label: formatDate(enddate, 'YYYY-MM'),
+                label: endmonth,
                 value: enddate,
             })
         }
@@ -136,7 +136,7 @@ const depositRates = computed(() => {
 // 表格数据
 const tableList = computed(() => {
     const list = details.value.deliverydate ?? []
-    return  list.filter((e) => e.enddate === selectedMonth.value && e.depositrate === selectedRate.value).sort((a,b)=> a.transferprice-b.transferprice)
+    return list.filter((e) => e.enddate === selectedMonth.value && e.depositrate === selectedRate.value).sort((a, b) => a.transferprice - b.transferprice)
 })
 
 const onSubmit = (item: Model.THJPurchaseTransferDetailRsp['deliverydate'][number]) => {

+ 3 - 4
src/packages/mobile/views/mine/main/index.vue

@@ -118,16 +118,15 @@ import { Cell, CellGroup, Button, Tag, Toast } from 'vant'
 import { fullloading, dialog } from '@/utils/vant'
 import { useNavigation } from '@/hooks/navigation'
 import { useLoginStore, useAccountStore, } from '@/stores'
-import { useAuth } from '@/business/auth'
 import AppIconfont from '@mobile/components/base/iconfont/index.vue'
 import { useBankAccountSign } from '@/business/bank'
 import { queryUserAccount } from '@/services/api/account'
-import { AuthStatus, getAuthStatusName } from '@/constants/account';
+import { AuthStatus, getAuthStatusName } from '@/constants/account'
+import eventBus from '@/services/bus'
 
 const { router, routerTo } = useNavigation()
 const { getUserId, getLoginId, getFirstAccountId } = useLoginStore()
 const { getBankAccountList, bankInfo } = useBankAccountSign()
-const { logout } = useAuth()
 const { accountInfo, freezeMargin, avaiableMoney } = useAccountStore()
 const authStatus = shallowRef(AuthStatus.Uncertified) // 实名认证状态
 const headerRef = shallowRef<HTMLDivElement>()
@@ -173,7 +172,7 @@ const userLogout = () => {
   dialog('是否退出当前账号?', {
     showCancelButton: true
   }).then(() => {
-    logout()
+    eventBus.$emit('LogoutNotify')
   })
 }
 

+ 2 - 3
src/packages/mobile/views/user/password/index.vue

@@ -26,10 +26,9 @@ import { shallowRef } from 'vue'
 import { CellGroup, Button, Field, Form, FormInstance, FieldRule, Toast } from 'vant'
 import { fullloading, dialog } from '@/utils/vant'
 import { validateRules } from '@/constants/regex'
-import { useAuth } from '@/business/auth'
 import { useAccountPassword } from '@/business/user'
+import eventBus from '@/services/bus'
 
-const { logout } = useAuth()
 const { formData, formSubmit } = useAccountPassword(1)
 const formRef = shallowRef<FormInstance>()
 const confirmpassword = shallowRef('')
@@ -67,7 +66,7 @@ const onSubmit = () => {
         formSubmit().then(() => {
             hideLoading()
             dialog('密码修改成功,请重新登录。').then(() => {
-                logout()
+                eventBus.$emit('LogoutNotify')
             })
         }).catch((err) => {
             Toast.fail(err)

+ 4 - 0
src/stores/modules/login.ts

@@ -1,4 +1,5 @@
 import { toRefs, shallowReadonly, ShallowRef } from 'vue'
+import { v4 } from 'uuid'
 import { login, queryLoginId } from '@/services/api/account'
 import { VueStore } from '../base'
 import cryptojs from 'crypto-js'
@@ -6,6 +7,7 @@ import WebStorage from '@/utils/storage/base'
 
 interface StoreState {
     loading: boolean;
+    autoLogin: boolean;
     loginInfo: ShallowRef<Proto.LoginRsp>;
 }
 
@@ -31,6 +33,7 @@ const store = new (class extends VueStore<StoreState> {
         const storage = new WebStorage(sessionStorage, 'loginInfo', getInitData())
         const state: StoreState = {
             loading: false,
+            autoLogin: false,
             loginInfo: storage.getRef(),
         }
         super(state)
@@ -49,6 +52,7 @@ const store = new (class extends VueStore<StoreState> {
                         login({
                             data: {
                                 ...param,
+                                GUID: v4(),
                                 LoginID: res.data,
                                 LoginPWD: cryptojs.SHA256(res.data + param.LoginPWD).toString(),
                             },

+ 2 - 0
src/types/model/contract.d.ts

@@ -27,6 +27,8 @@ declare namespace Model {
         deliverydate: {
             depositrate: number; // 定金比例
             enddate: string; // 预售结束日期
+            endmonth: string; // 交收月份(yyyy-mm)
+            takestartdate: string; // 提货开始日期
             tradeprice: number; // 摘牌价格
             tradeqty: number; // 摘牌数量
             transferamount: number; // 转让金额

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

@@ -24,7 +24,7 @@ export function notify(message?: string, type?: NotifyType, duration = 2000) {
  * @param callback 
  * @returns 
  */
-export function showLoading(message = '正在加载...', callback?: (hideLoading: () => void) => void,) {
+export function showLoading(message = '加载...', callback?: (hideLoading: () => void) => void,) {
     const toast = Toast.loading({
         message,
         duration: 0,