mirror of
https://github.com/ZeroCatDev/Classworks.git
synced 2025-12-07 21:13:11 +00:00
Compare commits
8 Commits
a7ff0e5714
...
6eb9bbd79c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6eb9bbd79c | ||
|
|
1999b09e59 | ||
|
|
7f166ffddc | ||
|
|
9aae9601f6 | ||
|
|
c36e4defc2 | ||
|
|
97fe02143f | ||
|
|
5c3fa9a0e5 | ||
|
|
5c27a30fde |
159
UNIFIED_LINK_GENERATOR.md
Normal file
159
UNIFIED_LINK_GENERATOR.md
Normal file
@ -0,0 +1,159 @@
|
||||
# 统一链接生成器功能测试
|
||||
|
||||
## 功能概述
|
||||
|
||||
新的统一链接生成器将预配置认证信息和设置分享功能合并到一个链接中,用户可以:
|
||||
|
||||
1. 输入命名空间和认证码(预配置认证)
|
||||
2. 选择需要分享的设置项
|
||||
3. 生成包含两种信息的统一链接
|
||||
|
||||
## 核心特性
|
||||
|
||||
### ✅ **统一链接生成**
|
||||
- 同时包含预配置认证参数和设置配置
|
||||
- 一个链接完成设备认证和设置应用
|
||||
- 自动编码和参数组合
|
||||
|
||||
### ✅ **智能界面设计**
|
||||
- 分层式界面:预配置认证 + 设置分享 + 链接生成
|
||||
- 实时预览和状态显示
|
||||
- 安全提醒和敏感信息保护
|
||||
|
||||
### ✅ **安全优化**
|
||||
- 数据源设置和已变更设置默认排除 `server.kvToken`
|
||||
- 敏感设置项标记和值遮盖
|
||||
- 多重安全提醒
|
||||
|
||||
## 链接格式
|
||||
|
||||
### 基础预配置链接
|
||||
```
|
||||
https://domain.com/?namespace=classroom-001&authCode=pass123&autoExecute=true
|
||||
```
|
||||
|
||||
### 包含设置的统一链接
|
||||
```
|
||||
https://domain.com/?namespace=classroom-001&authCode=pass123&autoExecute=true&config=eyJ...
|
||||
```
|
||||
|
||||
其中:
|
||||
- `namespace`: 设备命名空间
|
||||
- `authCode`: 认证码(可选)
|
||||
- `autoExecute`: 是否自动执行认证
|
||||
- `config`: Base64编码的设置JSON对象
|
||||
|
||||
## 使用流程
|
||||
|
||||
1. **输入预配置信息**
|
||||
- 命名空间(必填)
|
||||
- 认证码(可选)
|
||||
- 是否自动执行认证
|
||||
|
||||
2. **选择设置项**
|
||||
- 快速选择:数据源设置、已变更设置、全选
|
||||
- 手动选择:通过详细列表选择特定设置
|
||||
- 安全保护:敏感设置默认不选中
|
||||
|
||||
3. **生成统一链接**
|
||||
- 点击"生成统一链接"按钮
|
||||
- 链接实时生成和预览
|
||||
- 一键复制和测试
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 参数组合逻辑
|
||||
```javascript
|
||||
// 1. 添加预配置参数
|
||||
params.append("namespace", namespace);
|
||||
params.append("authCode", authCode);
|
||||
params.append("autoExecute", "true");
|
||||
|
||||
// 2. 添加设置配置
|
||||
if (selectedSettings.length > 0) {
|
||||
const configObj = {};
|
||||
selectedSettings.forEach(key => {
|
||||
configObj[key] = allSettings[key];
|
||||
});
|
||||
|
||||
const base64Config = btoa(JSON.stringify(configObj));
|
||||
params.append("config", base64Config);
|
||||
}
|
||||
```
|
||||
|
||||
### 自动更新机制
|
||||
- 监听预配置表单变化
|
||||
- 监听设置选择变化
|
||||
- 实时生成统一链接
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 1. **设备批量部署 + 环境配置**
|
||||
```
|
||||
https://classworks.example.com/?namespace=classroom-01&authCode=device01&autoExecute=true&config=eyJzZXJ2ZXIuZG9tYWluIjoiaHR0cHM6Ly9hcGkuZXhhbXBsZS5jb20ifQ==
|
||||
```
|
||||
- 自动认证为指定设备
|
||||
- 自动配置服务器地址等设置
|
||||
|
||||
### 2. **演示环境快速部署**
|
||||
```
|
||||
https://classworks.example.com/?namespace=demo&autoExecute=true&config=eyJkaXNwbGF5LnRoZW1lIjoiZGFyayIsImVkaXQubW9kZSI6InJlYWRvbmx5In0=
|
||||
```
|
||||
- 自动认证为演示账号
|
||||
- 自动应用演示环境设置
|
||||
|
||||
### 3. **培训环境标准化**
|
||||
```
|
||||
https://classworks.example.com/?namespace=training&authCode=train123&config=eyJkaXNwbGF5LnNob3dIZWxwIjp0cnVlLCJlZGl0LmVuYWJsZUd1aWRlIjp0cnVlfQ==
|
||||
```
|
||||
- 预配置培训账号
|
||||
- 启用帮助和引导功能
|
||||
|
||||
## 安全考虑
|
||||
|
||||
### ✅ **默认安全**
|
||||
- Token等敏感信息默认不包含
|
||||
- 快速选择按钮智能排除敏感设置
|
||||
- 明确的安全警告和提醒
|
||||
|
||||
### ✅ **用户控制**
|
||||
- 用户仍可手动选择包含敏感设置
|
||||
- 敏感设置有明确标记
|
||||
- 提供充分的风险提示
|
||||
|
||||
### ✅ **传输安全**
|
||||
- 建议HTTPS传输
|
||||
- URL参数会被自动清理
|
||||
- 设置信息经过Base64编码
|
||||
|
||||
## 兼容性
|
||||
|
||||
- ✅ 向后兼容现有的预配置功能
|
||||
- ✅ 向后兼容现有的设置分享功能
|
||||
- ✅ 新增统一链接格式
|
||||
- ✅ 保持所有现有API接口
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. **基础功能测试**
|
||||
- 仅预配置信息的链接生成
|
||||
- 预配置 + 设置的统一链接生成
|
||||
- 链接复制和测试功能
|
||||
|
||||
2. **安全功能测试**
|
||||
- 验证敏感设置默认不选中
|
||||
- 验证敏感设置标记显示
|
||||
- 验证安全提醒展示
|
||||
|
||||
3. **兼容性测试**
|
||||
- 测试生成的链接是否正常工作
|
||||
- 测试预配置认证是否正常
|
||||
- 测试设置应用是否正常
|
||||
|
||||
## 优势总结
|
||||
|
||||
1. **用户体验**: 一个链接完成所有配置,无需多步操作
|
||||
2. **部署效率**: 批量设备部署更加便捷
|
||||
3. **管理简化**: 减少链接管理复杂度
|
||||
4. **安全平衡**: 在便捷性和安全性之间找到平衡
|
||||
5. **功能完整**: 涵盖认证和配置的完整解决方案
|
||||
BIN
src/assets/cslogo.png
Normal file
BIN
src/assets/cslogo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
@ -153,7 +153,9 @@
|
||||
max-width="500"
|
||||
>
|
||||
<DeviceAuthDialog
|
||||
ref="deviceAuthDialog"
|
||||
:show-cancel="true"
|
||||
:preconfig="deviceAuthPreconfig"
|
||||
@success="handleAuthSuccess"
|
||||
@cancel="showDeviceAuthDialog = false"
|
||||
/>
|
||||
@ -184,13 +186,25 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { getSetting, setSetting } from '@/utils/settings'
|
||||
import DeviceAuthDialog from './auth/DeviceAuthDialog.vue'
|
||||
import TokenInputDialog from './auth/TokenInputDialog.vue'
|
||||
import AlternativeCodeDialog from './auth/AlternativeCodeDialog.vue'
|
||||
import FirstTimeGuide from './auth/FirstTimeGuide.vue'
|
||||
|
||||
const props = defineProps({
|
||||
preconfig: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
namespace: null,
|
||||
authCode: null,
|
||||
autoOpen: false,
|
||||
autoExecute: false
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['done'])
|
||||
|
||||
// 控制显示:仅首页且无 kvToken(且 provider 不是 kv-local)显示
|
||||
@ -202,10 +216,25 @@ const showDeviceAuthDialog = ref(false)
|
||||
const showTokenDialog = ref(false)
|
||||
const showAlternativeCodeDialog = ref(false)
|
||||
|
||||
// 设备认证对话框引用
|
||||
const deviceAuthDialog = ref(null)
|
||||
|
||||
const provider = computed(() => getSetting('server.provider'))
|
||||
const isKvProvider = computed(() => provider.value === 'kv-server' || provider.value === 'classworkscloud')
|
||||
const kvToken = computed(() => getSetting('server.kvToken'))
|
||||
|
||||
// 设备认证预配置数据
|
||||
const deviceAuthPreconfig = computed(() => {
|
||||
if (props.preconfig?.namespace) {
|
||||
return {
|
||||
namespace: props.preconfig.namespace,
|
||||
password: props.preconfig.authCode || '',
|
||||
autoExecute: props.preconfig.autoExecute || false
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
const evaluateVisibility = () => {
|
||||
const path = window.location.pathname
|
||||
const onHome = path === '/' || path === '/index' || path === '/index.html'
|
||||
@ -213,6 +242,21 @@ const evaluateVisibility = () => {
|
||||
visible.value = onHome && need
|
||||
}
|
||||
|
||||
// 监听预配数据变化,自动打开设备认证对话框
|
||||
watch(
|
||||
() => props.preconfig,
|
||||
(newPreconfig) => {
|
||||
if (newPreconfig?.autoOpen && newPreconfig?.namespace && visible.value) {
|
||||
console.log('检测到预配数据,自动打开设备认证对话框')
|
||||
// 延迟一下确保组件已完全挂载
|
||||
setTimeout(() => {
|
||||
showDeviceAuthDialog.value = true
|
||||
}, 500)
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
evaluateVisibility()
|
||||
})
|
||||
@ -231,8 +275,16 @@ const handleAutoAuthorize = () => {
|
||||
window.location.href = url
|
||||
}
|
||||
|
||||
const handleAuthSuccess = () => {
|
||||
const handleAuthSuccess = (tokenData) => {
|
||||
showDeviceAuthDialog.value = false
|
||||
console.log('认证成功:', tokenData)
|
||||
|
||||
// 如果是通过预配数据成功的,显示成功消息
|
||||
if (props.preconfig?.namespace) {
|
||||
// 可以在这里添加成功提示
|
||||
console.log(`预配数据认证成功: ${props.preconfig.namespace}`)
|
||||
}
|
||||
|
||||
evaluateVisibility()
|
||||
emit('done')
|
||||
}
|
||||
|
||||
@ -1,142 +1,356 @@
|
||||
<template>
|
||||
<v-card border class="settings-link-generator mb-4">
|
||||
<v-card-title class="text-h6">
|
||||
<v-icon start icon="mdi-link-variant" class="mr-2" />
|
||||
设置分享
|
||||
</v-card-title>
|
||||
<div>
|
||||
<!-- 统一链接生成器卡片 -->
|
||||
<v-card border class="unified-link-generator">
|
||||
<v-card-title class="text-h6">
|
||||
<v-icon start icon="mdi-link-variant" class="mr-2" />
|
||||
统一链接生成器
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<!-- 快速选择按钮 -->
|
||||
<div class="d-flex mb-3 gap-2 flex-wrap">
|
||||
<v-btn
|
||||
size="small"
|
||||
variant="tonal"
|
||||
color="primary"
|
||||
prepend-icon="mdi-select-all"
|
||||
@click="selectAll"
|
||||
>
|
||||
全选
|
||||
</v-btn>
|
||||
<v-btn
|
||||
size="small"
|
||||
variant="tonal"
|
||||
color="primary"
|
||||
prepend-icon="mdi-server-network"
|
||||
@click="selectDataSourceSettings"
|
||||
>
|
||||
数据源设置
|
||||
</v-btn>
|
||||
<v-btn
|
||||
size="small"
|
||||
variant="tonal"
|
||||
color="primary"
|
||||
prepend-icon="mdi-compare"
|
||||
@click="selectChangedSettings"
|
||||
>
|
||||
已变更设置
|
||||
</v-btn>
|
||||
<v-card-text>
|
||||
<div class="text-body-2 text-medium-emphasis mb-4">
|
||||
生成包含预配置认证信息和设置的统一链接。可以同时预配置设备认证和应用设置。
|
||||
</div>
|
||||
|
||||
<v-btn
|
||||
size="small"
|
||||
variant="tonal"
|
||||
color="error"
|
||||
prepend-icon="mdi-select-remove"
|
||||
@click="resetSelection"
|
||||
>
|
||||
取消选择
|
||||
</v-btn>
|
||||
</div>
|
||||
<!-- 预配置认证信息部分 -->
|
||||
<v-card variant="tonal" class="mb-4">
|
||||
<v-card-title class="text-subtitle-1">
|
||||
<v-icon start>mdi-account-key</v-icon>
|
||||
预配置认证信息
|
||||
</v-card-title>
|
||||
|
||||
<!-- 选择摘要和链接 -->
|
||||
<div class="d-flex align-center mt-3 mb-3 flex-wrap gap-2">
|
||||
<v-chip color="primary" class="mr-2">
|
||||
已选 {{ selectedItems.length }} 项设置
|
||||
</v-chip>
|
||||
|
||||
<template v-if="selectedItems.length > 0">
|
||||
<v-chip
|
||||
v-for="item in selectedItems"
|
||||
:key="item"
|
||||
size="small"
|
||||
class="mr-1"
|
||||
variant="text"
|
||||
>
|
||||
{{ getSettingDescription(item) }}
|
||||
</v-chip>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<v-text-field
|
||||
v-model="generatedLink"
|
||||
label="生成的链接"
|
||||
readonly
|
||||
variant="outlined"
|
||||
class="mb-2"
|
||||
:append-inner-icon="linkCopied ? 'mdi-check' : 'mdi-content-copy'"
|
||||
@click:append-inner="copyLink"
|
||||
/>
|
||||
|
||||
<!-- 设置列表折叠面板 -->
|
||||
<v-expansion-panels variant="accordion">
|
||||
<v-expansion-panel>
|
||||
<v-expansion-panel-title> 显示设置列表详情 </v-expansion-panel-title>
|
||||
|
||||
<v-expansion-panel-text>
|
||||
<v-data-table
|
||||
:items-per-page="settingItems.length"
|
||||
:headers="headers"
|
||||
:items="filteredItems"
|
||||
item-value="key"
|
||||
v-model="selectedItems"
|
||||
show-select
|
||||
density="compact"
|
||||
class="rounded setting-table"
|
||||
@update:selected="handleSelectionChange"
|
||||
:sort-by="[{ key: 'isChanged', order: 'desc' }]"
|
||||
>
|
||||
<template v-slot:top>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
label="搜索设置"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
single-line
|
||||
hide-details
|
||||
class="mb-4"
|
||||
></v-text-field>
|
||||
</template>
|
||||
v-model="preconfigForm.namespace"
|
||||
label="命名空间"
|
||||
variant="outlined"
|
||||
prepend-inner-icon="mdi-identifier"
|
||||
placeholder="例如: classroom-001"
|
||||
hint="设备的命名空间标识符"
|
||||
persistent-hint
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="preconfigForm.authCode"
|
||||
label="认证码"
|
||||
variant="outlined"
|
||||
prepend-inner-icon="mdi-lock-outline"
|
||||
placeholder="设备认证码(可选)"
|
||||
hint="留空则需要用户手动输入"
|
||||
persistent-hint
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<template #[`item.description`]="{ item }">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon size="small" :icon="item.icon" class="mr-2"></v-icon>
|
||||
{{ item.description }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #[`item.value`]="{ item }">
|
||||
<span v-if="typeof item.value === 'boolean'">
|
||||
{{ item.value ? "是" : "否" }}
|
||||
</span>
|
||||
<span v-else>{{ item.value }}</span>
|
||||
</template>
|
||||
|
||||
<template #[`item.key`]="{ item }">
|
||||
<span class="text-caption text-grey">{{ item.key }}</span>
|
||||
</template>
|
||||
|
||||
<template #[`item.isChanged`]="{ item }">
|
||||
<v-chip
|
||||
size="x-small"
|
||||
:color="item.isChanged ? 'warning' : 'success'"
|
||||
:text="item.isChanged ? '已修改' : '默认'"
|
||||
<v-row class="mt-2">
|
||||
<v-col cols="12">
|
||||
<v-checkbox
|
||||
v-model="preconfigForm.autoExecute"
|
||||
label="自动执行认证"
|
||||
hint="启用后会自动尝试认证,即使没有认证码也会尝试"
|
||||
persistent-hint
|
||||
density="compact"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- 预配置信息预览 -->
|
||||
<v-alert
|
||||
v-if="preconfigForm.namespace"
|
||||
type="info"
|
||||
variant="tonal"
|
||||
class="mt-3"
|
||||
>
|
||||
<div class="text-subtitle-2 mb-2">预配置信息:</div>
|
||||
<v-chip size="small" class="mr-2 mb-1">
|
||||
<v-icon start size="small">mdi-identifier</v-icon>
|
||||
命名空间: {{ preconfigForm.namespace }}
|
||||
</v-chip>
|
||||
<v-chip
|
||||
v-if="preconfigForm.authCode"
|
||||
size="small"
|
||||
class="mr-2 mb-1"
|
||||
color="warning"
|
||||
>
|
||||
<v-icon start size="small">mdi-lock</v-icon>
|
||||
认证码: {{ preconfigForm.authCode.length > 8 ? preconfigForm.authCode.substring(0, 8) + "..." : preconfigForm.authCode }}
|
||||
</v-chip>
|
||||
<v-chip v-else size="small" class="mr-2 mb-1" color="grey">
|
||||
<v-icon start size="small">mdi-lock-open</v-icon>
|
||||
无认证码
|
||||
</v-chip>
|
||||
<v-chip
|
||||
size="small"
|
||||
class="mr-2 mb-1"
|
||||
:color="preconfigForm.autoExecute ? 'success' : 'orange'"
|
||||
>
|
||||
<v-icon start size="small">{{
|
||||
preconfigForm.autoExecute ? "mdi-play-circle" : "mdi-hand-back-left"
|
||||
}}</v-icon>
|
||||
{{ preconfigForm.autoExecute ? "自动认证" : "手动认证" }}
|
||||
</v-chip>
|
||||
</v-alert>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 设置分享部分 -->
|
||||
<v-card variant="tonal" class="mb-4">
|
||||
<v-card-title class="text-subtitle-1">
|
||||
<v-icon start>mdi-cog-transfer</v-icon>
|
||||
设置分享(可选)
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<div class="text-body-2 text-medium-emphasis mb-3">
|
||||
选择需要包含在链接中的设置项。如果不选择任何设置,将只生成预配置认证链接。
|
||||
</div>
|
||||
|
||||
<!-- 设置快速选择按钮 -->
|
||||
<div class="d-flex mb-3 gap-2 flex-wrap">
|
||||
<v-btn
|
||||
size="small"
|
||||
variant="tonal"
|
||||
color="primary"
|
||||
prepend-icon="mdi-server-network"
|
||||
@click="selectDataSourceSettings"
|
||||
>
|
||||
数据源设置
|
||||
</v-btn>
|
||||
<v-btn
|
||||
size="small"
|
||||
variant="tonal"
|
||||
color="primary"
|
||||
prepend-icon="mdi-compare"
|
||||
@click="selectChangedSettings"
|
||||
>
|
||||
已变更设置
|
||||
</v-btn>
|
||||
<v-btn
|
||||
size="small"
|
||||
variant="tonal"
|
||||
color="success"
|
||||
prepend-icon="mdi-select-all"
|
||||
@click="selectAll"
|
||||
>
|
||||
全选
|
||||
</v-btn>
|
||||
<v-btn
|
||||
size="small"
|
||||
variant="tonal"
|
||||
color="error"
|
||||
prepend-icon="mdi-select-remove"
|
||||
@click="resetSelection"
|
||||
>
|
||||
清除选择
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<!-- 选择摘要 -->
|
||||
<div class="d-flex align-center mb-3 flex-wrap gap-2">
|
||||
<v-chip color="primary" class="mr-2">
|
||||
已选 {{ selectedItems.length }} 项设置
|
||||
</v-chip>
|
||||
|
||||
<template v-if="selectedItems.length > 0">
|
||||
<v-chip
|
||||
v-for="item in selectedItems.slice(0, 3)"
|
||||
:key="item"
|
||||
size="small"
|
||||
class="mr-1"
|
||||
variant="text"
|
||||
>
|
||||
{{ getSettingDescription(item) }}
|
||||
</v-chip>
|
||||
<v-chip
|
||||
v-if="selectedItems.length > 3"
|
||||
size="small"
|
||||
variant="text"
|
||||
color="grey"
|
||||
>
|
||||
+{{ selectedItems.length - 3 }} 更多
|
||||
</v-chip>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
<!-- 设置列表折叠面板 -->
|
||||
<v-expansion-panels variant="accordion">
|
||||
<v-expansion-panel>
|
||||
<v-expansion-panel-title>
|
||||
<template #default="{ expanded }">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon class="mr-2">{{ expanded ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
|
||||
显示设置列表详情
|
||||
</div>
|
||||
</template>
|
||||
</v-expansion-panel-title>
|
||||
|
||||
<v-expansion-panel-text>
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
label="搜索设置"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
single-line
|
||||
hide-details
|
||||
class="mb-4"
|
||||
clearable
|
||||
/>
|
||||
|
||||
<v-data-table
|
||||
:items-per-page="settingItems.length"
|
||||
:headers="headers"
|
||||
:items="filteredItems"
|
||||
item-value="key"
|
||||
v-model="selectedItems"
|
||||
show-select
|
||||
density="compact"
|
||||
class="rounded setting-table"
|
||||
@update:selected="handleSelectionChange"
|
||||
:sort-by="[{ key: 'isChanged', order: 'desc' }]"
|
||||
>
|
||||
<template #[`item.description`]="{ item }">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon
|
||||
size="small"
|
||||
:icon="item.icon"
|
||||
class="mr-2"
|
||||
/>
|
||||
{{ item.description }}
|
||||
<v-chip
|
||||
v-if="item.key === 'server.kvToken'"
|
||||
size="x-small"
|
||||
color="error"
|
||||
class="ml-2"
|
||||
>
|
||||
敏感
|
||||
</v-chip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #[`item.value`]="{ item }">
|
||||
<span v-if="typeof item.value === 'boolean'">
|
||||
{{ item.value ? "是" : "否" }}
|
||||
</span>
|
||||
<span v-else-if="item.key === 'server.kvToken' && item.value">
|
||||
{{ item.value.substring(0, 8) }}...
|
||||
</span>
|
||||
<span v-else>{{ item.value }}</span>
|
||||
</template>
|
||||
|
||||
<template #[`item.key`]="{ item }">
|
||||
<span class="text-caption text-grey">{{ item.key }}</span>
|
||||
</template>
|
||||
|
||||
<template #[`item.isChanged`]="{ item }">
|
||||
<v-chip
|
||||
size="x-small"
|
||||
:color="item.isChanged ? 'warning' : 'success'"
|
||||
:text="item.isChanged ? '已修改' : '默认'"
|
||||
density="compact"
|
||||
/>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 链接生成和操作部分 -->
|
||||
<v-card variant="outlined" class="mb-4">
|
||||
<v-card-title class="text-subtitle-1">
|
||||
<v-icon start>mdi-link</v-icon>
|
||||
生成的统一链接
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<!-- 操作按钮 -->
|
||||
<div class="d-flex mb-3 gap-2 flex-wrap">
|
||||
<v-btn
|
||||
variant="flat"
|
||||
color="primary"
|
||||
prepend-icon="mdi-auto-fix"
|
||||
@click="generateUnifiedLink"
|
||||
:disabled="!preconfigForm.namespace.trim()"
|
||||
>
|
||||
生成统一链接
|
||||
</v-btn>
|
||||
<v-btn
|
||||
variant="tonal"
|
||||
color="success"
|
||||
prepend-icon="mdi-test-tube"
|
||||
@click="openTestLink"
|
||||
:disabled="!unifiedLink"
|
||||
>
|
||||
测试链接
|
||||
</v-btn>
|
||||
<v-btn
|
||||
variant="tonal"
|
||||
color="error"
|
||||
prepend-icon="mdi-delete"
|
||||
@click="clearAll"
|
||||
>
|
||||
清空所有
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<!-- 生成的链接 -->
|
||||
<v-text-field
|
||||
v-model="unifiedLink"
|
||||
label="统一链接"
|
||||
readonly
|
||||
variant="outlined"
|
||||
class="mb-3"
|
||||
:append-inner-icon="linkCopied ? 'mdi-check' : 'mdi-content-copy'"
|
||||
@click:append-inner="copyUnifiedLink"
|
||||
:placeholder="preconfigForm.namespace ? '点击「生成统一链接」按钮' : '请先输入命名空间'"
|
||||
/>
|
||||
|
||||
<!-- 链接内容预览 -->
|
||||
<v-alert
|
||||
v-if="unifiedLink"
|
||||
type="success"
|
||||
variant="tonal"
|
||||
class="mb-3"
|
||||
>
|
||||
<div class="text-subtitle-2 mb-2">链接包含内容:</div>
|
||||
<div class="d-flex flex-wrap gap-1">
|
||||
<v-chip size="small" color="primary">
|
||||
<v-icon start size="small">mdi-account-key</v-icon>
|
||||
预配置认证
|
||||
</v-chip>
|
||||
<v-chip
|
||||
v-if="selectedItems.length > 0"
|
||||
size="small"
|
||||
color="secondary"
|
||||
>
|
||||
<v-icon start size="small">mdi-cog</v-icon>
|
||||
{{ selectedItems.length }} 项设置
|
||||
</v-chip>
|
||||
<v-chip v-else size="small" color="grey">
|
||||
<v-icon start size="small">mdi-cog-off</v-icon>
|
||||
无额外设置
|
||||
</v-chip>
|
||||
</div>
|
||||
</v-alert>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 安全提醒 -->
|
||||
<v-alert type="warning" variant="tonal">
|
||||
<div class="text-subtitle-2 mb-2">⚠️ 安全提醒</div>
|
||||
<ul class="text-body-2 pl-4">
|
||||
<li>认证码和设置信息会在URL中传输,请谨慎分发</li>
|
||||
<li>建议仅在受信任的网络环境中使用</li>
|
||||
<li>生产环境建议使用HTTPS协议</li>
|
||||
<li>数据源设置和已变更设置默认不包含敏感Token信息</li>
|
||||
</ul>
|
||||
</v-alert>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -158,15 +372,22 @@ export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
// 选择的设置项键名列表
|
||||
// 设置分享相关
|
||||
selectedItems: [],
|
||||
|
||||
// 生成的链接
|
||||
generatedLink: "",
|
||||
|
||||
// 是否已复制链接
|
||||
linkCopied: false,
|
||||
search: "",
|
||||
|
||||
// 预配置链接生成器相关
|
||||
preconfigForm: {
|
||||
namespace: "",
|
||||
authCode: "",
|
||||
autoExecute: false,
|
||||
},
|
||||
|
||||
// 统一链接相关
|
||||
unifiedLink: "",
|
||||
|
||||
headers: [
|
||||
{ title: "", key: "data-table-select" },
|
||||
{ title: "设置项", key: "description", sortable: true },
|
||||
@ -391,11 +612,14 @@ export default {
|
||||
},
|
||||
|
||||
/**
|
||||
* 选择数据源相关设置
|
||||
* 选择数据源相关设置(默认排除 server.kvToken)
|
||||
*/
|
||||
selectDataSourceSettings() {
|
||||
const dataSourceKeys = this.settingItems
|
||||
.filter((item) => item.key.startsWith("server."))
|
||||
.filter((item) =>
|
||||
item.key.startsWith("server.") &&
|
||||
item.key !== "server.kvToken" // 默认排除敏感的Token
|
||||
)
|
||||
.map((item) => item.key);
|
||||
|
||||
this.selectedItems = dataSourceKeys;
|
||||
@ -403,11 +627,14 @@ export default {
|
||||
},
|
||||
|
||||
/**
|
||||
* 选择已修改的设置
|
||||
* 选择已修改的设置(默认排除 server.kvToken)
|
||||
*/
|
||||
selectChangedSettings() {
|
||||
const changedKeys = this.settingItems
|
||||
.filter((item) => item.isChanged)
|
||||
.filter((item) =>
|
||||
item.isChanged &&
|
||||
item.key !== "server.kvToken" // 默认排除敏感的Token
|
||||
)
|
||||
.map((item) => item.key);
|
||||
|
||||
this.selectedItems = changedKeys;
|
||||
@ -443,16 +670,154 @@ export default {
|
||||
const setting = this.settingItems.find((item) => item.key === key);
|
||||
return setting ? setting.description : key;
|
||||
},
|
||||
|
||||
// ===== 统一链接生成器方法 =====
|
||||
|
||||
/**
|
||||
* 生成包含预配置信息和设置的统一链接
|
||||
*/
|
||||
generateUnifiedLink() {
|
||||
if (!this.preconfigForm.namespace.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const baseUrl = `${window.location.protocol}//${window.location.host}/`;
|
||||
const params = new URLSearchParams();
|
||||
|
||||
// 添加预配置参数
|
||||
params.append("namespace", this.preconfigForm.namespace.trim());
|
||||
|
||||
if (this.preconfigForm.authCode.trim()) {
|
||||
params.append("authCode", this.preconfigForm.authCode.trim());
|
||||
}
|
||||
|
||||
if (this.preconfigForm.autoExecute) {
|
||||
params.append("autoExecute", "true");
|
||||
}
|
||||
|
||||
// 添加设置配置(如果有选择的设置)
|
||||
if (this.selectedItems.length > 0) {
|
||||
const allSettings = exportSettingsAsKeyValue();
|
||||
const configObj = {};
|
||||
|
||||
for (const key of this.selectedItems) {
|
||||
configObj[key] = allSettings[key];
|
||||
}
|
||||
|
||||
// 转换为JSON并进行base64编码
|
||||
const jsonString = JSON.stringify(configObj);
|
||||
const utf8Encoder = new TextEncoder();
|
||||
const utf8Bytes = utf8Encoder.encode(jsonString);
|
||||
const base64String = btoa(
|
||||
Array.from(utf8Bytes)
|
||||
.map((byte) => String.fromCharCode(byte))
|
||||
.join("")
|
||||
);
|
||||
|
||||
params.append("config", base64String);
|
||||
}
|
||||
|
||||
// 生成完整URL
|
||||
this.unifiedLink = `${baseUrl}?${params.toString()}`;
|
||||
this.linkCopied = false;
|
||||
|
||||
console.log("生成统一链接:", this.unifiedLink);
|
||||
console.log("包含预配置:", !!this.preconfigForm.namespace);
|
||||
console.log("包含设置数量:", this.selectedItems.length);
|
||||
} catch (error) {
|
||||
console.error("生成统一链接失败:", error);
|
||||
this.unifiedLink = "链接生成失败,请重试";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 复制统一链接到剪贴板
|
||||
*/
|
||||
async copyUnifiedLink() {
|
||||
if (!this.unifiedLink) {
|
||||
this.generateUnifiedLink();
|
||||
}
|
||||
|
||||
if (!this.unifiedLink || this.unifiedLink.includes("失败")) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(this.unifiedLink);
|
||||
this.linkCopied = true;
|
||||
|
||||
// 3秒后重置复制状态
|
||||
setTimeout(() => {
|
||||
this.linkCopied = false;
|
||||
}, 3000);
|
||||
} catch (error) {
|
||||
console.error("复制统一链接失败:", error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 在新窗口中测试统一链接
|
||||
*/
|
||||
openTestLink() {
|
||||
if (this.unifiedLink && !this.unifiedLink.includes("失败")) {
|
||||
window.open(this.unifiedLink, "_blank");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 清空所有数据
|
||||
*/
|
||||
clearAll() {
|
||||
this.preconfigForm = {
|
||||
namespace: "",
|
||||
authCode: "",
|
||||
autoExecute: false,
|
||||
};
|
||||
this.selectedItems = [];
|
||||
this.unifiedLink = "";
|
||||
this.generatedLink = "";
|
||||
this.linkCopied = false;
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
// 监听选择变化,自动生成链接
|
||||
// 监听选择变化,自动生成统一链接
|
||||
selectedItems: {
|
||||
handler() {
|
||||
this.autoGenerateLink();
|
||||
if (this.preconfigForm.namespace.trim()) {
|
||||
this.generateUnifiedLink();
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
|
||||
// 监听预配置表单变化,自动生成统一链接
|
||||
"preconfigForm.namespace": {
|
||||
handler() {
|
||||
if (this.preconfigForm.namespace.trim()) {
|
||||
this.generateUnifiedLink();
|
||||
} else {
|
||||
this.unifiedLink = "";
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
"preconfigForm.authCode": {
|
||||
handler() {
|
||||
if (this.preconfigForm.namespace.trim()) {
|
||||
this.generateUnifiedLink();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
"preconfigForm.autoExecute": {
|
||||
handler() {
|
||||
if (this.preconfigForm.namespace.trim()) {
|
||||
this.generateUnifiedLink();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -100,14 +100,18 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, watch } from 'vue'
|
||||
import { getSetting, setSetting } from '@/utils/settings'
|
||||
import axios from '@/axios/axios'
|
||||
|
||||
defineProps({
|
||||
const props = defineProps({
|
||||
showCancel: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
preconfig: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
@ -120,6 +124,30 @@ const form = ref({
|
||||
const authenticating = ref(false)
|
||||
const error = ref('')
|
||||
|
||||
// 监听预配置数据变化
|
||||
watch(
|
||||
() => props.preconfig,
|
||||
(newPreconfig) => {
|
||||
if (newPreconfig) {
|
||||
console.log('应用预配置数据:', newPreconfig)
|
||||
form.value.namespace = newPreconfig.namespace || ''
|
||||
form.value.password = newPreconfig.password || ''
|
||||
|
||||
// 如果启用自动执行且有命名空间,自动尝试认证
|
||||
if (newPreconfig.autoExecute && newPreconfig.namespace) {
|
||||
console.log('检测到自动执行标志且有命名空间,自动执行认证')
|
||||
// 延迟一下确保UI已更新
|
||||
setTimeout(() => {
|
||||
authenticate()
|
||||
}, 300)
|
||||
} else if (newPreconfig.namespace) {
|
||||
console.log('预配置数据已填入,等待手动认证')
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
const authenticate = async () => {
|
||||
if (!form.value.namespace || authenticating.value) return
|
||||
error.value = ''
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-card border>
|
||||
<v-card border rounded="xl" hover>
|
||||
<v-card-item>
|
||||
<template #prepend>
|
||||
<v-icon icon="mdi-information" size="large" class="mr-2" />
|
||||
@ -13,8 +13,8 @@
|
||||
<div class="d-flex flex-column align-start">
|
||||
<v-avatar size="120" class="mb-4">
|
||||
<v-img
|
||||
src="https://github.com/SunWuyuan.png"
|
||||
alt="Sunwuyuan"
|
||||
src="../../assets/cslogo.png"
|
||||
alt="Classworks"
|
||||
/>
|
||||
</v-avatar>
|
||||
|
||||
|
||||
@ -65,10 +65,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text> </v-card
|
||||
><v-card title="Classworks KV" subtitle="云原生键值数据库" border hover
|
||||
><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
|
||||
|
||||
@ -43,7 +43,11 @@
|
||||
</template>
|
||||
</v-app-bar>
|
||||
<!-- 初始化选择卡片,仅在首页且需要授权时显示;不影响顶栏 -->
|
||||
<init-service-chooser v-if="shouldShowInit" @done="settingsTick++" />
|
||||
<init-service-chooser
|
||||
v-if="shouldShowInit"
|
||||
:preconfig="preconfigData"
|
||||
@done="settingsTick++"
|
||||
/>
|
||||
|
||||
<!-- 学生姓名管理组件 -->
|
||||
<StudentNameManager
|
||||
@ -649,7 +653,7 @@
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card> </v-dialog
|
||||
><br /><br /><br /><br /><br /><br />
|
||||
><br /><br /><br />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -797,6 +801,13 @@ export default {
|
||||
$offKvChanged: null,
|
||||
$offConnect: null,
|
||||
debouncedRealtimeRefresh: null,
|
||||
// 预配数据
|
||||
preconfigData: {
|
||||
namespace: null,
|
||||
authCode: null,
|
||||
autoOpen: false,
|
||||
autoExecute: false
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@ -1203,6 +1214,9 @@ export default {
|
||||
},
|
||||
|
||||
async initializeData() {
|
||||
// 解析预配数据
|
||||
this.parsePreconfigData();
|
||||
|
||||
const configApplied = await this.parseUrlConfig();
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
@ -1251,10 +1265,13 @@ export default {
|
||||
if (response.error.code === "NOT_FOUND") {
|
||||
this.state.showNoDataMessage = true;
|
||||
this.state.noDataMessage = response.error.message;
|
||||
this.state.boardData = {
|
||||
homework: {},
|
||||
attendance: { absent: [], late: [], exclude: [] },
|
||||
};
|
||||
// 只有当前没有数据时才设置为空,避免覆盖已有的本地数据
|
||||
if (!this.state.boardData || (!this.state.boardData.homework && !this.state.boardData.attendance)) {
|
||||
this.state.boardData = {
|
||||
homework: {},
|
||||
attendance: { absent: [], late: [], exclude: [] },
|
||||
};
|
||||
}
|
||||
} else {
|
||||
throw new Error(response.error.message);
|
||||
}
|
||||
@ -1272,11 +1289,16 @@ export default {
|
||||
this.$message.success("下载成功", "数据已更新");
|
||||
}
|
||||
} catch (error) {
|
||||
this.state.boardData = {
|
||||
homework: {},
|
||||
attendance: { absent: [], late: [], exclude: [] },
|
||||
};
|
||||
// 数据加载失败时不覆盖现有数据,只显示错误信息
|
||||
console.error("数据加载失败:", error);
|
||||
this.$message.error("下载失败", error.message);
|
||||
// 如果当前没有任何数据,才初始化为空数据
|
||||
if (!this.state.boardData || (!this.state.boardData.homework && !this.state.boardData.attendance)) {
|
||||
this.state.boardData = {
|
||||
homework: {},
|
||||
attendance: { absent: [], late: [], exclude: [] },
|
||||
};
|
||||
}
|
||||
} finally {
|
||||
this.loading.download = false;
|
||||
}
|
||||
@ -2224,6 +2246,64 @@ export default {
|
||||
currentDate.setDate(currentDate.getDate() + offset);
|
||||
this.handleDateSelect(currentDate);
|
||||
},
|
||||
|
||||
// 解析预配数据
|
||||
parsePreconfigData() {
|
||||
try {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const namespace = urlParams.get("namespace");
|
||||
const authCode = urlParams.get("authCode") || urlParams.get("auth_code");
|
||||
const autoExecute = urlParams.get("autoExecute") || urlParams.get("auto_execute");
|
||||
|
||||
if (namespace) {
|
||||
this.preconfigData.namespace = namespace;
|
||||
this.preconfigData.authCode = authCode;
|
||||
this.preconfigData.autoOpen = true;
|
||||
// 解析自动执行参数,支持 true/false、1/0、yes/no
|
||||
this.preconfigData.autoExecute = this.parseBoolean(autoExecute);
|
||||
|
||||
console.log("检测到预配数据:", {
|
||||
namespace: this.preconfigData.namespace,
|
||||
hasAuthCode: !!this.preconfigData.authCode,
|
||||
autoExecute: this.preconfigData.autoExecute
|
||||
});
|
||||
|
||||
// 清理URL参数,避免重复处理
|
||||
this.cleanupUrlParams(['namespace', 'authCode', 'auth_code', 'autoExecute', 'auto_execute']);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("解析预配数据失败:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// 解析布尔值参数
|
||||
parseBoolean(value) {
|
||||
if (!value) return false;
|
||||
const lowerValue = value.toLowerCase();
|
||||
return lowerValue === 'true' || lowerValue === '1' || lowerValue === 'yes';
|
||||
},
|
||||
|
||||
// 清理URL参数
|
||||
cleanupUrlParams(params) {
|
||||
try {
|
||||
const url = new URL(window.location);
|
||||
let hasChanged = false;
|
||||
|
||||
params.forEach(param => {
|
||||
if (url.searchParams.has(param)) {
|
||||
url.searchParams.delete(param);
|
||||
hasChanged = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasChanged) {
|
||||
// 使用 replaceState 避免创建新的历史记录
|
||||
window.history.replaceState({}, document.title, url.toString());
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("清理URL参数失败:", error);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -44,13 +44,13 @@
|
||||
direction="vertical"
|
||||
>
|
||||
<v-tabs-window-item value="index"
|
||||
><v-card class="service-card gradient-right clickable" elevation="8">
|
||||
><v-card class="service-card gradient-right clickable mb-4" elevation="8" rounded="xl" border hover @click="openClassworksKV" color="primary" variant="tonal">
|
||||
<v-card-item>
|
||||
<div class="card-title">
|
||||
<div>
|
||||
<div class="text-h6">Classworks KV</div>
|
||||
<div class="text-h6">在寻找 Classworks KV ?</div>
|
||||
<div class="text-caption text-medium-emphasis">
|
||||
云原生键值数据库
|
||||
文档形键值数据库
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -69,7 +69,7 @@
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<v-card title="Classworks" subtitle="设置" class="rounded-xl" border>
|
||||
<v-card title="Classworks" subtitle="设置" class="rounded-xl mb-4" border>
|
||||
<v-card-text>
|
||||
<v-alert
|
||||
color="error"
|
||||
@ -106,7 +106,7 @@
|
||||
</p>
|
||||
<p style="color: #666">
|
||||
上述浏览器商标为其所属公司所有,Classworks™
|
||||
与上述浏览器所属公司暂无竞争关系。
|
||||
与上述浏览器所属公司无竞争关系。
|
||||
</p>
|
||||
<br /><v-btn
|
||||
href="https://www.microsoft.com/zh-cn/windows/microsoft-edge"
|
||||
@ -119,7 +119,7 @@
|
||||
></v-alert
|
||||
>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-card><about-card />
|
||||
</v-tabs-window-item>
|
||||
|
||||
<v-tabs-window-item value="server">
|
||||
@ -376,7 +376,7 @@ export default {
|
||||
value: "student",
|
||||
},
|
||||
{
|
||||
title: "分享设置",
|
||||
title: "预配链接",
|
||||
icon: "mdi-share",
|
||||
value: "share",
|
||||
},
|
||||
|
||||
@ -60,6 +60,10 @@ html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
.v-app-bar {
|
||||
position: fixed !important;
|
||||
}
|
||||
|
||||
// 触摸友好的列表项
|
||||
.touch-list-item {
|
||||
min-height: 56px;
|
||||
|
||||
@ -185,12 +185,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* 确保容器高度不超过视口 */
|
||||
.main-window {
|
||||
max-height: calc(100vh - 80px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 优化滚动条样式 */
|
||||
.main-window::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
|
||||
@ -232,23 +232,11 @@ export default {
|
||||
// 迁移失败不影响URL生成,继续执行
|
||||
}
|
||||
}
|
||||
|
||||
// 获取认证token
|
||||
const authtoken = getSetting("server.kvToken");
|
||||
// 构建云端访问URL
|
||||
let url = `${serverUrl}/${machineId}/${key}`;
|
||||
let url = `${serverUrl}/kv/${key}?token=${authtoken}`;
|
||||
|
||||
// 根据网站验证情况添加token参数
|
||||
const namespaceInfo = await kvServerProvider.loadNamespaceInfo();
|
||||
if (namespaceInfo && namespaceInfo.success !== false) {
|
||||
const { accessType } = namespaceInfo;
|
||||
|
||||
// 如果是私有访问,添加token参数
|
||||
if (accessType === 'private' && siteKey) {
|
||||
const urlObj = new URL(url);
|
||||
urlObj.searchParams.set('token', siteKey);
|
||||
url = urlObj.toString();
|
||||
}
|
||||
// 公开或受保护访问不需要token参数
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user