361 lines
11 KiB
Vue
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>
|