Files
daohangye/src/components/apps/ImportExport/index.vue
T

361 lines
11 KiB
Vue

<script setup lang="ts">
import { onMounted, ref } from 'vue'
import type { UploadFileInfo } from 'naive-ui'
import { NAlert, NButton, NCheckbox, NCheckboxGroup, NDivider, NInput, NSpace, NUpload, useMessage } from 'naive-ui'
import { RoundCardModal, SvgIcon } from '@/components/common'
import type { IconGroup, ImportJsonResult } from '@/utils/jsonImportExport'
import { ConfigVersionLowError, FormatError, exportJson, importJsonString } from '@/utils/jsonImportExport'
import { get as getAbout } from '@/api/system/about'
import { edit as addGroup, getList as getGroupList } from '@/api/panel/itemIconGroup'
import { addMultiple as addMultipleIcons, getListByGroupId } from '@/api/panel/itemIcon'
import { t } from '@/locales'
interface ItemGroup extends Panel.ItemIconGroup {
items?: Panel.ItemInfo[]
}
const ms = useMessage()
const jsonData = ref<string | null>(null)
const importWarning = ref<string[]>([])
const importRoundModalShow = ref(false)
const exportRoundModalShow = ref(false)
const loading = ref(false)
const uploadLoading = ref(false)
const version = ref('') // 当前软件版本
const debug = ref(false)
const importObj = ref<ImportJsonResult | null> (null)
const importItems = ref<string[]>(['icons']) // 当前软件版本支持导入导出的项目
const checkedItems = ref<string[]>(['icons']) // 当前准备导入的项目
// 导入图标
async function importIcons(): Promise<string | null> {
const groups = importObj.value?.geticons()
const batchSize = 50
if (!groups)
return null
try {
for (let i = 0; i < groups.length; i++) {
const element = groups[i]
// 创建组得到组id
const createGroupResponse = await addGroup<Panel.ItemIconGroup>({
title: element.title,
sort: element.sort,
})
if (createGroupResponse.code === 0) {
const groupId = createGroupResponse.data?.id
if (groupId) {
let addIcons: Panel.ItemInfo[] = []
// 批量添加子项
for (let iconI = 0; iconI < element.children.length; iconI++) {
const iconElement = element.children[iconI]
addIcons.push({
title: iconElement.title,
sort: iconElement.sort,
icon: iconElement.icon,
url: iconElement.url,
lanUrl: iconElement.lanUrl,
description: iconElement.description,
openMethod: iconElement.openMethod,
itemIconGroupId: groupId,
})
// 每 batchSize 个添加一次
if (addIcons.length === batchSize || iconI === element.children.length - 1) {
const response = await addMultipleIcons(addIcons)
if (response.code !== 0)
return response.msg
addIcons = []
}
}
}
}
else {
return createGroupResponse.msg
}
}
return null
}
catch (error) {
if (error instanceof Error)
return `${t('common.failed')}: ${error.message}`
else
return t('common.unknownError')
}
}
// 导出图标
async function exportIcons(): Promise<IconGroup[]> {
const iconGroups: IconGroup[] = []
// 获取组数据
const { code, data } = await getGroupList<Common.ListResponse<ItemGroup[]>>()
if (code === 0) {
// 使用 Promise.all 等待所有异步操作完成
await Promise.all(data.list.map(async (element) => {
const group: IconGroup = {
title: element.title as string,
sort: element.sort as 0,
children: [],
}
const res = await getListByGroupId<Common.ListResponse<Panel.ItemInfo[]>>(element.id)
if (res.code === 0) {
for (const iconElement of res.data.list) {
group.children.push({
icon: iconElement.icon,
sort: iconElement.sort || 99999,
title: iconElement.title,
url: iconElement.url,
lanUrl: iconElement.lanUrl || '',
description: iconElement.description || '',
openMethod: iconElement.openMethod || 1,
})
}
}
iconGroups.push(group)
}))
return iconGroups
}
else {
return []
}
}
onMounted(() => {
interface Version {
versionName: string
versionCode: number
}
getAbout<Version>().then((res) => {
if (res.code === 0)
version.value = res.data.versionName
})
})
function handleFileChange(options: { file: UploadFileInfo; fileList: Array<UploadFileInfo> }) {
uploadLoading.value = true
console.log(options.file.file)
if (options.file.file) {
const reader = new FileReader()
reader.onload = () => {
if (reader.result) {
jsonData.value = reader.result as string
importCheck()
}
else {
ms.error(`${t('common.failed')}: ${t('common.repeatLater')}`)
}
uploadLoading.value = false
}
reader.readAsText(options.file.file)
}
}
// 验证导入文件
function importCheck() {
importWarning.value = []
if (jsonData.value) {
try {
importObj.value = importJsonString(jsonData.value)
if (importObj.value) {
if (!importObj.value.isPassCheckMd5())
importWarning.value.push(t('apps.exportImport.fileModified'))
if (!importObj.value.isPassCheckConfigVersionOld())
importWarning.value.push(t('apps.exportImport.warnConfigFileLow'))
if (!importObj.value.isPassCheckConfigVersionNew())
importWarning.value.push(t('apps.exportImport.softwareVersionLow'))
// (暂时不做)此处可以判断,当前的配置文件是否存在的导入项目(不存在隐藏importItems里面的值)操作变量:importItems
// 通过了验证,打开弹窗
importRoundModalShow.value = !importRoundModalShow.value
// console.log(importObj.value.geticons())
}
}
catch (error) {
if (error instanceof ConfigVersionLowError) {
ms.error(t('apps.exportImport.errorConfigFileLow'))
console.error('The configuration file version is too low to be compatible')
}
else if (error instanceof FormatError) {
ms.error(t('apps.exportImport.errorConfigFileFormat'))
console.error('The format is incorrect and cannot be imported')
}
}
}
else {
ms.error(t('apps.exportImport.errorConfigFileFormat'))
}
}
// 开始导出
async function handleStartExport() {
loading.value = true
// console.log('要导出的项目', checkedItems.value)
// 获取软件版本号
const exportResult = exportJson(version.value)
if (checkedItems.value.includes('icons')) {
console.log('export icons ...')
const iconGroups = await exportIcons()
exportResult.addIconsData(iconGroups)
console.log('export icons finish', iconGroups)
}
// console.log('导出结果')
jsonData.value = exportResult.string()
exportResult.exportFile()
loading.value = false
exportRoundModalShow.value = false
// ms.success(t('common.success'))
}
// 开始导入
async function handleStartImport() {
loading.value = true
if (checkedItems.value.includes('icons')) {
console.log('export icons ...')
const errMsg = await importIcons()
if (errMsg !== null)
ms.success(`${t('common.failed')}:${errMsg}`)
}
loading.value = false
importRoundModalShow.value = false
ms.success(`${t('common.success')}, ${t('common.refreshPage')}`)
}
</script>
<template>
<div class="pt-2">
<NAlert type="info" :bordered="false">
<p>{{ $t('apps.exportImport.tip') }}</p>
</NAlert>
<div class="flex justify-center m-[50px]">
<div class="m-[10px]">
<NUpload
accept=".sun-panel.json,.sunpanel.json"
directory-dnd
:default-upload="false"
:show-file-list="false"
@change="handleFileChange"
>
<NButton type="info" size="large" :loading="uploadLoading">
<template #icon>
<SvgIcon icon="fa6:solid-file-import" />
</template>
{{ $t('apps.exportImport.import') }}
</NButton>
</NUpload>
</div>
<div class="m-[10px]">
<NButton type="info" size="large" @click="exportRoundModalShow = !exportRoundModalShow">
<template #icon>
<SvgIcon icon="fa6:solid-file-export" />
</template>
{{ $t('apps.exportImport.export') }}
</NButton>
</div>
</div>
<div class="flex justify-center">
<a href="https://hslr-s.github.io/sun-panel-tool-page/#/" target="_blank">{{ $t('apps.exportImport.transmuteStandard') }}</a>
</div>
<!-- 调试模式 -->
<div v-if="debug">
<NButton @click="importCheck">
检查导入
</NButton>
<!-- <NButton @click="exportJsonS">
导出JSON
</NButton> -->
<NButton @click="jsonData = ''">
清空导入数据
</NButton>
<NInput
v-model:value="jsonData"
type="textarea"
placeholder="基本的 Textarea"
/>
<div v-if="jsonData">
<h2>JSON 数据</h2>
<pre>{{ jsonData }}</pre>
</div>
</div>
<RoundCardModal v-model:show="importRoundModalShow" style="max-width: 400px;" :title=" $t('apps.exportImport.import')">
<div v-if="importWarning.length > 0">
<NAlert :title="$t('common.warning')" type="warning">
<div v-for="(text, index) in importWarning " :key="index">
{{ text }}
</div>
</NAlert>
</div>
<NDivider title-placement="left">
{{ $t('apps.exportImport.selectImportData') }}
</NDivider>
<NSpace justify="center" style="margin-top: 20px;">
<NCheckboxGroup v-model:value="checkedItems">
<NCheckbox v-if="importItems.includes('icons')" value="icons" :label="$t('apps.exportImport.moduleIcon')" />
<NCheckbox v-if="importItems.includes('style')" value="style" :label="$t('apps.exportImport.moduleStyle')" />
</NCheckboxGroup>
</NSpace>
<NSpace justify="center">
<div class="mt-[50px]">
<NButton type="success" :disabled="checkedItems.length === 0" :loading="loading" @click="handleStartImport">
{{ $t('common.continue') }}
</NButton>
</div>
</NSpace>
</RoundCardModal>
<RoundCardModal v-model:show="exportRoundModalShow" style="max-width: 400px;" :title=" $t('apps.exportImport.export')">
<NDivider title-placement="left">
{{ $t('apps.exportImport.selectExportData') }}
</NDivider>
<NSpace justify="center" style="margin-top: 20px;">
<NCheckboxGroup v-model:value="checkedItems">
<NCheckbox v-if="importItems.includes('icons')" value="icons" :label="$t('apps.exportImport.moduleIcon')" />
<NCheckbox v-if="importItems.includes('style')" value="style" :label="$t('apps.exportImport.moduleStyle')" />
</NCheckboxGroup>
</NSpace>
<NSpace justify="center">
<div class="mt-[50px]">
<NButton type="success" :disabled="checkedItems.length === 0" :loading="loading" @click="handleStartExport">
{{ $t('common.continue') }}
</NButton>
</div>
</NSpace>
</RoundCardModal>
</div>
</template>