feat 联调
This commit is contained in:
10
src/App.vue
10
src/App.vue
@@ -1,6 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { useDeviceAuth } from '@/composables/useDeviceAuth'
|
||||
import { initTables } from '@/service'
|
||||
|
||||
const { initAuth } = useDeviceAuth()
|
||||
|
||||
onLaunch(async () => {
|
||||
try {
|
||||
await initTables()
|
||||
@@ -9,6 +12,13 @@ onLaunch(async () => {
|
||||
catch (err) {
|
||||
console.error('数据库初始化失败:', err)
|
||||
}
|
||||
|
||||
try {
|
||||
await initAuth()
|
||||
}
|
||||
catch (err) {
|
||||
console.error('设备授权检测失败:', err)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useCurrentProject } from '@/composables/useCurrentProject'
|
||||
import { useDeviceAuth } from '@/composables/useDeviceAuth'
|
||||
|
||||
const { hasCurrentProject, currentProject } = useCurrentProject()
|
||||
const { isActivated } = useDeviceAuth()
|
||||
const { needAuthPrompt } = useDeviceAuth()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -14,7 +14,7 @@ const { isActivated } = useDeviceAuth()
|
||||
</text>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<unauthorized v-if="!isActivated" />
|
||||
<unauthorized v-if="needAuthPrompt" />
|
||||
<view v-else class="construction-info">
|
||||
<view class="icon-placeholder" />
|
||||
<view class="info">
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useDbStore } from '@/store/modules/db'
|
||||
|
||||
const db = useDbStore()
|
||||
const { hasCurrentProject, currentProjectId } = useCurrentProject()
|
||||
const { isActivated } = useDeviceAuth()
|
||||
const { isActivated, needAuthPrompt } = useDeviceAuth()
|
||||
|
||||
const currentProject = computed(() => {
|
||||
if (!hasCurrentProject.value || currentProjectId.value == null)
|
||||
@@ -99,7 +99,7 @@ onMounted(async () => {
|
||||
</text>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<unauthorized v-if="!isActivated" />
|
||||
<unauthorized v-if="needAuthPrompt" />
|
||||
<view v-else class="progress-content">
|
||||
<view class="progress-circle">
|
||||
<wd-circle :model-value="progressPercent ?? 0" :size="120">
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useDeviceAuth } from '@/composables/useDeviceAuth'
|
||||
import { useDbStore } from '@/store/modules/db'
|
||||
|
||||
const db = useDbStore()
|
||||
const { isActivated } = useDeviceAuth()
|
||||
const { isActivated, needAuthPrompt } = useDeviceAuth()
|
||||
|
||||
/** 首页展示最近 2 条,按 id 倒序 */
|
||||
const list = computed(() => (db.project || []).slice().sort((a, b) => (b.id ?? 0) - (a.id ?? 0)).slice(0, 2))
|
||||
@@ -41,7 +41,7 @@ function goToDetail(item: Project) {
|
||||
</wd-button>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<unauthorized v-if="!isActivated" />
|
||||
<unauthorized v-if="needAuthPrompt" />
|
||||
<view v-else-if="!list.length" class="empty-tip">
|
||||
暂无项目
|
||||
</view>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useDeviceAuth } from '@/composables/useDeviceAuth'
|
||||
import { useRtkLive } from '@/composables/useRtkLive'
|
||||
|
||||
const { hasCurrentProject } = useCurrentProject()
|
||||
const { isActivated } = useDeviceAuth()
|
||||
const { needAuthPrompt } = useDeviceAuth()
|
||||
const { speedText, tempText } = useRtkLive()
|
||||
|
||||
function goToWork() {
|
||||
@@ -22,7 +22,7 @@ function goToWork() {
|
||||
</text>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<unauthorized v-if="!isActivated" />
|
||||
<unauthorized v-if="needAuthPrompt" />
|
||||
<view v-else-if="!hasCurrentProject" class="empty-tip">
|
||||
--
|
||||
</view>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useDbStore } from '@/store/modules/db'
|
||||
|
||||
const db = useDbStore()
|
||||
const { hasCurrentProject, currentProjectId } = useCurrentProject()
|
||||
const { isActivated } = useDeviceAuth()
|
||||
const { isActivated, needAuthPrompt } = useDeviceAuth()
|
||||
|
||||
/** 首页只展示当前项目最近几条 */
|
||||
const list = computed(() => {
|
||||
@@ -50,7 +50,7 @@ function goToDetail(item: WarningRecord) {
|
||||
</wd-button>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<unauthorized v-if="!isActivated" />
|
||||
<unauthorized v-if="needAuthPrompt" />
|
||||
<view v-else-if="!hasCurrentProject" class="empty-tip">
|
||||
--
|
||||
</view>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { DeviceAuthStatus } from '@/api/device'
|
||||
import type { AuthStatus } from '@/api/types'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { computed, ref } from 'vue'
|
||||
@@ -5,63 +6,192 @@ import { activateDevice, getDeviceStatus } from '@/api/device'
|
||||
import { getDeviceId } from '@/utils/device'
|
||||
import storage from '@/utils/storage'
|
||||
|
||||
const AUTH_STATUS_KEY = 'device_auth_status'
|
||||
const AUTH_INFO_KEY = 'device_auth_info'
|
||||
const ACTIVATION_CODE_KEY = 'device_activation_code'
|
||||
|
||||
/** 模块级共享,保证各页面读到同一授权状态 */
|
||||
const authStatus = ref<AuthStatus>(storage.get<AuthStatus>(AUTH_STATUS_KEY, '0') ?? '0')
|
||||
function readCachedAuthInfo(): DeviceAuthStatus | null {
|
||||
return storage.get<DeviceAuthStatus>(AUTH_INFO_KEY) ?? null
|
||||
}
|
||||
|
||||
function toAuthStatus(value: AuthStatus | string | number | null | undefined): AuthStatus | null {
|
||||
if (value == null || value === '')
|
||||
return null
|
||||
const status = String(value) as AuthStatus
|
||||
if (status === '0' || status === '1' || status === '2' || status === '3')
|
||||
return status
|
||||
return null
|
||||
}
|
||||
|
||||
/** 兼容 data 嵌套 / 扁平结构 / authStatus 为数字 */
|
||||
function pickAuthPayload(res: unknown): Partial<DeviceAuthStatus> | null {
|
||||
if (!res || typeof res !== 'object')
|
||||
return null
|
||||
const raw = res as Record<string, unknown>
|
||||
const nested = raw.data
|
||||
if (nested && typeof nested === 'object')
|
||||
return nested as Partial<DeviceAuthStatus>
|
||||
if ('authStatus' in raw)
|
||||
return raw as Partial<DeviceAuthStatus>
|
||||
return null
|
||||
}
|
||||
|
||||
function normalizeAuthInfo(payload: Partial<DeviceAuthStatus> | null | undefined): DeviceAuthStatus | null {
|
||||
if (!payload)
|
||||
return null
|
||||
const status = toAuthStatus(payload.authStatus as AuthStatus | string | number | undefined)
|
||||
if (!status)
|
||||
return null
|
||||
return {
|
||||
deviceId: payload.deviceId ?? getDeviceId(),
|
||||
deviceName: payload.deviceName ?? '',
|
||||
authStatus: status,
|
||||
startDate: payload.startDate ?? '',
|
||||
endDate: payload.endDate ?? '',
|
||||
remainingDays: Number(payload.remainingDays ?? 0),
|
||||
}
|
||||
}
|
||||
|
||||
function createUnauthorizedInfo(): DeviceAuthStatus {
|
||||
return {
|
||||
deviceId: getDeviceId(),
|
||||
deviceName: '',
|
||||
authStatus: '0',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
remainingDays: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/** 模块级共享,保证各页面读到同一授权信息 */
|
||||
const authInfo = ref<DeviceAuthStatus | null>(readCachedAuthInfo())
|
||||
const activationCode = ref<string>(storage.get<string>(ACTIVATION_CODE_KEY, '') ?? '')
|
||||
const loading = ref(false)
|
||||
const authReady = ref(false)
|
||||
|
||||
/**
|
||||
* 设备授权状态:仅 authStatus === '1' 视为已激活
|
||||
* 未激活时首页相关模块保留标题,内容展示未授权提示
|
||||
*
|
||||
* 注意:onShow 仅在页面级调用时注册,避免子组件重复监听
|
||||
* 进入软件:先查本地激活码 → 无码再查授权状态并写入 storage
|
||||
* 激活成功后再查一次状态写入 storage,形成闭环
|
||||
* 仅未授权(0)或已过期(2)时展示 unauthorized / activateNotice
|
||||
*/
|
||||
export function useDeviceAuth(options?: { watchShow?: boolean }) {
|
||||
const authStatus = computed(() => toAuthStatus(authInfo.value?.authStatus))
|
||||
const isActivated = computed(() => authStatus.value === '1')
|
||||
const deviceId = computed(() => getDeviceId())
|
||||
/** 是否需要展示未授权提示组件 */
|
||||
const needAuthPrompt = computed(() => authStatus.value === '0' || authStatus.value === '2')
|
||||
const hasActivationCode = computed(() => !!activationCode.value.trim())
|
||||
const deviceId = computed(() => authInfo.value?.deviceId || getDeviceId())
|
||||
|
||||
function setAuthStatus(status: AuthStatus) {
|
||||
authStatus.value = status
|
||||
storage.set(AUTH_STATUS_KEY, status)
|
||||
/** 将完整授权信息写入内存 + storage */
|
||||
function saveAuthInfo(info: DeviceAuthStatus) {
|
||||
authInfo.value = info
|
||||
storage.set(AUTH_INFO_KEY, info)
|
||||
}
|
||||
|
||||
function setActivationCode(code: string) {
|
||||
const trimmed = code.trim()
|
||||
activationCode.value = trimmed
|
||||
if (trimmed)
|
||||
storage.set(ACTIVATION_CODE_KEY, trimmed)
|
||||
else
|
||||
storage.remove(ACTIVATION_CODE_KEY)
|
||||
}
|
||||
|
||||
/** 查询设备授权状态,并完整落库;失败时返回 null */
|
||||
async function fetchAuthStatus() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getDeviceStatus(deviceId.value)
|
||||
const status = res.data?.authStatus
|
||||
if (status != null && status !== '')
|
||||
setAuthStatus(String(status) as AuthStatus)
|
||||
const res = await getDeviceStatus(getDeviceId())
|
||||
const info = normalizeAuthInfo(pickAuthPayload(res))
|
||||
if (info) {
|
||||
saveAuthInfo(info)
|
||||
return info
|
||||
}
|
||||
console.warn('[deviceAuth] 授权状态响应无法解析:', res)
|
||||
return null
|
||||
}
|
||||
catch {
|
||||
// 接口失败时沿用本地缓存状态
|
||||
catch (err) {
|
||||
console.warn('[deviceAuth] 查询授权状态失败:', err)
|
||||
return null
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交授权码激活设备,成功后更新本地状态 */
|
||||
async function activate(activationCode: string) {
|
||||
const code = activationCode.trim()
|
||||
if (!code)
|
||||
/**
|
||||
* 启动时初始化授权:
|
||||
* 1. 本地已有激活码 → 读取 storage 中的授权信息
|
||||
* 2. 无激活码 → 查询设备授权状态;查不到则按未授权处理并展示提示
|
||||
*/
|
||||
async function initAuth() {
|
||||
loading.value = true
|
||||
try {
|
||||
const localCode = storage.get<string>(ACTIVATION_CODE_KEY, '') ?? ''
|
||||
if (localCode.trim()) {
|
||||
activationCode.value = localCode.trim()
|
||||
const cached = readCachedAuthInfo()
|
||||
if (cached) {
|
||||
authInfo.value = cached
|
||||
return authInfo.value
|
||||
}
|
||||
// 有激活码但无缓存信息,补拉一次状态
|
||||
const info = await fetchAuthStatus()
|
||||
if (info)
|
||||
return info
|
||||
saveAuthInfo({ ...createUnauthorizedInfo(), authStatus: '1' })
|
||||
return authInfo.value
|
||||
}
|
||||
|
||||
// 无激活码:以服务端为准;失败或无法解析 → 未授权,展示提示组件
|
||||
const info = await fetchAuthStatus()
|
||||
if (info)
|
||||
return info
|
||||
|
||||
saveAuthInfo(createUnauthorizedInfo())
|
||||
return authInfo.value
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
authReady.value = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交授权码激活 → 缓存激活码 → 再查授权状态落库(闭环)
|
||||
*/
|
||||
async function activate(code: string) {
|
||||
const trimmed = code.trim()
|
||||
if (!trimmed)
|
||||
throw new Error('请输入授权码')
|
||||
|
||||
const res = await activateDevice({
|
||||
activationCode: code,
|
||||
deviceId: deviceId.value,
|
||||
activationCode: trimmed,
|
||||
deviceId: getDeviceId(),
|
||||
})
|
||||
const ok = res.code === 200 || res.code === 0
|
||||
if (!ok)
|
||||
throw new Error(res.msg || '激活失败')
|
||||
|
||||
setAuthStatus('1')
|
||||
setActivationCode(trimmed)
|
||||
|
||||
const info = await fetchAuthStatus()
|
||||
if (!info) {
|
||||
saveAuthInfo({
|
||||
deviceId: res.deviceId || getDeviceId(),
|
||||
deviceName: authInfo.value?.deviceName ?? '',
|
||||
authStatus: '1',
|
||||
startDate: authInfo.value?.startDate ?? '',
|
||||
endDate: res.endDate || authInfo.value?.endDate || '',
|
||||
remainingDays: authInfo.value?.remainingDays ?? 0,
|
||||
})
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
function refreshAuthStatus() {
|
||||
authStatus.value = storage.get<AuthStatus>(AUTH_STATUS_KEY, '0') ?? '0'
|
||||
authInfo.value = readCachedAuthInfo()
|
||||
activationCode.value = storage.get<string>(ACTIVATION_CODE_KEY, '') ?? ''
|
||||
}
|
||||
|
||||
if (options?.watchShow) {
|
||||
@@ -71,12 +201,19 @@ export function useDeviceAuth(options?: { watchShow?: boolean }) {
|
||||
}
|
||||
|
||||
return {
|
||||
authInfo,
|
||||
authStatus,
|
||||
activationCode,
|
||||
isActivated,
|
||||
needAuthPrompt,
|
||||
hasActivationCode,
|
||||
deviceId,
|
||||
loading,
|
||||
setAuthStatus,
|
||||
authReady,
|
||||
saveAuthInfo,
|
||||
setActivationCode,
|
||||
fetchAuthStatus,
|
||||
initAuth,
|
||||
activate,
|
||||
refreshAuthStatus,
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@ definePage({
|
||||
},
|
||||
})
|
||||
|
||||
const { isActivated, fetchAuthStatus } = useDeviceAuth({ watchShow: true })
|
||||
const { needAuthPrompt, initAuth } = useDeviceAuth({ watchShow: true })
|
||||
|
||||
onMounted(() => {
|
||||
fetchAuthStatus()
|
||||
initAuth()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -30,7 +30,7 @@ onMounted(() => {
|
||||
<home-projectList />
|
||||
</view>
|
||||
<view class="column-right">
|
||||
<home-activateNotice v-if="!isActivated" />
|
||||
<home-activateNotice v-if="needAuthPrompt" />
|
||||
<home-progress />
|
||||
<home-warning />
|
||||
</view>
|
||||
|
||||
Reference in New Issue
Block a user