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