mirror of
https://github.com/ZeroCatDev/Classworks.git
synced 2025-12-07 21:13:11 +00:00
feat: Refactor data migration functionality and introduce cloud migration dialog
- Removed the DataMigration.vue component and integrated its functionality into KvDatabaseCard.vue. - Added a new CloudMigrationDialog.vue component for handling cloud data migration. - Updated KvDatabaseCard.vue to include a button for initiating local migration and to manage the visibility of the new CloudMigrationDialog. - Cleaned up the DataProviderSettingsCard.vue by removing old data migration UI elements. - Enhanced user experience by providing a more streamlined migration process with clear category selection and progress indication.
This commit is contained in:
parent
d50788c1f5
commit
1831c9144d
@ -79,6 +79,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.js"></script>
|
||||||
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="display: none;">浙ICP备2024068645号-4</a>
|
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="display: none;">xICP备x号-4</a>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<a
|
<a
|
||||||
aria-label="浙ICP备2024068645号"
|
aria-label="xICP备x号"
|
||||||
class="floating-icp-link"
|
class="floating-icp-link"
|
||||||
href="https://beian.miit.gov.cn/"
|
href="https://beian.miit.gov.cn/"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
style="display: none;"
|
||||||
>
|
>
|
||||||
浙ICP备2024068645号
|
xICP备x号
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
283
src/components/settings/CloudMigrationDialog.vue
Normal file
283
src/components/settings/CloudMigrationDialog.vue
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog v-model="dialog" max-width="600" scrollable>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>迁移到云端</v-card-title>
|
||||||
|
<v-card-text style="height: 400px;">
|
||||||
|
<div v-if="loading" class="d-flex justify-center align-center fill-height">
|
||||||
|
<v-progress-circular indeterminate color="primary"></v-progress-circular>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="keys.length === 0" class="d-flex justify-center align-center fill-height">
|
||||||
|
没有找到本地数据
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<!-- Category Selection -->
|
||||||
|
<v-list select-strategy="classic" class="mb-4">
|
||||||
|
<v-list-subheader>选择数据类型</v-list-subheader>
|
||||||
|
|
||||||
|
<v-list-item
|
||||||
|
v-for="category in categories"
|
||||||
|
:key="category.id"
|
||||||
|
@click="toggleCategory(category)"
|
||||||
|
>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-checkbox-btn
|
||||||
|
:model-value="getCategoryState(category)"
|
||||||
|
:indeterminate="getCategoryIndeterminate(category)"
|
||||||
|
@click.stop="toggleCategory(category)"
|
||||||
|
></v-checkbox-btn>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title>{{ category.label }}</v-list-item-title>
|
||||||
|
<v-list-item-subtitle>{{ category.description }} ({{ getCategoryCount(category) }} 项)</v-list-item-subtitle>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
|
||||||
|
<v-divider class="mb-4"></v-divider>
|
||||||
|
|
||||||
|
<!-- Individual Keys Expansion -->
|
||||||
|
<v-expansion-panels>
|
||||||
|
<v-expansion-panel title="详细数据列表">
|
||||||
|
<v-expansion-panel-text>
|
||||||
|
<v-list select-strategy="classic" density="compact">
|
||||||
|
<v-list-item
|
||||||
|
v-for="key in keys"
|
||||||
|
:key="key"
|
||||||
|
:value="key"
|
||||||
|
>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-checkbox-btn v-model="selectedKeys" :value="key"></v-checkbox-btn>
|
||||||
|
</template>
|
||||||
|
<v-list-item-title>{{ key }}</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-expansion-panel-text>
|
||||||
|
</v-expansion-panel>
|
||||||
|
</v-expansion-panels>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-actions>
|
||||||
|
<div class="text-caption ml-4 text-medium-emphasis">
|
||||||
|
已选择 {{ selectedKeys.length }} 项
|
||||||
|
</div>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn variant="text" @click="dialog = false">取消</v-btn>
|
||||||
|
<v-btn color="primary" @click="migrate" :loading="migrating" :disabled="selectedKeys.length === 0">
|
||||||
|
开始迁移
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
|
||||||
|
<!-- Result Dialog -->
|
||||||
|
<v-dialog v-model="resultDialog" max-width="500">
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>迁移结果</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<div v-if="result">
|
||||||
|
<p>总计: {{ result.summary.total }}</p>
|
||||||
|
<p>成功: {{ result.summary.successful }}</p>
|
||||||
|
<p>失败: {{ result.summary.failed }}</p>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="error">
|
||||||
|
<p class="text-error">{{ error }}</p>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="primary" @click="resultDialog = false">关闭</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch, computed } from 'vue';
|
||||||
|
import { kvLocalProvider } from '@/utils/providers/kvLocalProvider';
|
||||||
|
import { getSetting } from '@/utils/settings';
|
||||||
|
import axios from '@/axios/axios';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: Boolean,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
|
const dialog = ref(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
const migrating = ref(false);
|
||||||
|
const keys = ref([]);
|
||||||
|
const selectedKeys = ref([]);
|
||||||
|
const resultDialog = ref(false);
|
||||||
|
const result = ref(null);
|
||||||
|
const error = ref(null);
|
||||||
|
|
||||||
|
const categories = [
|
||||||
|
{
|
||||||
|
id: 'student-list',
|
||||||
|
label: '学生列表',
|
||||||
|
description: 'classworks-list-main',
|
||||||
|
matcher: (key) => key === 'classworks-list-main' || key.startsWith('classworks-list-main')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'homework-data',
|
||||||
|
label: '作业数据',
|
||||||
|
description: 'classworks-data-*',
|
||||||
|
matcher: (key) => key.startsWith('classworks-data-')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'lists',
|
||||||
|
label: '列表',
|
||||||
|
description: 'classworks-list-*',
|
||||||
|
matcher: (key) => key.startsWith('classworks-list-')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'other',
|
||||||
|
label: '其他',
|
||||||
|
description: '所有其他键',
|
||||||
|
matcher: (key) => !key.startsWith('classworks-data-') && !key.startsWith('classworks-list-')
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
watch(() => props.modelValue, (val) => {
|
||||||
|
dialog.value = val;
|
||||||
|
if (val) {
|
||||||
|
loadKeys();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(dialog, (val) => {
|
||||||
|
emit('update:modelValue', val);
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadKeys = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await kvLocalProvider.loadKeys({ limit: 1000 }); // Load many keys
|
||||||
|
keys.value = res.keys || [];
|
||||||
|
selectedKeys.value = [];
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCategoryKeys = (category) => {
|
||||||
|
return keys.value.filter(category.matcher);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCategoryCount = (category) => {
|
||||||
|
return getCategoryKeys(category).length;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCategoryState = (category) => {
|
||||||
|
const catKeys = getCategoryKeys(category);
|
||||||
|
if (catKeys.length === 0) return false;
|
||||||
|
const selectedCount = catKeys.filter(k => selectedKeys.value.includes(k)).length;
|
||||||
|
return selectedCount === catKeys.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCategoryIndeterminate = (category) => {
|
||||||
|
const catKeys = getCategoryKeys(category);
|
||||||
|
if (catKeys.length === 0) return false;
|
||||||
|
const selectedCount = catKeys.filter(k => selectedKeys.value.includes(k)).length;
|
||||||
|
return selectedCount > 0 && selectedCount < catKeys.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleCategory = (category) => {
|
||||||
|
const catKeys = getCategoryKeys(category);
|
||||||
|
if (catKeys.length === 0) return;
|
||||||
|
|
||||||
|
const currentState = getCategoryState(category);
|
||||||
|
|
||||||
|
const newSelectedKeys = new Set(selectedKeys.value);
|
||||||
|
|
||||||
|
if (currentState) {
|
||||||
|
// Unselect
|
||||||
|
catKeys.forEach(k => newSelectedKeys.delete(k));
|
||||||
|
} else {
|
||||||
|
// Select
|
||||||
|
catKeys.forEach(k => newSelectedKeys.add(k));
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedKeys.value = Array.from(newSelectedKeys);
|
||||||
|
};
|
||||||
|
|
||||||
|
const migrate = async () => {
|
||||||
|
migrating.value = true;
|
||||||
|
error.value = null;
|
||||||
|
result.value = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const batchData = {};
|
||||||
|
for (const key of selectedKeys.value) {
|
||||||
|
const res = await kvLocalProvider.loadData(key);
|
||||||
|
if (res) {
|
||||||
|
// kvLocalProvider.loadData returns formatResponse(JSON.parse(data)) which is just the data object if successful?
|
||||||
|
// Let's check formatResponse in dataProvider.js: export const formatResponse = (data) => data;
|
||||||
|
// So it returns the data directly?
|
||||||
|
// Wait, kvLocalProvider.js: return formatResponse(JSON.parse(data));
|
||||||
|
// But if error: return formatError(...) which returns { success: false, error: ... }
|
||||||
|
// formatResponse is just identity function.
|
||||||
|
// So if success, it returns the object.
|
||||||
|
// But wait, formatError returns an object with success: false.
|
||||||
|
// If success, it returns the data object directly?
|
||||||
|
// Let's re-read dataProvider.js
|
||||||
|
|
||||||
|
// export const formatResponse = (data) => data;
|
||||||
|
// export const formatError = (message, code = "UNKNOWN_ERROR") => ({ success: false, error: {code, message} });
|
||||||
|
|
||||||
|
// So if successful, it returns the data object.
|
||||||
|
// If failed, it returns { success: false, ... }
|
||||||
|
// This is a bit ambiguous if the data object itself has a success property.
|
||||||
|
// But assuming the data is what we want.
|
||||||
|
|
||||||
|
// However, looking at kvLocalProvider.js:
|
||||||
|
// if (!data) return formatError(...)
|
||||||
|
// return formatResponse(JSON.parse(data))
|
||||||
|
|
||||||
|
// So if I get an object that has success: false, it might be an error.
|
||||||
|
// But usually data is just the stored object.
|
||||||
|
|
||||||
|
if (res && res.success === false && res.error) {
|
||||||
|
console.warn(`Skipping key ${key} due to load error`, res.error);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
batchData[key] = res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const serverUrl = getSetting("server.domain");
|
||||||
|
const token = getSetting("server.kvToken");
|
||||||
|
|
||||||
|
if (!serverUrl || !token) {
|
||||||
|
throw new Error("请先配置服务器地址和 Token");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove trailing slash if present
|
||||||
|
const baseUrl = serverUrl.replace(/\/$/, '');
|
||||||
|
|
||||||
|
const response = await axios.post(`${baseUrl}/kv/_batchimport`, batchData, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data && response.data.code === 200) {
|
||||||
|
result.value = response.data.data;
|
||||||
|
resultDialog.value = true;
|
||||||
|
dialog.value = false;
|
||||||
|
} else {
|
||||||
|
throw new Error(response.data?.message || "迁移失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
error.value = e.response?.data?.message || e.message || "发生未知错误";
|
||||||
|
resultDialog.value = true;
|
||||||
|
} finally {
|
||||||
|
migrating.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@ -53,33 +53,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</template>
|
</template>
|
||||||
<v-list-item>
|
|
||||||
<template #prepend>
|
|
||||||
<v-icon class="mr-3" icon="mdi-database-import"/>
|
|
||||||
</template>
|
|
||||||
<v-list-item-title>迁移旧数据</v-list-item-title>
|
|
||||||
<v-list-item-subtitle
|
|
||||||
>将旧的存储格式数据转移到新的KV存储
|
|
||||||
</v-list-item-subtitle
|
|
||||||
>
|
|
||||||
<template #append>
|
|
||||||
<v-btn :loading="migrateLoading" variant="tonal" @click="migrateData">
|
|
||||||
迁移
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
</v-list-item
|
|
||||||
><!-- 显示机器ID -->
|
|
||||||
<v-list-item>
|
|
||||||
<template #prepend>
|
|
||||||
<v-icon class="mr-3" icon="mdi-identifier"/>
|
|
||||||
</template>
|
|
||||||
<v-list-item-title>本机唯一标识符</v-list-item-title>
|
|
||||||
<v-list-item-subtitle v-if="machineId">{{
|
|
||||||
machineId
|
|
||||||
}}
|
|
||||||
</v-list-item-subtitle>
|
|
||||||
<v-list-item-subtitle v-else>正在加载...</v-list-item-subtitle>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<v-icon class="mr-3" icon="mdi-lan-connect"/>
|
<v-icon class="mr-3" icon="mdi-lan-connect"/>
|
||||||
|
|||||||
@ -33,6 +33,10 @@
|
|||||||
<v-icon class="mr-1" icon="mdi-plus"/>
|
<v-icon class="mr-1" icon="mdi-plus"/>
|
||||||
新建
|
新建
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
<v-btn @click="showMigrationDialog = true">
|
||||||
|
<v-icon class="mr-1" icon="mdi-cloud-upload"/>
|
||||||
|
从本地迁移
|
||||||
|
</v-btn>
|
||||||
</v-btn-group>
|
</v-btn-group>
|
||||||
</template>
|
</template>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
@ -361,11 +365,14 @@
|
|||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
|
||||||
|
<cloud-migration-dialog v-model="showMigrationDialog" />
|
||||||
</settings-card>
|
</settings-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import SettingsCard from '@/components/SettingsCard.vue';
|
import SettingsCard from '@/components/SettingsCard.vue';
|
||||||
|
import CloudMigrationDialog from '../CloudMigrationDialog.vue';
|
||||||
import dataProvider from '@/utils/dataProvider';
|
import dataProvider from '@/utils/dataProvider';
|
||||||
import {getSetting} from '@/utils/settings';
|
import {getSetting} from '@/utils/settings';
|
||||||
import {openDB} from 'idb';
|
import {openDB} from 'idb';
|
||||||
@ -373,7 +380,8 @@ import {openDB} from 'idb';
|
|||||||
export default {
|
export default {
|
||||||
name: 'KvDatabaseCard',
|
name: 'KvDatabaseCard',
|
||||||
components: {
|
components: {
|
||||||
SettingsCard
|
SettingsCard,
|
||||||
|
CloudMigrationDialog
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
@ -391,6 +399,7 @@ export default {
|
|||||||
deleteDialog: false,
|
deleteDialog: false,
|
||||||
createDialog: false,
|
createDialog: false,
|
||||||
cloudUrlDialog: false,
|
cloudUrlDialog: false,
|
||||||
|
showMigrationDialog: false,
|
||||||
|
|
||||||
// 选中的项目
|
// 选中的项目
|
||||||
selectedItem: null,
|
selectedItem: null,
|
||||||
|
|||||||
@ -1,233 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-container>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12">
|
|
||||||
<div class="d-flex align-center mb-6">
|
|
||||||
<v-icon
|
|
||||||
class="mr-3"
|
|
||||||
color="primary"
|
|
||||||
size="x-large"
|
|
||||||
>
|
|
||||||
mdi-database-sync
|
|
||||||
</v-icon>
|
|
||||||
<div>
|
|
||||||
<h1 class="text-h4">
|
|
||||||
数据迁移工具
|
|
||||||
</h1>
|
|
||||||
<div class="text-subtitle-1 text-grey">
|
|
||||||
将现有数据迁移至 KV 存储系统
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<v-card
|
|
||||||
class="mb-6"
|
|
||||||
color="info"
|
|
||||||
variant="tonal"
|
|
||||||
>
|
|
||||||
<v-card-text class="d-flex align-center">
|
|
||||||
<v-icon
|
|
||||||
class="mr-2"
|
|
||||||
color="info"
|
|
||||||
>
|
|
||||||
mdi-information-outline
|
|
||||||
</v-icon>
|
|
||||||
<span>
|
|
||||||
使用此工具可以将数据从旧存储系统迁移到新的 KV 存储系统,选择本地或云端迁移,以确保数据不会丢失。
|
|
||||||
</span>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
|
|
||||||
<MigrationTool ref="migrationTool"/>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<!-- 一键迁移对话框 -->
|
|
||||||
<v-dialog
|
|
||||||
v-model="showMigrationDialog"
|
|
||||||
max-width="500"
|
|
||||||
persistent
|
|
||||||
>
|
|
||||||
<v-card>
|
|
||||||
<v-card-title class="text-h5 d-flex align-center">
|
|
||||||
<v-icon
|
|
||||||
class="mr-3"
|
|
||||||
color="primary"
|
|
||||||
size="large"
|
|
||||||
>
|
|
||||||
mdi-database-sync
|
|
||||||
</v-icon>
|
|
||||||
一键数据迁移
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text class="mt-4">
|
|
||||||
<p>
|
|
||||||
系统将自动读取您的配置,并将过去半年的数据迁移至Classworks
|
|
||||||
KV数据库中
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<v-alert
|
|
||||||
class="mt-4"
|
|
||||||
color="info"
|
|
||||||
icon="mdi-information-outline"
|
|
||||||
variant="tonal"
|
|
||||||
>
|
|
||||||
<ul class="ml-3 mt-1">
|
|
||||||
<li>数据源: {{ dataSourceText }}</li>
|
|
||||||
<li>班级: {{ classNumber }}</li>
|
|
||||||
<li>服务器: {{ serverDomain || "本地存储" }}</li>
|
|
||||||
<li>
|
|
||||||
迁移范围: {{ formatDate(sixMonthsAgo) }} 至
|
|
||||||
{{ formatDate(today) }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</v-alert>
|
|
||||||
</v-card-text>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer/>
|
|
||||||
<v-btn
|
|
||||||
color="grey-darken-1"
|
|
||||||
variant="text"
|
|
||||||
@click="showMigrationDialog = false"
|
|
||||||
>
|
|
||||||
稍后再说
|
|
||||||
</v-btn>
|
|
||||||
<v-btn
|
|
||||||
:disabled="isAutoMigrating"
|
|
||||||
:loading="isAutoMigrating"
|
|
||||||
color="primary"
|
|
||||||
size="large"
|
|
||||||
variant="elevated"
|
|
||||||
@click="startAutoMigration"
|
|
||||||
>
|
|
||||||
<v-icon
|
|
||||||
class="mr-2"
|
|
||||||
left
|
|
||||||
>
|
|
||||||
mdi-database-export
|
|
||||||
</v-icon>
|
|
||||||
开始一键迁移
|
|
||||||
</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import MigrationTool from "@/components/MigrationTool.vue";
|
|
||||||
import {getSetting, setSetting} from "@/utils/settings";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "DataMigrationPage",
|
|
||||||
components: {
|
|
||||||
MigrationTool,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
const today = new Date();
|
|
||||||
const sixMonthsAgo = new Date();
|
|
||||||
sixMonthsAgo.setMonth(today.getMonth() - 3);
|
|
||||||
|
|
||||||
return {
|
|
||||||
showMigrationDialog: false,
|
|
||||||
isAutoMigrating: false,
|
|
||||||
today,
|
|
||||||
sixMonthsAgo,
|
|
||||||
classNumber: "",
|
|
||||||
serverDomain: "",
|
|
||||||
dataProvider: "",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
dataSourceText() {
|
|
||||||
switch (this.dataProvider) {
|
|
||||||
case "server":
|
|
||||||
return "服务器";
|
|
||||||
case "indexeddb":
|
|
||||||
return "本地数据库";
|
|
||||||
case "kv-local":
|
|
||||||
return "本地 KV 存储";
|
|
||||||
case "kv-server":
|
|
||||||
return "远程 KV 存储";
|
|
||||||
case "classworkscloud":
|
|
||||||
return "Classworks 云";
|
|
||||||
default:
|
|
||||||
return "未知来源";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
async mounted() {
|
|
||||||
this.loadSettings();
|
|
||||||
if (this.serverDomain == "https://class.wuyuan.dev") {
|
|
||||||
await this.startAutoMigration();
|
|
||||||
this.$router.push("/");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
loadSettings() {
|
|
||||||
this.classNumber = getSetting("server.classNumber");
|
|
||||||
this.serverDomain = getSetting("server.domain");
|
|
||||||
this.dataProvider = getSetting("server.provider");
|
|
||||||
|
|
||||||
this.showMigrationDialog =
|
|
||||||
this.dataProvider === "server" || this.dataProvider === "indexeddb";
|
|
||||||
},
|
|
||||||
formatDate(date) {
|
|
||||||
return date.toLocaleDateString();
|
|
||||||
},
|
|
||||||
async startAutoMigration() {
|
|
||||||
if (!this.$refs.migrationTool) {
|
|
||||||
console.error("MigrationTool组件引用不可用");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isAutoMigrating = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 设置迁移工具的参数
|
|
||||||
const migrationTool = this.$refs.migrationTool;
|
|
||||||
migrationTool.classNumber = this.classNumber;
|
|
||||||
migrationTool.migrationType =
|
|
||||||
this.dataProvider === "server" ? "server" : "local";
|
|
||||||
migrationTool.serverUrl = this.serverDomain;
|
|
||||||
migrationTool.targetStorage = "kv-server";
|
|
||||||
//migrationTool.targetServerUrl = this.serverDomain;
|
|
||||||
|
|
||||||
// 设置半年的日期范围
|
|
||||||
migrationTool.startDate = this.formatDateString(this.sixMonthsAgo);
|
|
||||||
migrationTool.endDate = this.formatDateString(this.today);
|
|
||||||
|
|
||||||
// 根据数据源类型进行相应操作
|
|
||||||
if (this.dataProvider === "server") {
|
|
||||||
// 预览服务器数据
|
|
||||||
await migrationTool.previewServerData();
|
|
||||||
} else {
|
|
||||||
// 扫描本地数据库
|
|
||||||
await migrationTool.scanLocalDatabase();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 开始迁移
|
|
||||||
if (migrationTool.displayItems.length > 0) {
|
|
||||||
await migrationTool.startMigration();
|
|
||||||
} else {
|
|
||||||
console.warn("没有找到可迁移的数据");
|
|
||||||
}
|
|
||||||
setSetting("server.provider", "classworkscloud");
|
|
||||||
} catch (error) {
|
|
||||||
console.error("自动迁移失败:", error);
|
|
||||||
} finally {
|
|
||||||
this.isAutoMigrating = false;
|
|
||||||
this.showMigrationDialog = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
formatDateString(date) {
|
|
||||||
const year = date.getFullYear();
|
|
||||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
||||||
const day = String(date.getDate()).padStart(2, "0");
|
|
||||||
return `${year}-${month}-${day}`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
metaInfo: {
|
|
||||||
title: "数据迁移工具",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
Loading…
x
Reference in New Issue
Block a user