koishi-plugin-media-luna 0.0.7 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/api.ts +37 -0
- package/client/components/SetupWizard.vue +284 -0
- package/client/components/setup/SetupAuth.vue +323 -0
- package/client/components/setup/SetupStorage.vue +159 -0
- package/client/pages/index.vue +51 -22
- package/dist/index.js +1 -1
- package/dist/style.css +1 -1
- package/lib/core/api/index.d.ts +1 -0
- package/lib/core/api/index.d.ts.map +1 -1
- package/lib/core/api/index.js +5 -1
- package/lib/core/api/index.js.map +1 -1
- package/lib/core/api/setup-api.d.ts +6 -0
- package/lib/core/api/setup-api.d.ts.map +1 -0
- package/lib/core/api/setup-api.js +205 -0
- package/lib/core/api/setup-api.js.map +1 -0
- package/lib/plugins/connector-chat-api/index.js +2 -2
- package/lib/plugins/connector-dalle/index.js +2 -2
- package/lib/plugins/connector-dalle/index.js.map +1 -1
- package/lib/plugins/connector-flux/index.js +2 -2
- package/lib/plugins/connector-sd-webui/index.js +2 -2
- package/lib/plugins/webui-auth/index.d.ts.map +1 -1
- package/lib/plugins/webui-auth/index.js +9 -2
- package/lib/plugins/webui-auth/index.js.map +1 -1
- package/lib/plugins/webui-auth/service.d.ts.map +1 -1
- package/lib/plugins/webui-auth/service.js +27 -18
- package/lib/plugins/webui-auth/service.js.map +1 -1
- package/package.json +1 -1
package/client/api.ts
CHANGED
|
@@ -174,6 +174,43 @@ export const settingsApi = {
|
|
|
174
174
|
call<{ added: number, updated: number, removed: number }>('media-luna/presets/sync')
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
// 设置向导 API
|
|
178
|
+
export const setupApi = {
|
|
179
|
+
/** 获取设置状态 */
|
|
180
|
+
status: () =>
|
|
181
|
+
call<{
|
|
182
|
+
needsSetup: boolean
|
|
183
|
+
storageConfigured: boolean
|
|
184
|
+
userBound: boolean
|
|
185
|
+
storageBackend: string
|
|
186
|
+
boundUid: number | null
|
|
187
|
+
}>('media-luna/setup/status'),
|
|
188
|
+
/** 获取存储配置字段定义 */
|
|
189
|
+
getStorageFields: () =>
|
|
190
|
+
call<ConfigField[]>('media-luna/setup/storage/fields'),
|
|
191
|
+
/** 获取当前存储配置 */
|
|
192
|
+
getStorageConfig: () =>
|
|
193
|
+
call<Record<string, any>>('media-luna/setup/storage/get'),
|
|
194
|
+
/** 更新存储配置 */
|
|
195
|
+
updateStorageConfig: (config: Record<string, any>) =>
|
|
196
|
+
call<void>('media-luna/setup/storage/update', config),
|
|
197
|
+
/** 生成验证码 */
|
|
198
|
+
generateVerifyCode: (uid: number) =>
|
|
199
|
+
call<{ code: string, expiresIn: number, uid: number }>('media-luna/setup/verify-code/generate', { uid }),
|
|
200
|
+
/** 验证验证码 */
|
|
201
|
+
verifyCode: (code: string, uid: number) =>
|
|
202
|
+
call<{ uid: number }>('media-luna/setup/verify-code/verify', { code, uid }),
|
|
203
|
+
/** 直接绑定 UID */
|
|
204
|
+
bindUid: (uid: number) =>
|
|
205
|
+
call<{ uid: number }>('media-luna/setup/bind-uid', { uid }),
|
|
206
|
+
/** 获取可用用户列表 */
|
|
207
|
+
getUsers: () =>
|
|
208
|
+
call<Array<{ id: number, name: string, authority: number }>>('media-luna/setup/users'),
|
|
209
|
+
/** 完成设置 */
|
|
210
|
+
complete: () =>
|
|
211
|
+
call<void>('media-luna/setup/complete')
|
|
212
|
+
}
|
|
213
|
+
|
|
177
214
|
// 插件 API
|
|
178
215
|
export const pluginApi = {
|
|
179
216
|
/** 获取所有已加载插件 */
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="setup-wizard">
|
|
3
|
+
<div class="wizard-container">
|
|
4
|
+
<!-- 头部 -->
|
|
5
|
+
<div class="wizard-header">
|
|
6
|
+
<h1>欢迎使用 Media Luna</h1>
|
|
7
|
+
<p>在开始使用前,请完成以下基础配置</p>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<!-- 步骤指示器 -->
|
|
11
|
+
<div class="steps-indicator">
|
|
12
|
+
<div
|
|
13
|
+
v-for="(step, index) in steps"
|
|
14
|
+
:key="step.id"
|
|
15
|
+
class="step-item"
|
|
16
|
+
:class="{
|
|
17
|
+
active: currentStep === index,
|
|
18
|
+
completed: currentStep > index
|
|
19
|
+
}"
|
|
20
|
+
>
|
|
21
|
+
<div class="step-number">
|
|
22
|
+
<k-icon v-if="currentStep > index" name="check" />
|
|
23
|
+
<span v-else>{{ index + 1 }}</span>
|
|
24
|
+
</div>
|
|
25
|
+
<span class="step-label">{{ step.label }}</span>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<!-- 步骤内容 -->
|
|
30
|
+
<div class="wizard-content">
|
|
31
|
+
<transition name="fade" mode="out-in">
|
|
32
|
+
<!-- 存储配置步骤 -->
|
|
33
|
+
<SetupStorage
|
|
34
|
+
v-if="currentStep === 0"
|
|
35
|
+
key="storage"
|
|
36
|
+
v-model="storageConfig"
|
|
37
|
+
:saving="saving"
|
|
38
|
+
@next="handleStorageNext"
|
|
39
|
+
/>
|
|
40
|
+
|
|
41
|
+
<!-- 用户绑定步骤 -->
|
|
42
|
+
<SetupAuth
|
|
43
|
+
v-else-if="currentStep === 1"
|
|
44
|
+
key="auth"
|
|
45
|
+
:saving="saving"
|
|
46
|
+
@complete="handleComplete"
|
|
47
|
+
@skip="handleSkip"
|
|
48
|
+
/>
|
|
49
|
+
|
|
50
|
+
<!-- 完成步骤 -->
|
|
51
|
+
<div v-else-if="currentStep === 2" key="complete" class="step-complete">
|
|
52
|
+
<div class="complete-icon">
|
|
53
|
+
<k-icon name="check-circle" />
|
|
54
|
+
</div>
|
|
55
|
+
<h2>配置完成</h2>
|
|
56
|
+
<p>您已完成 Media Luna 的初始设置,现在可以开始使用了。</p>
|
|
57
|
+
<k-button type="primary" size="large" @click="finishSetup">
|
|
58
|
+
开始使用
|
|
59
|
+
</k-button>
|
|
60
|
+
</div>
|
|
61
|
+
</transition>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<script setup lang="ts">
|
|
68
|
+
import { ref } from 'vue'
|
|
69
|
+
import { message } from '@koishijs/client'
|
|
70
|
+
import { setupApi } from '../api'
|
|
71
|
+
import SetupStorage from './setup/SetupStorage.vue'
|
|
72
|
+
import SetupAuth from './setup/SetupAuth.vue'
|
|
73
|
+
|
|
74
|
+
const emit = defineEmits<{
|
|
75
|
+
(e: 'complete'): void
|
|
76
|
+
}>()
|
|
77
|
+
|
|
78
|
+
// 步骤定义
|
|
79
|
+
const steps = [
|
|
80
|
+
{ id: 'storage', label: '存储配置' },
|
|
81
|
+
{ id: 'auth', label: '用户绑定' },
|
|
82
|
+
{ id: 'complete', label: '完成' }
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
// 当前步骤
|
|
86
|
+
const currentStep = ref(0)
|
|
87
|
+
const saving = ref(false)
|
|
88
|
+
|
|
89
|
+
// 存储配置(由 SetupStorage 组件动态加载和管理)
|
|
90
|
+
const storageConfig = ref<Record<string, any>>({})
|
|
91
|
+
|
|
92
|
+
// 处理存储配置完成
|
|
93
|
+
const handleStorageNext = async () => {
|
|
94
|
+
saving.value = true
|
|
95
|
+
try {
|
|
96
|
+
await setupApi.updateStorageConfig(storageConfig.value)
|
|
97
|
+
message.success('存储配置已保存')
|
|
98
|
+
currentStep.value = 1
|
|
99
|
+
} catch (e) {
|
|
100
|
+
message.error('保存失败: ' + (e instanceof Error ? e.message : '未知错误'))
|
|
101
|
+
} finally {
|
|
102
|
+
saving.value = false
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 处理用户绑定完成
|
|
107
|
+
const handleComplete = () => {
|
|
108
|
+
currentStep.value = 2
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 跳过用户绑定
|
|
112
|
+
const handleSkip = () => {
|
|
113
|
+
currentStep.value = 2
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 完成设置
|
|
117
|
+
const finishSetup = async () => {
|
|
118
|
+
try {
|
|
119
|
+
await setupApi.complete()
|
|
120
|
+
emit('complete')
|
|
121
|
+
} catch (e) {
|
|
122
|
+
// 忽略错误,直接完成
|
|
123
|
+
emit('complete')
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
</script>
|
|
127
|
+
|
|
128
|
+
<style scoped>
|
|
129
|
+
.setup-wizard {
|
|
130
|
+
position: fixed;
|
|
131
|
+
top: 0;
|
|
132
|
+
left: 0;
|
|
133
|
+
right: 0;
|
|
134
|
+
bottom: 0;
|
|
135
|
+
background: var(--k-color-bg-1);
|
|
136
|
+
z-index: 9999;
|
|
137
|
+
display: flex;
|
|
138
|
+
align-items: center;
|
|
139
|
+
justify-content: center;
|
|
140
|
+
overflow: auto;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.wizard-container {
|
|
144
|
+
width: 100%;
|
|
145
|
+
max-width: 640px;
|
|
146
|
+
padding: 2rem;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.wizard-header {
|
|
150
|
+
text-align: center;
|
|
151
|
+
margin-bottom: 2rem;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.wizard-header h1 {
|
|
155
|
+
font-size: 1.75rem;
|
|
156
|
+
font-weight: 600;
|
|
157
|
+
color: var(--k-color-text);
|
|
158
|
+
margin: 0 0 0.5rem 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.wizard-header p {
|
|
162
|
+
color: var(--k-color-text-description);
|
|
163
|
+
margin: 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* 步骤指示器 */
|
|
167
|
+
.steps-indicator {
|
|
168
|
+
display: flex;
|
|
169
|
+
justify-content: center;
|
|
170
|
+
gap: 2rem;
|
|
171
|
+
margin-bottom: 2rem;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.step-item {
|
|
175
|
+
display: flex;
|
|
176
|
+
flex-direction: column;
|
|
177
|
+
align-items: center;
|
|
178
|
+
gap: 0.5rem;
|
|
179
|
+
position: relative;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.step-item:not(:last-child)::after {
|
|
183
|
+
content: '';
|
|
184
|
+
position: absolute;
|
|
185
|
+
left: calc(50% + 20px);
|
|
186
|
+
top: 16px;
|
|
187
|
+
width: calc(2rem + 20px);
|
|
188
|
+
height: 2px;
|
|
189
|
+
background: var(--k-color-border);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.step-item.completed:not(:last-child)::after {
|
|
193
|
+
background: var(--k-color-success);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.step-number {
|
|
197
|
+
width: 32px;
|
|
198
|
+
height: 32px;
|
|
199
|
+
border-radius: 50%;
|
|
200
|
+
display: flex;
|
|
201
|
+
align-items: center;
|
|
202
|
+
justify-content: center;
|
|
203
|
+
font-weight: 600;
|
|
204
|
+
font-size: 0.9rem;
|
|
205
|
+
background: var(--k-color-bg-2);
|
|
206
|
+
color: var(--k-color-text-description);
|
|
207
|
+
border: 2px solid var(--k-color-border);
|
|
208
|
+
transition: all 0.2s;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.step-item.active .step-number {
|
|
212
|
+
background: var(--k-color-active);
|
|
213
|
+
color: white;
|
|
214
|
+
border-color: var(--k-color-active);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.step-item.completed .step-number {
|
|
218
|
+
background: var(--k-color-success);
|
|
219
|
+
color: white;
|
|
220
|
+
border-color: var(--k-color-success);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.step-label {
|
|
224
|
+
font-size: 0.85rem;
|
|
225
|
+
color: var(--k-color-text-description);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.step-item.active .step-label {
|
|
229
|
+
color: var(--k-color-text);
|
|
230
|
+
font-weight: 500;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.step-item.completed .step-label {
|
|
234
|
+
color: var(--k-color-success);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/* 步骤内容 */
|
|
238
|
+
.wizard-content {
|
|
239
|
+
background: var(--k-card-bg);
|
|
240
|
+
border: 1px solid var(--k-color-border);
|
|
241
|
+
border-radius: 12px;
|
|
242
|
+
padding: 2rem;
|
|
243
|
+
min-height: 400px;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* 完成步骤 */
|
|
247
|
+
.step-complete {
|
|
248
|
+
display: flex;
|
|
249
|
+
flex-direction: column;
|
|
250
|
+
align-items: center;
|
|
251
|
+
justify-content: center;
|
|
252
|
+
text-align: center;
|
|
253
|
+
padding: 3rem 0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.complete-icon {
|
|
257
|
+
font-size: 4rem;
|
|
258
|
+
color: var(--k-color-success);
|
|
259
|
+
margin-bottom: 1.5rem;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.step-complete h2 {
|
|
263
|
+
font-size: 1.5rem;
|
|
264
|
+
font-weight: 600;
|
|
265
|
+
color: var(--k-color-text);
|
|
266
|
+
margin: 0 0 0.75rem 0;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.step-complete p {
|
|
270
|
+
color: var(--k-color-text-description);
|
|
271
|
+
margin: 0 0 2rem 0;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/* 过渡动画 */
|
|
275
|
+
.fade-enter-active,
|
|
276
|
+
.fade-leave-active {
|
|
277
|
+
transition: opacity 0.2s ease;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.fade-enter-from,
|
|
281
|
+
.fade-leave-to {
|
|
282
|
+
opacity: 0;
|
|
283
|
+
}
|
|
284
|
+
</style>
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="setup-auth">
|
|
3
|
+
<h3>用户绑定</h3>
|
|
4
|
+
<p class="step-desc">
|
|
5
|
+
将 WebUI 与 Koishi 用户绑定。你可以直接配置用户 ID,也可以通过验证码进行验证绑定。
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<div class="config-panel">
|
|
9
|
+
<!-- UID 输入 -->
|
|
10
|
+
<div class="form-row">
|
|
11
|
+
<div class="form-label">用户 ID (UID)</div>
|
|
12
|
+
<div class="field-container">
|
|
13
|
+
<el-input-number
|
|
14
|
+
v-model="uid"
|
|
15
|
+
:min="1"
|
|
16
|
+
:controls="false"
|
|
17
|
+
placeholder="Koishi 用户 ID"
|
|
18
|
+
class="uid-input full-width"
|
|
19
|
+
@input="handleUidChange"
|
|
20
|
+
/>
|
|
21
|
+
<div class="field-desc">
|
|
22
|
+
输入你的 Koishi <code>uid</code>,不是原神的。
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<!-- 验证码显示区域 -->
|
|
28
|
+
<div class="verify-section">
|
|
29
|
+
<div class="verify-card">
|
|
30
|
+
<div class="code-display">
|
|
31
|
+
<div class="code-label">验证码</div>
|
|
32
|
+
<div class="code-value">{{ verifyCode }}</div>
|
|
33
|
+
<div class="code-meta">有效期 {{ expiresIn }} 秒</div>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="verify-guide">
|
|
36
|
+
<p>请在聊天平台向机器人发送以下指令完成绑定:</p>
|
|
37
|
+
<div class="command-box">
|
|
38
|
+
<code>bindui</code>
|
|
39
|
+
</div>
|
|
40
|
+
<p class="small-hint">发送指令后,请根据提示输入左侧的验证码。</p>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="step-actions">
|
|
47
|
+
<k-button @click="$emit('skip')">跳过</k-button>
|
|
48
|
+
<k-button type="primary" :loading="saving" @click="handleSave">
|
|
49
|
+
保存 / 完成
|
|
50
|
+
</k-button>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<script setup lang="ts">
|
|
56
|
+
import { ref, onMounted, onUnmounted } from 'vue'
|
|
57
|
+
import { message, receive } from '@koishijs/client'
|
|
58
|
+
import { setupApi } from '../../api'
|
|
59
|
+
|
|
60
|
+
const emit = defineEmits<{
|
|
61
|
+
(e: 'complete'): void
|
|
62
|
+
(e: 'skip'): void
|
|
63
|
+
}>()
|
|
64
|
+
|
|
65
|
+
defineProps<{
|
|
66
|
+
saving: boolean
|
|
67
|
+
}>()
|
|
68
|
+
|
|
69
|
+
const uid = ref<number | null>(null)
|
|
70
|
+
const generating = ref(false)
|
|
71
|
+
const verifyCode = ref('')
|
|
72
|
+
const expiresIn = ref(0)
|
|
73
|
+
const initialBoundUid = ref<number | null>(null)
|
|
74
|
+
|
|
75
|
+
let stopReceive: (() => void) | null = null
|
|
76
|
+
let pollTimer: number | null = null
|
|
77
|
+
|
|
78
|
+
// 加载当前状态
|
|
79
|
+
const loadStatus = async () => {
|
|
80
|
+
try {
|
|
81
|
+
const status = await setupApi.status()
|
|
82
|
+
if (status.boundUid) {
|
|
83
|
+
uid.value = status.boundUid
|
|
84
|
+
initialBoundUid.value = status.boundUid
|
|
85
|
+
}
|
|
86
|
+
} catch (e) {
|
|
87
|
+
console.error('Failed to load status:', e)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 生成验证码
|
|
92
|
+
const generateCode = async () => {
|
|
93
|
+
if (generateTimer) clearTimeout(generateTimer)
|
|
94
|
+
if (!uid.value) return
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
generating.value = true
|
|
98
|
+
const result = await setupApi.generateVerifyCode(uid.value)
|
|
99
|
+
verifyCode.value = result.code
|
|
100
|
+
expiresIn.value = result.expiresIn
|
|
101
|
+
message.success('验证码已生成,请在聊天平台完成验证')
|
|
102
|
+
} catch (e) {
|
|
103
|
+
message.error('生成验证码失败: ' + (e instanceof Error ? e.message : '未知错误'))
|
|
104
|
+
} finally {
|
|
105
|
+
generating.value = false
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 监听 UID 变化自动生成验证码
|
|
110
|
+
let generateTimer: number | null = null
|
|
111
|
+
const handleUidChange = () => {
|
|
112
|
+
if (generateTimer) clearTimeout(generateTimer)
|
|
113
|
+
verifyCode.value = ''
|
|
114
|
+
|
|
115
|
+
if (uid.value) {
|
|
116
|
+
generateTimer = window.setTimeout(generateCode, 1000)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 保存/直接绑定
|
|
121
|
+
const handleSave = async () => {
|
|
122
|
+
if (!uid.value) {
|
|
123
|
+
message.warning('请输入用户 ID')
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
// 尝试直接更新配置(如果通过验证码流程绑定了,这里再次设置也是安全的)
|
|
129
|
+
await setupApi.bindUid(uid.value)
|
|
130
|
+
message.success('配置已保存')
|
|
131
|
+
emit('complete')
|
|
132
|
+
} catch (e) {
|
|
133
|
+
message.error('保存失败: ' + (e instanceof Error ? e.message : '未知错误'))
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
onMounted(() => {
|
|
138
|
+
loadStatus()
|
|
139
|
+
|
|
140
|
+
// 监听来自 QQ 的绑定请求
|
|
141
|
+
stopReceive = (receive as any)('media-luna/webui-auth/bind-request', (data: { uid: number, code: string, expiresIn: number }) => {
|
|
142
|
+
if (data.uid) {
|
|
143
|
+
uid.value = data.uid
|
|
144
|
+
|
|
145
|
+
if (data.code) {
|
|
146
|
+
verifyCode.value = data.code
|
|
147
|
+
expiresIn.value = data.expiresIn
|
|
148
|
+
message.info('收到绑定请求,请在聊天平台完成验证')
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// 轮询绑定状态
|
|
154
|
+
pollTimer = window.setInterval(async () => {
|
|
155
|
+
try {
|
|
156
|
+
const status = await setupApi.status()
|
|
157
|
+
if (status.boundUid) {
|
|
158
|
+
// 只有当绑定的 UID 与当前(或请求的)UID 一致时才自动完成
|
|
159
|
+
if (uid.value && status.boundUid !== uid.value) return
|
|
160
|
+
|
|
161
|
+
// 如果绑定状态发生了变化(从无到有,或变更了用户),则自动完成
|
|
162
|
+
// 如果是重新绑定同一个用户,由于状态未变,不自动完成(避免过早提示)
|
|
163
|
+
if (status.boundUid !== initialBoundUid.value) {
|
|
164
|
+
uid.value = status.boundUid
|
|
165
|
+
message.success('检测到绑定成功!')
|
|
166
|
+
emit('complete')
|
|
167
|
+
if (pollTimer) clearInterval(pollTimer)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} catch {}
|
|
171
|
+
}, 3000)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
onUnmounted(() => {
|
|
175
|
+
if (stopReceive) stopReceive()
|
|
176
|
+
if (pollTimer) clearInterval(pollTimer)
|
|
177
|
+
})
|
|
178
|
+
</script>
|
|
179
|
+
|
|
180
|
+
<style scoped>
|
|
181
|
+
.setup-auth h3 {
|
|
182
|
+
font-size: 1.25rem;
|
|
183
|
+
font-weight: 600;
|
|
184
|
+
color: var(--k-color-text);
|
|
185
|
+
margin: 0 0 0.5rem 0;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.step-desc {
|
|
189
|
+
color: var(--k-color-text-description);
|
|
190
|
+
margin: 0 0 1.5rem 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.config-panel {
|
|
194
|
+
background: var(--k-card-bg);
|
|
195
|
+
padding: 1.5rem;
|
|
196
|
+
border-radius: 12px;
|
|
197
|
+
border: 1px solid var(--k-color-border);
|
|
198
|
+
margin-bottom: 1.5rem;
|
|
199
|
+
display: flex;
|
|
200
|
+
flex-direction: column;
|
|
201
|
+
gap: 1.5rem;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* 表单样式 imitation of ConfigRenderer */
|
|
205
|
+
.form-row {
|
|
206
|
+
display: flex;
|
|
207
|
+
align-items: flex-start;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.form-label {
|
|
211
|
+
width: 120px;
|
|
212
|
+
flex-shrink: 0;
|
|
213
|
+
color: var(--k-color-text-description);
|
|
214
|
+
padding-top: 6px;
|
|
215
|
+
font-size: 0.9rem;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.field-container {
|
|
219
|
+
flex: 1;
|
|
220
|
+
display: flex;
|
|
221
|
+
flex-direction: column;
|
|
222
|
+
gap: 0.5rem;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.uid-input {
|
|
226
|
+
width: 100%;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.field-desc {
|
|
230
|
+
font-size: 0.8rem;
|
|
231
|
+
color: var(--k-color-text-description);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.field-desc code {
|
|
235
|
+
background: var(--k-color-bg-2);
|
|
236
|
+
padding: 0.1em 0.4em;
|
|
237
|
+
border-radius: 4px;
|
|
238
|
+
font-family: monospace;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/* 验证码区域 */
|
|
242
|
+
.verify-section {
|
|
243
|
+
border-top: 1px solid var(--k-color-border);
|
|
244
|
+
padding-top: 1.5rem;
|
|
245
|
+
margin-top: 0.5rem;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.verify-card {
|
|
249
|
+
background: var(--k-color-bg-2);
|
|
250
|
+
border-radius: 8px;
|
|
251
|
+
padding: 1.5rem;
|
|
252
|
+
display: flex;
|
|
253
|
+
gap: 2rem;
|
|
254
|
+
align-items: center;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.code-display {
|
|
258
|
+
display: flex;
|
|
259
|
+
flex-direction: column;
|
|
260
|
+
align-items: center;
|
|
261
|
+
gap: 0.5rem;
|
|
262
|
+
padding-right: 2rem;
|
|
263
|
+
border-right: 1px dashed var(--k-color-border);
|
|
264
|
+
min-width: 150px;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.code-label {
|
|
268
|
+
font-size: 0.85rem;
|
|
269
|
+
color: var(--k-color-text-description);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.code-value {
|
|
273
|
+
font-size: 2rem;
|
|
274
|
+
font-family: monospace;
|
|
275
|
+
font-weight: 700;
|
|
276
|
+
color: var(--k-color-active);
|
|
277
|
+
letter-spacing: 0.1em;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.code-meta {
|
|
281
|
+
font-size: 0.8rem;
|
|
282
|
+
color: var(--k-color-text-description);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.verify-guide {
|
|
286
|
+
flex: 1;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.verify-guide p {
|
|
290
|
+
margin: 0 0 0.75rem 0;
|
|
291
|
+
font-size: 0.9rem;
|
|
292
|
+
color: var(--k-color-text);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.command-box {
|
|
296
|
+
background: var(--k-card-bg);
|
|
297
|
+
padding: 0.75rem 1rem;
|
|
298
|
+
border-radius: 6px;
|
|
299
|
+
border: 1px solid var(--k-color-border);
|
|
300
|
+
display: inline-block;
|
|
301
|
+
margin-bottom: 0.75rem;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.command-box code {
|
|
305
|
+
font-family: monospace;
|
|
306
|
+
color: var(--k-color-active);
|
|
307
|
+
font-weight: 600;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.small-hint {
|
|
311
|
+
font-size: 0.8rem !important;
|
|
312
|
+
color: var(--k-color-text-description) !important;
|
|
313
|
+
margin: 0 !important;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.step-actions {
|
|
317
|
+
display: flex;
|
|
318
|
+
justify-content: flex-end;
|
|
319
|
+
gap: 0.75rem;
|
|
320
|
+
padding-top: 1.5rem;
|
|
321
|
+
border-top: 1px solid var(--k-color-border);
|
|
322
|
+
}
|
|
323
|
+
</style>
|