mirror of
https://github.com/ZeroCatDev/Classworks.git
synced 2025-12-07 21:13:11 +00:00
feat: 实现渐进式设备注册功能,自动生成设备信息并获取访问令牌,优化用户体验
This commit is contained in:
parent
670666aa41
commit
49ea5f1b2f
@ -17,16 +17,16 @@ axiosInstance.interceptors.request.use(
|
||||
|
||||
// 只有在 kv-server 或 classworkscloud 模式下才添加请求头
|
||||
if (provider === "kv-server" || provider === "classworkscloud") {
|
||||
// 确保每次请求时都获取最新的 siteKey
|
||||
const siteKey = getSetting("server.siteKey");
|
||||
if (siteKey) {
|
||||
requestConfig.headers["x-site-key"] = Base64.encode(siteKey);
|
||||
}
|
||||
|
||||
// 自动添加命名空间密码
|
||||
const namespacePassword = getSetting("namespace.password");
|
||||
if (namespacePassword) {
|
||||
requestConfig.headers["x-namespace-password"] = Base64.encode(namespacePassword);
|
||||
// 优先使用新的 kvToken
|
||||
const kvToken = getSetting("server.kvToken");
|
||||
if (kvToken) {
|
||||
requestConfig.headers["x-app-token"] = kvToken;
|
||||
} else {
|
||||
// 向后兼容旧的 siteKey
|
||||
const siteKey = getSetting("server.siteKey");
|
||||
if (siteKey) {
|
||||
requestConfig.headers["x-site-key"] = Base64.encode(siteKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -99,40 +99,41 @@
|
||||
</v-card-item>
|
||||
</v-card>
|
||||
</div>
|
||||
<div class="options-buttons">
|
||||
<v-btn
|
||||
variant="tonal"
|
||||
prepend-icon="mdi-laptop"
|
||||
size="small"
|
||||
@click="useLocalMode"
|
||||
>
|
||||
使用本地模式
|
||||
</v-btn>
|
||||
<v-btn
|
||||
variant="tonal"
|
||||
prepend-icon="mdi-flash"
|
||||
size="small"
|
||||
@click="handleAutoAuthorize"
|
||||
>
|
||||
授权码式授权(弃用)
|
||||
</v-btn>
|
||||
<v-btn
|
||||
variant="tonal"
|
||||
prepend-icon="mdi-key"
|
||||
size="small"
|
||||
@click="showTokenDialog = true"
|
||||
>
|
||||
输入 Token
|
||||
</v-btn>
|
||||
<v-btn
|
||||
variant="tonal"
|
||||
prepend-icon="mdi-code-tags"
|
||||
size="small"
|
||||
@click="showAlternativeCodeDialog = true"
|
||||
>
|
||||
输入替代代码
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<div class="options-buttons">
|
||||
<v-btn
|
||||
variant="tonal"
|
||||
prepend-icon="mdi-laptop"
|
||||
size="small"
|
||||
@click="useLocalMode"
|
||||
>
|
||||
使用本地模式
|
||||
</v-btn>
|
||||
<v-btn
|
||||
variant="tonal"
|
||||
prepend-icon="mdi-flash"
|
||||
size="small"
|
||||
@click="handleAutoAuthorize"
|
||||
>
|
||||
授权码式授权(弃用)
|
||||
</v-btn>
|
||||
<v-btn
|
||||
variant="tonal"
|
||||
prepend-icon="mdi-key"
|
||||
size="small"
|
||||
@click="showTokenDialog = true"
|
||||
>
|
||||
输入 Token
|
||||
</v-btn>
|
||||
<v-btn
|
||||
variant="tonal"
|
||||
prepend-icon="mdi-code-tags"
|
||||
size="small"
|
||||
@click="showAlternativeCodeDialog = true"
|
||||
>
|
||||
输入替代代码
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="footer-hint">
|
||||
@ -145,7 +146,10 @@
|
||||
v-model="showGuideDialog"
|
||||
max-width="600"
|
||||
>
|
||||
<FirstTimeGuide @close="showGuideDialog = false" />
|
||||
<FirstTimeGuide
|
||||
@close="showGuideDialog = false"
|
||||
@success="handleGuideSuccess"
|
||||
/>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog
|
||||
@ -275,6 +279,13 @@ const handleAutoAuthorize = () => {
|
||||
window.location.href = url
|
||||
}
|
||||
|
||||
const handleGuideSuccess = (tokenData) => {
|
||||
showGuideDialog.value = false
|
||||
console.log('渐进式注册成功:', tokenData)
|
||||
evaluateVisibility()
|
||||
emit('done')
|
||||
}
|
||||
|
||||
const handleAuthSuccess = (tokenData) => {
|
||||
showDeviceAuthDialog.value = false
|
||||
console.log('认证成功:', tokenData)
|
||||
|
||||
@ -140,141 +140,7 @@
|
||||
</div>
|
||||
</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>
|
||||
|
||||
<!-- 步骤 3: 询问使用场景 -->
|
||||
@ -386,6 +252,32 @@
|
||||
</h3>
|
||||
</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
|
||||
:variant="kvserverurl=='https://kv.houlang.cloud'? 'elevated' : 'outlined'"
|
||||
:color=" kvserverurl=='https://kv.houlang.cloud'? 'primary' : 'error' "
|
||||
@ -398,6 +290,7 @@
|
||||
>
|
||||
mdi-open-in-new
|
||||
</v-icon>
|
||||
|
||||
<h4 class="text-h6 font-weight-bold">
|
||||
请访问 {{ kvserverurl=='https://kv.houlang.cloud'? 'Classworks KV' : '自定义的 Classworks KV 实例 ' }} 控制台
|
||||
</h4>
|
||||
@ -409,33 +302,6 @@
|
||||
</h6>
|
||||
</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
|
||||
@ -511,6 +377,166 @@
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</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>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
@ -528,7 +554,7 @@
|
||||
</v-btn>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
v-if="currentStep < 4"
|
||||
v-if="currentStep < totalSteps && currentStep !== 4"
|
||||
:disabled="currentStep === 3 && !storageType"
|
||||
size="large"
|
||||
color="primary"
|
||||
@ -541,7 +567,7 @@
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-else
|
||||
v-if="currentStep === totalSteps || currentStep === 4"
|
||||
size="large"
|
||||
color="primary"
|
||||
variant="elevated"
|
||||
@ -554,14 +580,24 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { getSetting } from '@/utils/settings'
|
||||
const emit = defineEmits(['close'])
|
||||
import { ref, computed } from 'vue'
|
||||
import { getSetting, setSetting } from '@/utils/settings'
|
||||
import axios from '@/axios/axios'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
const emit = defineEmits(['close', 'success'])
|
||||
const kvserverurl = getSetting('server.authDomain')
|
||||
const currentStep = ref(1)
|
||||
const totalSteps = 4
|
||||
const totalSteps = 5
|
||||
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 = () => {
|
||||
if (currentStep.value < totalSteps) {
|
||||
currentStep.value++
|
||||
@ -586,6 +622,147 @@ const finish = () => {
|
||||
const openKVSite = () => {
|
||||
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>
|
||||
|
||||
<style scoped>
|
||||
@ -668,4 +845,50 @@ const openKVSite = () => {
|
||||
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>
|
||||
|
||||
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" />
|
||||
</template>
|
||||
|
||||
|
||||
<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-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
|
||||
border hover
|
||||
border
|
||||
hover
|
||||
class="w-100"
|
||||
variant="tonal"
|
||||
:prepend-avatar="namespaceInfo.account.avatarUrl"
|
||||
:title="namespaceInfo.account.name || '未命名用户'"
|
||||
:subtitle="
|
||||
'此设备由贵校管理 管理员账号 ID: ' + namespaceInfo.account.id
|
||||
"
|
||||
:subtitle="'此设备由贵校管理 管理员账号 ID: ' + namespaceInfo.account.id"
|
||||
>
|
||||
<v-card-text
|
||||
>此设备由贵校或贵单位管理,该管理员系此空间所有者,如有疑问请咨询他,对于恶意绑定、滥用行为请反馈。</v-card-text
|
||||
>
|
||||
<v-card-text>
|
||||
此设备由贵校或贵单位管理,该管理员系此空间所有者,如有疑问请咨询他,对于恶意绑定、滥用行为请反馈。
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
<!-- 设备信息卡片 -->
|
||||
<v-card v-if="namespaceInfo.device" variant="tonal" class="mb-4" border hover>
|
||||
<v-card-title class="pb-1"> 设备信息 </v-card-title>
|
||||
<v-card
|
||||
v-if="namespaceInfo.device"
|
||||
variant="tonal"
|
||||
class="mb-4"
|
||||
border
|
||||
hover
|
||||
>
|
||||
<v-card-title class="pb-1">
|
||||
设备信息
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<div class="d-flex flex-column gap-1">
|
||||
<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>{{ namespaceInfo.device.name || "未命名设备" }}</span>
|
||||
<span>{{ namespaceInfo.device.name || '未命名设备' }}</span>
|
||||
</div>
|
||||
<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>{{ namespaceInfo.device.id }}</span>
|
||||
</div>
|
||||
<div class="d-flex align-center">
|
||||
<v-icon size="small" class="me-2"> mdi-uuid </v-icon>
|
||||
<!-- 仅未绑定账号时显示 UUID -->
|
||||
<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="text-truncate">{{
|
||||
namespaceInfo.device.uuid || "未知"
|
||||
}}</span>
|
||||
<span class="text-truncate">{{ namespaceInfo.device.uuid }}</span>
|
||||
</div>
|
||||
<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>{{ formatDate(namespaceInfo.device.createdAt) }}</span>
|
||||
</div>
|
||||
@ -59,33 +120,54 @@
|
||||
v-if="namespaceInfo.device.updatedAt"
|
||||
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>{{ formatDate(namespaceInfo.device.updatedAt) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text> </v-card
|
||||
><v-card title="Classworks KV" subtitle="文档形键值数据库" border hover
|
||||
><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
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<v-card
|
||||
title="Classworks KV"
|
||||
subtitle="文档形键值数据库"
|
||||
border
|
||||
hover
|
||||
>
|
||||
<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"
|
||||
class="text-none"
|
||||
append-icon="mdi-open-in-new"
|
||||
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-else>
|
||||
<v-alert type="info" variant="tonal">
|
||||
<v-alert
|
||||
type="info"
|
||||
variant="tonal"
|
||||
>
|
||||
<v-alert-title>未获取到设备信息</v-alert-title>
|
||||
<p>您尚未完成云端存储授权或连接失败,请点击下方按钮进行初始化。</p>
|
||||
</v-alert>
|
||||
@ -102,17 +184,28 @@
|
||||
刷新设备信息
|
||||
</v-btn>
|
||||
|
||||
<v-btn color="error" variant="outlined" @click="showReinitDialog = true">
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="outlined"
|
||||
@click="showReinitDialog = true"
|
||||
>
|
||||
重新初始化云端存储
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
|
||||
<!-- 重新初始化确认对话框 -->
|
||||
<v-dialog v-model="showReinitDialog" max-width="500">
|
||||
<v-dialog
|
||||
v-model="showReinitDialog"
|
||||
max-width="500"
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title>确认重新初始化</v-card-title>
|
||||
<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>
|
||||
此操作将清除当前的云端存储配置(包括 Token),您需要重新进行授权。
|
||||
</v-alert>
|
||||
@ -120,8 +213,18 @@
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="showReinitDialog = false">取消</v-btn>
|
||||
<v-btn color="error" @click="confirmReinitialize">确认</v-btn>
|
||||
<v-btn
|
||||
variant="text"
|
||||
@click="showReinitDialog = false"
|
||||
>
|
||||
取消
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
@click="confirmReinitialize"
|
||||
>
|
||||
确认
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
@ -190,6 +293,13 @@ export default {
|
||||
async reloadInfo() {
|
||||
await this.fetchNamespaceInfo();
|
||||
},
|
||||
getBindAccountUrl() {
|
||||
const uuid = this.namespaceInfo?.device?.uuid;
|
||||
if (uuid) {
|
||||
return `${this.defaultAuthServer}?uuid=${encodeURIComponent(uuid)}&tolinktoaccount=true`;
|
||||
}
|
||||
return this.defaultAuthServer;
|
||||
},
|
||||
confirmReinitialize() {
|
||||
// 删除 token 配置(设置为空字符串以触发 shouldShowInit)
|
||||
setSetting('server.kvToken', '');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user