mirror of
https://github.com/ZeroCatDev/Classworks.git
synced 2025-12-08 13:49:37 +00:00
feat: 实现渐进式设备注册功能,自动生成设备信息并获取访问令牌,优化用户体验
This commit is contained in:
parent
670666aa41
commit
49ea5f1b2f
@ -17,16 +17,16 @@ axiosInstance.interceptors.request.use(
|
|||||||
|
|
||||||
// 只有在 kv-server 或 classworkscloud 模式下才添加请求头
|
// 只有在 kv-server 或 classworkscloud 模式下才添加请求头
|
||||||
if (provider === "kv-server" || provider === "classworkscloud") {
|
if (provider === "kv-server" || provider === "classworkscloud") {
|
||||||
// 确保每次请求时都获取最新的 siteKey
|
// 优先使用新的 kvToken
|
||||||
|
const kvToken = getSetting("server.kvToken");
|
||||||
|
if (kvToken) {
|
||||||
|
requestConfig.headers["x-app-token"] = kvToken;
|
||||||
|
} else {
|
||||||
|
// 向后兼容旧的 siteKey
|
||||||
const siteKey = getSetting("server.siteKey");
|
const siteKey = getSetting("server.siteKey");
|
||||||
if (siteKey) {
|
if (siteKey) {
|
||||||
requestConfig.headers["x-site-key"] = Base64.encode(siteKey);
|
requestConfig.headers["x-site-key"] = Base64.encode(siteKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自动添加命名空间密码
|
|
||||||
const namespacePassword = getSetting("namespace.password");
|
|
||||||
if (namespacePassword) {
|
|
||||||
requestConfig.headers["x-namespace-password"] = Base64.encode(namespacePassword);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -99,6 +99,7 @@
|
|||||||
</v-card-item>
|
</v-card-item>
|
||||||
</v-card>
|
</v-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="options-buttons">
|
<div class="options-buttons">
|
||||||
<v-btn
|
<v-btn
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
@ -145,7 +146,10 @@
|
|||||||
v-model="showGuideDialog"
|
v-model="showGuideDialog"
|
||||||
max-width="600"
|
max-width="600"
|
||||||
>
|
>
|
||||||
<FirstTimeGuide @close="showGuideDialog = false" />
|
<FirstTimeGuide
|
||||||
|
@close="showGuideDialog = false"
|
||||||
|
@success="handleGuideSuccess"
|
||||||
|
/>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
|
||||||
<v-dialog
|
<v-dialog
|
||||||
@ -275,6 +279,13 @@ const handleAutoAuthorize = () => {
|
|||||||
window.location.href = url
|
window.location.href = url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleGuideSuccess = (tokenData) => {
|
||||||
|
showGuideDialog.value = false
|
||||||
|
console.log('渐进式注册成功:', tokenData)
|
||||||
|
evaluateVisibility()
|
||||||
|
emit('done')
|
||||||
|
}
|
||||||
|
|
||||||
const handleAuthSuccess = (tokenData) => {
|
const handleAuthSuccess = (tokenData) => {
|
||||||
showDeviceAuthDialog.value = false
|
showDeviceAuthDialog.value = false
|
||||||
console.log('认证成功:', tokenData)
|
console.log('认证成功:', tokenData)
|
||||||
|
|||||||
@ -140,141 +140,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|
||||||
<!-- 工作流程说明 -->
|
|
||||||
<v-card
|
|
||||||
variant="outlined"
|
|
||||||
class="pa-5 mb-4"
|
|
||||||
>
|
|
||||||
<h4 class="text-h6 mb-4">
|
|
||||||
<v-icon
|
|
||||||
color="primary"
|
|
||||||
class="mr-2"
|
|
||||||
>
|
|
||||||
mdi-information
|
|
||||||
</v-icon>
|
|
||||||
工作流程
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<v-timeline
|
|
||||||
side="end"
|
|
||||||
density="compact"
|
|
||||||
line-thickness="2"
|
|
||||||
>
|
|
||||||
<v-timeline-item
|
|
||||||
dot-color="primary"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<div class="text-body-2">
|
|
||||||
<strong>步骤 1:</strong> 在 Classworks 应用中编辑作业
|
|
||||||
</div>
|
|
||||||
</v-timeline-item>
|
|
||||||
|
|
||||||
<v-timeline-item
|
|
||||||
dot-color="success"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<div class="text-body-2">
|
|
||||||
<strong>步骤 2:</strong> 数据自动上传到 Classworks KV
|
|
||||||
</div>
|
|
||||||
</v-timeline-item>
|
|
||||||
|
|
||||||
<v-timeline-item
|
|
||||||
dot-color="info"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<div class="text-body-2">
|
|
||||||
<strong>步骤 3:</strong> 其他设备从 Classworks KV 同步数据
|
|
||||||
</div>
|
|
||||||
</v-timeline-item>
|
|
||||||
|
|
||||||
<v-timeline-item
|
|
||||||
dot-color="warning"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<div class="text-body-2">
|
|
||||||
<strong>步骤 4:</strong> 所有设备显示相同的作业内容
|
|
||||||
</div>
|
|
||||||
</v-timeline-item>
|
|
||||||
</v-timeline>
|
|
||||||
</v-card>
|
|
||||||
|
|
||||||
<!-- 优势说明 -->
|
|
||||||
<v-row>
|
|
||||||
<v-col
|
|
||||||
cols="12"
|
|
||||||
md="4"
|
|
||||||
>
|
|
||||||
<v-card
|
|
||||||
variant="tonal"
|
|
||||||
color="blue"
|
|
||||||
class="pa-4 h-100"
|
|
||||||
>
|
|
||||||
<v-icon
|
|
||||||
size="40"
|
|
||||||
color="blue-darken-2"
|
|
||||||
class="mb-2"
|
|
||||||
>
|
|
||||||
mdi-devices
|
|
||||||
</v-icon>
|
|
||||||
<h5 class="text-subtitle-1 font-weight-bold mb-2">
|
|
||||||
多设备访问
|
|
||||||
</h5>
|
|
||||||
<p class="text-body-2">
|
|
||||||
在教室、办公室、家中的任何设备上访问相同的数据
|
|
||||||
</p>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<v-col
|
|
||||||
cols="12"
|
|
||||||
md="4"
|
|
||||||
>
|
|
||||||
<v-card
|
|
||||||
variant="tonal"
|
|
||||||
color="green"
|
|
||||||
class="pa-4 h-100"
|
|
||||||
>
|
|
||||||
<v-icon
|
|
||||||
size="40"
|
|
||||||
color="green-darken-2"
|
|
||||||
class="mb-2"
|
|
||||||
>
|
|
||||||
mdi-sync
|
|
||||||
</v-icon>
|
|
||||||
<h5 class="text-subtitle-1 font-weight-bold mb-2">
|
|
||||||
实时同步
|
|
||||||
</h5>
|
|
||||||
<p class="text-body-2">
|
|
||||||
修改后立即同步,所有设备保持数据一致
|
|
||||||
</p>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
|
|
||||||
<v-col
|
|
||||||
cols="12"
|
|
||||||
md="4"
|
|
||||||
>
|
|
||||||
<v-card
|
|
||||||
variant="tonal"
|
|
||||||
color="orange"
|
|
||||||
class="pa-4 h-100"
|
|
||||||
>
|
|
||||||
<v-icon
|
|
||||||
size="40"
|
|
||||||
color="orange-darken-2"
|
|
||||||
class="mb-2"
|
|
||||||
>
|
|
||||||
mdi-shield-lock
|
|
||||||
</v-icon>
|
|
||||||
<h5 class="text-subtitle-1 font-weight-bold mb-2">
|
|
||||||
安全可靠
|
|
||||||
</h5>
|
|
||||||
<p class="text-body-2">
|
|
||||||
通过密码和命名空间隔离,保护班级数据安全
|
|
||||||
</p>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 步骤 3: 询问使用场景 -->
|
<!-- 步骤 3: 询问使用场景 -->
|
||||||
@ -386,6 +252,32 @@
|
|||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<v-card
|
||||||
|
variant="tonal"
|
||||||
|
class="pa-6 mb-6"
|
||||||
|
>
|
||||||
|
<div class="d-flex flex-column flex-sm-row align-center">
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<h4 class="text-h6 font-weight-bold mb-2">
|
||||||
|
自动注册设备
|
||||||
|
</h4>
|
||||||
|
<p class="text-body-2 mb-3 text-medium-emphasis">
|
||||||
|
通过引导式流程自动创建设备、获取令牌并完成初始化。适合首次体验或快速部署多终端。
|
||||||
|
</p>
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
variant="elevated"
|
||||||
|
prepend-icon="mdi-flash"
|
||||||
|
@click="goToProgressiveStep"
|
||||||
|
>
|
||||||
|
自动注册
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
<div class="mb-6">
|
||||||
|
也可以手动前往 Classworks KV 控制台获取认证信息:</div>
|
||||||
<v-card
|
<v-card
|
||||||
:variant="kvserverurl=='https://kv.houlang.cloud'? 'elevated' : 'outlined'"
|
:variant="kvserverurl=='https://kv.houlang.cloud'? 'elevated' : 'outlined'"
|
||||||
:color=" kvserverurl=='https://kv.houlang.cloud'? 'primary' : 'error' "
|
:color=" kvserverurl=='https://kv.houlang.cloud'? 'primary' : 'error' "
|
||||||
@ -398,6 +290,7 @@
|
|||||||
>
|
>
|
||||||
mdi-open-in-new
|
mdi-open-in-new
|
||||||
</v-icon>
|
</v-icon>
|
||||||
|
|
||||||
<h4 class="text-h6 font-weight-bold">
|
<h4 class="text-h6 font-weight-bold">
|
||||||
请访问 {{ kvserverurl=='https://kv.houlang.cloud'? 'Classworks KV' : '自定义的 Classworks KV 实例 ' }} 控制台
|
请访问 {{ kvserverurl=='https://kv.houlang.cloud'? 'Classworks KV' : '自定义的 Classworks KV 实例 ' }} 控制台
|
||||||
</h4>
|
</h4>
|
||||||
@ -409,33 +302,6 @@
|
|||||||
</h6>
|
</h6>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|
||||||
<v-card
|
|
||||||
variant="tonal"
|
|
||||||
color="info"
|
|
||||||
class="pa-5"
|
|
||||||
>
|
|
||||||
<div class="text-body-1 mb-3">
|
|
||||||
<v-icon
|
|
||||||
size="20"
|
|
||||||
class="mr-2"
|
|
||||||
>
|
|
||||||
mdi-information
|
|
||||||
</v-icon>
|
|
||||||
在控制台完成以下操作:
|
|
||||||
</div>
|
|
||||||
<div class="text-body-2 mb-2">
|
|
||||||
1. 注册或登录账号
|
|
||||||
</div>
|
|
||||||
<div class="text-body-2 mb-2">
|
|
||||||
2. 创建班级空间
|
|
||||||
</div>
|
|
||||||
<div class="text-body-2 mb-2">
|
|
||||||
3. 获取命名空间和密码
|
|
||||||
</div>
|
|
||||||
<div class="text-body-2">
|
|
||||||
4. 返回这里输入认证信息
|
|
||||||
</div>
|
|
||||||
</v-card>
|
|
||||||
|
|
||||||
<!-- 常见问题 -->
|
<!-- 常见问题 -->
|
||||||
<v-expansion-panels
|
<v-expansion-panels
|
||||||
@ -511,6 +377,166 @@
|
|||||||
</v-expansion-panel>
|
</v-expansion-panel>
|
||||||
</v-expansion-panels>
|
</v-expansion-panels>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 步骤 5: 渐进式注册完整流程 -->
|
||||||
|
<div
|
||||||
|
v-show="currentStep === 5"
|
||||||
|
class="step-content"
|
||||||
|
>
|
||||||
|
<div class="text-center mb-6">
|
||||||
|
<v-avatar
|
||||||
|
size="80"
|
||||||
|
color="primary"
|
||||||
|
variant="tonal"
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
|
<v-icon size="48">
|
||||||
|
mdi-rocket-launch
|
||||||
|
</v-icon>
|
||||||
|
</v-avatar>
|
||||||
|
<h3 class="text-h5 font-weight-bold mb-2">
|
||||||
|
渐进式注册
|
||||||
|
</h3>
|
||||||
|
<p class="text-body-2 text-medium-emphasis">
|
||||||
|
您可以暂时不配置 Classworks KV
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<v-progress-linear
|
||||||
|
:model-value="progressValue"
|
||||||
|
height="8"
|
||||||
|
color="primary"
|
||||||
|
rounded
|
||||||
|
class="mb-6"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col
|
||||||
|
cols="12"
|
||||||
|
>
|
||||||
|
<v-card
|
||||||
|
variant="tonal"
|
||||||
|
:color="statusColor"
|
||||||
|
>
|
||||||
|
<v-card-item>
|
||||||
|
<div class="d-flex align-center mb-3">
|
||||||
|
<v-icon
|
||||||
|
:color="statusColor"
|
||||||
|
class="mr-2"
|
||||||
|
size="32"
|
||||||
|
>
|
||||||
|
{{ statusIcon }}
|
||||||
|
</v-icon>
|
||||||
|
<div class="text-h6 font-weight-medium">
|
||||||
|
{{ statusTitle }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="deviceInfo"
|
||||||
|
class="text-body-2 mb-2"
|
||||||
|
>
|
||||||
|
<div class="mb-2">
|
||||||
|
<strong>设备名称:</strong>{{ deviceInfo.deviceName }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>设备 UUID:</strong>
|
||||||
|
<code class="device-code">{{ deviceInfo.uuid }}</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="progressiveStatus === 'error'"
|
||||||
|
class="text-body-2 text-error"
|
||||||
|
>
|
||||||
|
{{ progressiveError }}
|
||||||
|
</div>
|
||||||
|
</v-card-item>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col
|
||||||
|
cols="12"
|
||||||
|
>
|
||||||
|
<v-card variant="outlined">
|
||||||
|
<v-card-item>
|
||||||
|
<div class="text-subtitle-2 font-weight-medium mb-3">
|
||||||
|
过程日志
|
||||||
|
</div>
|
||||||
|
<div class="log-box">
|
||||||
|
<div
|
||||||
|
v-for="(log, i) in logs"
|
||||||
|
:key="i"
|
||||||
|
class="text-caption log-line"
|
||||||
|
>
|
||||||
|
{{ log.time }} · {{ log.message }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="!logs.length"
|
||||||
|
class="text-caption text-medium-emphasis"
|
||||||
|
>
|
||||||
|
等待开始…
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-card-item>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<div class="d-flex flex-wrap gap-2 mt-4">
|
||||||
|
<v-btn
|
||||||
|
v-if="progressiveStatus === 'idle'"
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
prepend-icon="mdi-play"
|
||||||
|
@click="startProgressiveRegister"
|
||||||
|
>
|
||||||
|
开始创建
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
v-if="progressiveStatus === 'error'"
|
||||||
|
color="error"
|
||||||
|
variant="outlined"
|
||||||
|
prepend-icon="mdi-refresh"
|
||||||
|
@click="retryProgressiveRegister"
|
||||||
|
>
|
||||||
|
重试
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
v-if="progressiveStatus === 'registering'"
|
||||||
|
color="primary"
|
||||||
|
variant="tonal"
|
||||||
|
:loading="true"
|
||||||
|
prepend-icon="mdi-progress-clock"
|
||||||
|
>
|
||||||
|
正在执行…
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
v-if="progressiveStatus === 'success'"
|
||||||
|
color="success"
|
||||||
|
size="large"
|
||||||
|
variant="elevated"
|
||||||
|
prepend-icon="mdi-check-circle"
|
||||||
|
@click="applyTokenAndClose"
|
||||||
|
>
|
||||||
|
应用令牌并关闭
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
v-if="progressiveStatus === 'success'"
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
variant="outlined"
|
||||||
|
prepend-icon="mdi-open-in-new"
|
||||||
|
@click="openAuthPage"
|
||||||
|
>
|
||||||
|
前往绑定账户
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
<!-- 底部操作按钮 -->
|
<!-- 底部操作按钮 -->
|
||||||
@ -528,7 +554,7 @@
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-btn
|
<v-btn
|
||||||
v-if="currentStep < 4"
|
v-if="currentStep < totalSteps && currentStep !== 4"
|
||||||
:disabled="currentStep === 3 && !storageType"
|
:disabled="currentStep === 3 && !storageType"
|
||||||
size="large"
|
size="large"
|
||||||
color="primary"
|
color="primary"
|
||||||
@ -541,7 +567,7 @@
|
|||||||
</v-icon>
|
</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
v-else
|
v-if="currentStep === totalSteps || currentStep === 4"
|
||||||
size="large"
|
size="large"
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="elevated"
|
variant="elevated"
|
||||||
@ -554,14 +580,24 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { getSetting } from '@/utils/settings'
|
import { getSetting, setSetting } from '@/utils/settings'
|
||||||
const emit = defineEmits(['close'])
|
import axios from '@/axios/axios'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
const emit = defineEmits(['close', 'success'])
|
||||||
const kvserverurl = getSetting('server.authDomain')
|
const kvserverurl = getSetting('server.authDomain')
|
||||||
const currentStep = ref(1)
|
const currentStep = ref(1)
|
||||||
const totalSteps = 4
|
const totalSteps = 5
|
||||||
const storageType = ref('')
|
const storageType = ref('')
|
||||||
|
|
||||||
|
// 渐进式注册相关状态
|
||||||
|
const progressiveStatus = ref('idle') // 'idle' | 'registering' | 'success' | 'error'
|
||||||
|
const progressiveError = ref('')
|
||||||
|
const deviceInfo = ref(null)
|
||||||
|
const tokenData = ref(null) // 保存获取到的 token 数据
|
||||||
|
const logs = ref([])
|
||||||
|
const stepStates = ref({ 1: false, 2: false, 3: false, 4: false })
|
||||||
|
|
||||||
const nextStep = () => {
|
const nextStep = () => {
|
||||||
if (currentStep.value < totalSteps) {
|
if (currentStep.value < totalSteps) {
|
||||||
currentStep.value++
|
currentStep.value++
|
||||||
@ -586,6 +622,147 @@ const finish = () => {
|
|||||||
const openKVSite = () => {
|
const openKVSite = () => {
|
||||||
window.open(kvserverurl, '_blank')
|
window.open(kvserverurl, '_blank')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 渐进式注册相关方法
|
||||||
|
const goToProgressiveStep = () => {
|
||||||
|
currentStep.value = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const progressValue = computed(() => {
|
||||||
|
const done = Object.values(stepStates.value).filter(Boolean).length
|
||||||
|
return (done / 4) * 100
|
||||||
|
})
|
||||||
|
|
||||||
|
const statusColor = computed(() => {
|
||||||
|
return progressiveStatus.value === 'success'
|
||||||
|
? 'success'
|
||||||
|
: progressiveStatus.value === 'error'
|
||||||
|
? 'error'
|
||||||
|
: 'primary'
|
||||||
|
})
|
||||||
|
|
||||||
|
const statusIcon = computed(() => {
|
||||||
|
return progressiveStatus.value === 'success'
|
||||||
|
? 'mdi-check-circle'
|
||||||
|
: progressiveStatus.value === 'error'
|
||||||
|
? 'mdi-alert-circle'
|
||||||
|
: progressiveStatus.value === 'registering'
|
||||||
|
? 'mdi-progress-clock'
|
||||||
|
: 'mdi-rocket-launch'
|
||||||
|
})
|
||||||
|
|
||||||
|
const statusTitle = computed(() => {
|
||||||
|
return progressiveStatus.value === 'success'
|
||||||
|
? '完成!设备已创建'
|
||||||
|
: progressiveStatus.value === 'error'
|
||||||
|
? '创建失败'
|
||||||
|
: progressiveStatus.value === 'registering'
|
||||||
|
? '正在执行…'
|
||||||
|
: '准备开始'
|
||||||
|
})
|
||||||
|
|
||||||
|
const addLog = (message) => {
|
||||||
|
const now = new Date()
|
||||||
|
const hh = String(now.getHours()).padStart(2, '0')
|
||||||
|
const mm = String(now.getMinutes()).padStart(2, '0')
|
||||||
|
const ss = String(now.getSeconds()).padStart(2, '0')
|
||||||
|
logs.value.push({ time: `${hh}:${mm}:${ss}`, message })
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const generateDeviceName = () => {
|
||||||
|
return `Classworks`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主流程
|
||||||
|
const startProgressiveRegister = async () => {
|
||||||
|
if (progressiveStatus.value === 'registering') return
|
||||||
|
|
||||||
|
progressiveStatus.value = 'registering'
|
||||||
|
progressiveError.value = ''
|
||||||
|
logs.value = []
|
||||||
|
stepStates.value = { 1: false, 2: false, 3: false, 4: false }
|
||||||
|
|
||||||
|
try {
|
||||||
|
addLog('正在生成设备信息…')
|
||||||
|
const uuid = uuidv4()
|
||||||
|
const deviceName = generateDeviceName()
|
||||||
|
const serverUrl = getSetting('server.domain')
|
||||||
|
stepStates.value[1] = true
|
||||||
|
|
||||||
|
addLog('向服务器注册设备…')
|
||||||
|
const response = await axios.post(`${serverUrl}/devices`, { uuid, deviceName })
|
||||||
|
void response
|
||||||
|
stepStates.value[2] = true
|
||||||
|
|
||||||
|
// 保存设备信息
|
||||||
|
deviceInfo.value = { uuid, deviceName, createdAt: new Date().toISOString(), registered: true }
|
||||||
|
localStorage.setItem('Classworks_progressive_device', JSON.stringify(deviceInfo.value))
|
||||||
|
|
||||||
|
addLog('获取访问令牌…')
|
||||||
|
try {
|
||||||
|
const tokenResp = await axios.post(`${serverUrl}/apps/auth/token`, {
|
||||||
|
namespace: uuid,
|
||||||
|
password: '',
|
||||||
|
appId: 'd158067f53627d2b98babe8bffd2fd7d',
|
||||||
|
})
|
||||||
|
if (tokenResp.data && tokenResp.data.token) {
|
||||||
|
// 保存 token 数据供后续使用
|
||||||
|
tokenData.value = tokenResp.data
|
||||||
|
setSetting('server.kvToken', tokenResp.data.token)
|
||||||
|
|
||||||
|
// 如果返回了 device 信息,保存 uuid
|
||||||
|
if (tokenResp.data.device?.uuid) {
|
||||||
|
setSetting('device.uuid', tokenResp.data.device.uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
addLog('已获取 Token 并写入配置')
|
||||||
|
} else {
|
||||||
|
addLog('未返回 Token,您可以稍后在授权页完成配置')
|
||||||
|
}
|
||||||
|
} catch (tokenErr) {
|
||||||
|
console.warn('自动获取 Token 失败:', tokenErr)
|
||||||
|
addLog('自动获取 Token 失败,可在授权页手动完成')
|
||||||
|
}
|
||||||
|
stepStates.value[3] = true
|
||||||
|
|
||||||
|
addLog('完成!您可以应用令牌或前往授权页面继续配置')
|
||||||
|
stepStates.value[4] = true
|
||||||
|
progressiveStatus.value = 'success'
|
||||||
|
} catch (error) {
|
||||||
|
console.error('设备注册失败:', error)
|
||||||
|
progressiveError.value = error.response?.data?.message || error.message || '网络连接失败'
|
||||||
|
addLog('失败:' + progressiveError.value)
|
||||||
|
progressiveStatus.value = 'error'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const retryProgressiveRegister = () => {
|
||||||
|
progressiveStatus.value = 'idle'
|
||||||
|
progressiveError.value = ''
|
||||||
|
logs.value = []
|
||||||
|
stepStates.value = { 1: false, 2: false, 3: false, 4: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
const openAuthPage = () => {
|
||||||
|
const info = deviceInfo.value
|
||||||
|
if (!info?.uuid) return
|
||||||
|
const authDomain = getSetting('server.authDomain')
|
||||||
|
const url = `${authDomain}/?uuid=${encodeURIComponent(info.uuid)}&tolinktoaccount=true`
|
||||||
|
window.open(url, '_blank')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用令牌并关闭(触发 success 事件让父组件更新)
|
||||||
|
const applyTokenAndClose = () => {
|
||||||
|
if (tokenData.value) {
|
||||||
|
// 触发 success 事件,传递 token 数据,类似 DeviceAuthDialog
|
||||||
|
emit('success', tokenData.value)
|
||||||
|
}
|
||||||
|
// 关闭引导
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -668,4 +845,50 @@ const openKVSite = () => {
|
|||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 渐进式注册卡片样式 */
|
||||||
|
.progressive-register-card {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border: 2px solid transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progressive-register-card:hover {
|
||||||
|
box-shadow: 0 8px 24px rgba(0,0,0,0.12) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progressive-register-card .card-icon-wrapper {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progressive-register-card .card-actions {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progressive-register-card code {
|
||||||
|
background: rgba(0,0,0,0.1);
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 渐进式注册页面样式 */
|
||||||
|
.log-box {
|
||||||
|
height: 140px;
|
||||||
|
overflow: auto;
|
||||||
|
background: rgba(0, 0, 0, 0.04);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-line + .log-line {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-code {
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
304
src/components/auth/ProgressiveRegisterPage.vue
Normal file
304
src/components/auth/ProgressiveRegisterPage.vue
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
<template>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title class="d-flex align-center">
|
||||||
|
<v-icon
|
||||||
|
icon="mdi-account-plus"
|
||||||
|
class="mr-2"
|
||||||
|
/>
|
||||||
|
渐进式注册
|
||||||
|
</v-card-title>
|
||||||
|
|
||||||
|
<v-card-text>
|
||||||
|
<div v-if="!isRegistered && !isRegistering">
|
||||||
|
<p class="text-body-1 mb-4">
|
||||||
|
快速创建设备并开始使用 Classworks 云端功能
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<v-alert
|
||||||
|
type="info"
|
||||||
|
variant="tonal"
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<v-icon icon="mdi-information" />
|
||||||
|
</template>
|
||||||
|
系统将自动为您创建设备并获取访问令牌,无需手动配置
|
||||||
|
</v-alert>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 注册进行中 -->
|
||||||
|
<div v-else-if="isRegistering">
|
||||||
|
<div class="text-center py-4">
|
||||||
|
<v-progress-circular
|
||||||
|
indeterminate
|
||||||
|
size="48"
|
||||||
|
color="primary"
|
||||||
|
class="mb-4"
|
||||||
|
/>
|
||||||
|
<p class="text-h6 mb-2">
|
||||||
|
正在注册设备...
|
||||||
|
</p>
|
||||||
|
<p class="text-body-2 text-medium-emphasis">
|
||||||
|
{{ registrationStep }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 注册成功 -->
|
||||||
|
<div v-else-if="isRegistered && deviceInfo">
|
||||||
|
<v-alert
|
||||||
|
type="success"
|
||||||
|
variant="tonal"
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<v-icon icon="mdi-check-circle" />
|
||||||
|
</template>
|
||||||
|
设备注册成功!已自动获取访问令牌
|
||||||
|
</v-alert>
|
||||||
|
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<template #prepend>
|
||||||
|
<v-icon icon="mdi-identifier" />
|
||||||
|
</template>
|
||||||
|
<v-list-item-title>设备名称</v-list-item-title>
|
||||||
|
<v-list-item-subtitle>{{ deviceInfo.deviceName }}</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
|
||||||
|
<v-list-item>
|
||||||
|
<template #prepend>
|
||||||
|
<v-icon icon="mdi-key" />
|
||||||
|
</template>
|
||||||
|
<v-list-item-title>设备 UUID</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="font-mono text-caption">
|
||||||
|
{{ deviceInfo.uuid }}
|
||||||
|
</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
|
||||||
|
<v-alert
|
||||||
|
type="info"
|
||||||
|
variant="tonal"
|
||||||
|
class="mt-4"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<v-icon icon="mdi-information" />
|
||||||
|
</template>
|
||||||
|
您可以点击下方按钮访问云端控制台来设置密码和管理高级功能
|
||||||
|
</v-alert>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 错误状态 -->
|
||||||
|
<div v-else-if="errorMessage">
|
||||||
|
<v-alert
|
||||||
|
type="error"
|
||||||
|
variant="tonal"
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<v-icon icon="mdi-alert-circle" />
|
||||||
|
</template>
|
||||||
|
{{ errorMessage }}
|
||||||
|
</v-alert>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer />
|
||||||
|
|
||||||
|
<!-- 注册按钮 -->
|
||||||
|
<v-btn
|
||||||
|
v-if="!isRegistered && !isRegistering"
|
||||||
|
color="primary"
|
||||||
|
prepend-icon="mdi-plus"
|
||||||
|
:loading="isRegistering"
|
||||||
|
@click="registerDevice"
|
||||||
|
>
|
||||||
|
注册设备
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<!-- 访问控制台按钮 -->
|
||||||
|
<v-btn
|
||||||
|
v-if="isRegistered && deviceInfo"
|
||||||
|
color="success"
|
||||||
|
prepend-icon="mdi-open-in-new"
|
||||||
|
@click="openConsole"
|
||||||
|
>
|
||||||
|
访问控制台
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<!-- 重试按钮 -->
|
||||||
|
<v-btn
|
||||||
|
v-if="errorMessage"
|
||||||
|
color="primary"
|
||||||
|
prepend-icon="mdi-refresh"
|
||||||
|
@click="resetAndRetry"
|
||||||
|
>
|
||||||
|
重试
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
variant="text"
|
||||||
|
@click="$emit('close')"
|
||||||
|
>
|
||||||
|
关闭
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { getSetting, setSetting } from '@/utils/settings'
|
||||||
|
import axios from '@/axios/axios'
|
||||||
|
|
||||||
|
// 事件定义
|
||||||
|
const emit = defineEmits(['close', 'success'])
|
||||||
|
|
||||||
|
// 本地存储键名
|
||||||
|
const PROGRESSIVE_DEVICE_KEY = 'Classworks_progressive_device'
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const isRegistering = ref(false)
|
||||||
|
const isRegistered = ref(false)
|
||||||
|
const deviceInfo = ref(null)
|
||||||
|
const errorMessage = ref('')
|
||||||
|
const registrationStep = ref('')
|
||||||
|
|
||||||
|
// 生成 UUID
|
||||||
|
const generateUUID = () => {
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||||
|
const r = Math.random() * 16 | 0
|
||||||
|
const v = c === 'x' ? r : (r & 0x3 | 0x8)
|
||||||
|
return v.toString(16)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成随机设备名称
|
||||||
|
const generateDeviceName = () => {
|
||||||
|
const adjectives = ['快速', '智能', '高效', '便捷', '实用', '简洁', '现代', '专业']
|
||||||
|
const nouns = ['设备', '终端', '工作站', '助手', '伴侣']
|
||||||
|
const numbers = Math.floor(Math.random() * 999) + 1
|
||||||
|
|
||||||
|
const adjective = adjectives[Math.floor(Math.random() * adjectives.length)]
|
||||||
|
const noun = nouns[Math.floor(Math.random() * nouns.length)]
|
||||||
|
|
||||||
|
return `${adjective}${noun}${numbers}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存设备信息到本地存储
|
||||||
|
const saveDeviceInfo = (info) => {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(PROGRESSIVE_DEVICE_KEY, JSON.stringify(info))
|
||||||
|
deviceInfo.value = info
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存设备信息失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册设备
|
||||||
|
const registerDevice = async () => {
|
||||||
|
if (isRegistering.value) return
|
||||||
|
|
||||||
|
isRegistering.value = true
|
||||||
|
errorMessage.value = ''
|
||||||
|
registrationStep.value = '正在生成设备信息...'
|
||||||
|
|
||||||
|
try {
|
||||||
|
const uuid = generateUUID()
|
||||||
|
const deviceName = generateDeviceName()
|
||||||
|
const serverUrl = getSetting('server.domain')
|
||||||
|
|
||||||
|
registrationStep.value = '正在注册设备到服务器...'
|
||||||
|
console.log('开始注册设备:', { uuid, deviceName, serverUrl })
|
||||||
|
|
||||||
|
// 调用设备注册接口
|
||||||
|
const response = await axios.post(`${serverUrl}/devices`, {
|
||||||
|
uuid,
|
||||||
|
deviceName
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('设备注册响应:', response.data)
|
||||||
|
|
||||||
|
// 保存设备信息
|
||||||
|
const newDeviceInfo = {
|
||||||
|
uuid,
|
||||||
|
deviceName,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
registered: true
|
||||||
|
}
|
||||||
|
|
||||||
|
saveDeviceInfo(newDeviceInfo)
|
||||||
|
|
||||||
|
registrationStep.value = '正在获取访问令牌...'
|
||||||
|
|
||||||
|
// 自动获取 token(使用 uuid 作为 namespace,密码为空)
|
||||||
|
await autoLogin(uuid)
|
||||||
|
|
||||||
|
isRegistered.value = true
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('设备注册失败:', error)
|
||||||
|
errorMessage.value = error.response?.data?.message || error.message || '网络连接失败'
|
||||||
|
} finally {
|
||||||
|
isRegistering.value = false
|
||||||
|
registrationStep.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自动登录获取 token
|
||||||
|
const autoLogin = async (uuid) => {
|
||||||
|
try {
|
||||||
|
const serverUrl = getSetting('server.domain')
|
||||||
|
|
||||||
|
// 使用设备认证接口获取 token
|
||||||
|
const response = await axios.post(`${serverUrl}/apps/auth/token`, {
|
||||||
|
namespace: uuid,
|
||||||
|
password: '' // 空密码
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.data && response.data.token) {
|
||||||
|
// 设置 token 到配置中
|
||||||
|
setSetting('server.kvToken', response.data.token)
|
||||||
|
console.log('自动登录成功,token 已设置')
|
||||||
|
|
||||||
|
// 触发成功事件
|
||||||
|
emit('success', response.data)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('自动登录失败:', error)
|
||||||
|
throw new Error('获取访问令牌失败: ' + (error.response?.data?.message || error.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开控制台
|
||||||
|
const openConsole = () => {
|
||||||
|
if (!deviceInfo.value) return
|
||||||
|
|
||||||
|
const authDomain = getSetting('server.authDomain')
|
||||||
|
const url = `${authDomain}/?uuid=${encodeURIComponent(deviceInfo.value.uuid)}`
|
||||||
|
|
||||||
|
console.log('打开控制台:', url)
|
||||||
|
window.open(url, '_blank')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置并重试
|
||||||
|
const resetAndRetry = () => {
|
||||||
|
errorMessage.value = ''
|
||||||
|
isRegistered.value = false
|
||||||
|
deviceInfo.value = null
|
||||||
|
// 清除本地存储
|
||||||
|
try {
|
||||||
|
localStorage.removeItem(PROGRESSIVE_DEVICE_KEY)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('清除本地存储失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.font-mono {
|
||||||
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -4,54 +4,115 @@
|
|||||||
<v-progress-linear v-if="loading" indeterminate color="primary" />
|
<v-progress-linear v-if="loading" indeterminate color="primary" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<v-card-title>
|
<v-card-title>
|
||||||
<v-icon class="me-2"> mdi-cloud-check </v-icon>
|
<v-icon
|
||||||
|
class="me-2"
|
||||||
|
>
|
||||||
|
mdi-cloud-check
|
||||||
|
</v-icon>
|
||||||
设备信息
|
设备信息
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
|
|
||||||
<v-card-text v-if="hasNamespaceInfo">
|
<v-card-text v-if="hasNamespaceInfo">
|
||||||
<!-- 用户信息与头像 -->
|
<!-- 未绑定账号时的提示卡片 -->
|
||||||
<div v-if="namespaceInfo.account" class="d-flex align-center mb-4">
|
<div
|
||||||
|
v-if="namespaceInfo.hasAccount === false"
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
|
<v-alert
|
||||||
|
type="warning"
|
||||||
|
variant="tonal"
|
||||||
|
border
|
||||||
|
>
|
||||||
|
<v-alert-title>设备未绑定账号</v-alert-title>
|
||||||
|
<div>当前设备尚未绑定账号,部分功能可能受限。请前往绑定账号以获得完整体验。</div>
|
||||||
|
<v-btn
|
||||||
|
class="mt-3"
|
||||||
|
variant="outlined"
|
||||||
|
:href="getBindAccountUrl()"
|
||||||
|
append-icon="mdi-open-in-new"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
前往绑定账号
|
||||||
|
</v-btn>
|
||||||
|
</v-alert>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 已绑定账号时显示账号信息 -->
|
||||||
|
<div
|
||||||
|
v-if="namespaceInfo.hasAccount && namespaceInfo.account"
|
||||||
|
class="d-flex align-center mb-4"
|
||||||
|
>
|
||||||
<v-card
|
<v-card
|
||||||
border hover
|
border
|
||||||
|
hover
|
||||||
class="w-100"
|
class="w-100"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
:prepend-avatar="namespaceInfo.account.avatarUrl"
|
:prepend-avatar="namespaceInfo.account.avatarUrl"
|
||||||
:title="namespaceInfo.account.name || '未命名用户'"
|
:title="namespaceInfo.account.name || '未命名用户'"
|
||||||
:subtitle="
|
:subtitle="'此设备由贵校管理 管理员账号 ID: ' + namespaceInfo.account.id"
|
||||||
'此设备由贵校管理 管理员账号 ID: ' + namespaceInfo.account.id
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<v-card-text
|
|
||||||
>此设备由贵校或贵单位管理,该管理员系此空间所有者,如有疑问请咨询他,对于恶意绑定、滥用行为请反馈。</v-card-text
|
|
||||||
>
|
>
|
||||||
|
<v-card-text>
|
||||||
|
此设备由贵校或贵单位管理,该管理员系此空间所有者,如有疑问请咨询他,对于恶意绑定、滥用行为请反馈。
|
||||||
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 设备信息卡片 -->
|
<!-- 设备信息卡片 -->
|
||||||
<v-card v-if="namespaceInfo.device" variant="tonal" class="mb-4" border hover>
|
<v-card
|
||||||
<v-card-title class="pb-1"> 设备信息 </v-card-title>
|
v-if="namespaceInfo.device"
|
||||||
|
variant="tonal"
|
||||||
|
class="mb-4"
|
||||||
|
border
|
||||||
|
hover
|
||||||
|
>
|
||||||
|
<v-card-title class="pb-1">
|
||||||
|
设备信息
|
||||||
|
</v-card-title>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<div class="d-flex flex-column gap-1">
|
<div class="d-flex flex-column gap-1">
|
||||||
<div class="d-flex align-center">
|
<div class="d-flex align-center">
|
||||||
<v-icon size="small" class="me-2"> mdi-tag </v-icon>
|
<v-icon
|
||||||
|
size="small"
|
||||||
|
class="me-2"
|
||||||
|
>
|
||||||
|
mdi-tag
|
||||||
|
</v-icon>
|
||||||
<span class="font-weight-medium me-2">设备名称:</span>
|
<span class="font-weight-medium me-2">设备名称:</span>
|
||||||
<span>{{ namespaceInfo.device.name || "未命名设备" }}</span>
|
<span>{{ namespaceInfo.device.name || '未命名设备' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-center">
|
<div class="d-flex align-center">
|
||||||
<v-icon size="small" class="me-2"> mdi-identifier </v-icon>
|
<v-icon
|
||||||
|
size="small"
|
||||||
|
class="me-2"
|
||||||
|
>
|
||||||
|
mdi-identifier
|
||||||
|
</v-icon>
|
||||||
<span class="font-weight-medium me-2">设备 ID:</span>
|
<span class="font-weight-medium me-2">设备 ID:</span>
|
||||||
<span>{{ namespaceInfo.device.id }}</span>
|
<span>{{ namespaceInfo.device.id }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-center">
|
<!-- 仅未绑定账号时显示 UUID -->
|
||||||
<v-icon size="small" class="me-2"> mdi-uuid </v-icon>
|
<div
|
||||||
|
v-if="namespaceInfo.hasAccount === false && namespaceInfo.device.uuid"
|
||||||
|
class="d-flex align-center"
|
||||||
|
>
|
||||||
|
<v-icon
|
||||||
|
size="small"
|
||||||
|
class="me-2"
|
||||||
|
>
|
||||||
|
mdi-uuid
|
||||||
|
</v-icon>
|
||||||
<span class="font-weight-medium me-2">UUID:</span>
|
<span class="font-weight-medium me-2">UUID:</span>
|
||||||
<span class="text-truncate">{{
|
<span class="text-truncate">{{ namespaceInfo.device.uuid }}</span>
|
||||||
namespaceInfo.device.uuid || "未知"
|
|
||||||
}}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-center">
|
<div class="d-flex align-center">
|
||||||
<v-icon size="small" class="me-2"> mdi-calendar </v-icon>
|
<v-icon
|
||||||
|
size="small"
|
||||||
|
class="me-2"
|
||||||
|
>
|
||||||
|
mdi-calendar
|
||||||
|
</v-icon>
|
||||||
<span class="font-weight-medium me-2">创建时间:</span>
|
<span class="font-weight-medium me-2">创建时间:</span>
|
||||||
<span>{{ formatDate(namespaceInfo.device.createdAt) }}</span>
|
<span>{{ formatDate(namespaceInfo.device.createdAt) }}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -59,33 +120,54 @@
|
|||||||
v-if="namespaceInfo.device.updatedAt"
|
v-if="namespaceInfo.device.updatedAt"
|
||||||
class="d-flex align-center"
|
class="d-flex align-center"
|
||||||
>
|
>
|
||||||
<v-icon size="small" class="me-2"> mdi-calendar-clock </v-icon>
|
<v-icon
|
||||||
|
size="small"
|
||||||
|
class="me-2"
|
||||||
|
>
|
||||||
|
mdi-calendar-clock
|
||||||
|
</v-icon>
|
||||||
<span class="font-weight-medium me-2">更新时间:</span>
|
<span class="font-weight-medium me-2">更新时间:</span>
|
||||||
<span>{{ formatDate(namespaceInfo.device.updatedAt) }}</span>
|
<span>{{ formatDate(namespaceInfo.device.updatedAt) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</v-card-text> </v-card
|
</v-card-text>
|
||||||
><v-card title="Classworks KV" subtitle="文档形键值数据库" border hover
|
</v-card>
|
||||||
><v-card-text
|
|
||||||
>Classworks KV
|
<v-card
|
||||||
是厚浪云推出的文档形键值数据库,其是一个开放的云应用平台,为各种应用提供存储服务。此设备正在使用其服务,如果您希望管理设备信息,请前往
|
title="Classworks KV"
|
||||||
Classworks KV
|
subtitle="文档形键值数据库"
|
||||||
的网站,如果您在服务推出前就在使用 Classworks,您的数据已被自动迁移。
|
border
|
||||||
<br/><br/>Classworks KV 的全域管理员是 <a href="https://wuyuan.dev" target="_blank">孙悟元</a></v-card-text
|
hover
|
||||||
><v-card-actions
|
>
|
||||||
><v-btn
|
<v-card-text>
|
||||||
|
Classworks KV 是厚浪云推出的文档形键值数据库,其是一个开放的云应用平台,为各种应用提供存储服务。此设备正在使用其服务,如果您希望管理设备信息,请前往 Classworks KV 的网站,如果您在服务推出前就在使用 Classworks,您的数据已被自动迁移。
|
||||||
|
<br><br>
|
||||||
|
Classworks KV 的全域管理员是
|
||||||
|
<a
|
||||||
|
href="https://wuyuan.dev"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
孙悟元
|
||||||
|
</a>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn
|
||||||
:href="defaultAuthServer"
|
:href="defaultAuthServer"
|
||||||
class="text-none"
|
class="text-none"
|
||||||
append-icon="mdi-open-in-new"
|
append-icon="mdi-open-in-new"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>前往 Classworks KV</v-btn
|
|
||||||
></v-card-actions
|
|
||||||
></v-card
|
|
||||||
>
|
>
|
||||||
|
前往 Classworks KV
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
<v-card-text v-else>
|
<v-card-text v-else>
|
||||||
<v-alert type="info" variant="tonal">
|
<v-alert
|
||||||
|
type="info"
|
||||||
|
variant="tonal"
|
||||||
|
>
|
||||||
<v-alert-title>未获取到设备信息</v-alert-title>
|
<v-alert-title>未获取到设备信息</v-alert-title>
|
||||||
<p>您尚未完成云端存储授权或连接失败,请点击下方按钮进行初始化。</p>
|
<p>您尚未完成云端存储授权或连接失败,请点击下方按钮进行初始化。</p>
|
||||||
</v-alert>
|
</v-alert>
|
||||||
@ -102,17 +184,28 @@
|
|||||||
刷新设备信息
|
刷新设备信息
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
<v-btn color="error" variant="outlined" @click="showReinitDialog = true">
|
<v-btn
|
||||||
|
color="error"
|
||||||
|
variant="outlined"
|
||||||
|
@click="showReinitDialog = true"
|
||||||
|
>
|
||||||
重新初始化云端存储
|
重新初始化云端存储
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
|
|
||||||
<!-- 重新初始化确认对话框 -->
|
<!-- 重新初始化确认对话框 -->
|
||||||
<v-dialog v-model="showReinitDialog" max-width="500">
|
<v-dialog
|
||||||
|
v-model="showReinitDialog"
|
||||||
|
max-width="500"
|
||||||
|
>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-card-title>确认重新初始化</v-card-title>
|
<v-card-title>确认重新初始化</v-card-title>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-alert type="warning" variant="tonal" class="mb-3">
|
<v-alert
|
||||||
|
type="warning"
|
||||||
|
variant="tonal"
|
||||||
|
class="mb-3"
|
||||||
|
>
|
||||||
<v-alert-title>警告</v-alert-title>
|
<v-alert-title>警告</v-alert-title>
|
||||||
此操作将清除当前的云端存储配置(包括 Token),您需要重新进行授权。
|
此操作将清除当前的云端存储配置(包括 Token),您需要重新进行授权。
|
||||||
</v-alert>
|
</v-alert>
|
||||||
@ -120,8 +213,18 @@
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-btn variant="text" @click="showReinitDialog = false">取消</v-btn>
|
<v-btn
|
||||||
<v-btn color="error" @click="confirmReinitialize">确认</v-btn>
|
variant="text"
|
||||||
|
@click="showReinitDialog = false"
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
color="error"
|
||||||
|
@click="confirmReinitialize"
|
||||||
|
>
|
||||||
|
确认
|
||||||
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
@ -190,6 +293,13 @@ export default {
|
|||||||
async reloadInfo() {
|
async reloadInfo() {
|
||||||
await this.fetchNamespaceInfo();
|
await this.fetchNamespaceInfo();
|
||||||
},
|
},
|
||||||
|
getBindAccountUrl() {
|
||||||
|
const uuid = this.namespaceInfo?.device?.uuid;
|
||||||
|
if (uuid) {
|
||||||
|
return `${this.defaultAuthServer}?uuid=${encodeURIComponent(uuid)}&tolinktoaccount=true`;
|
||||||
|
}
|
||||||
|
return this.defaultAuthServer;
|
||||||
|
},
|
||||||
confirmReinitialize() {
|
confirmReinitialize() {
|
||||||
// 删除 token 配置(设置为空字符串以触发 shouldShowInit)
|
// 删除 token 配置(设置为空字符串以触发 shouldShowInit)
|
||||||
setSetting('server.kvToken', '');
|
setSetting('server.kvToken', '');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user