调整界面

调整脚本
增加功能:备份、恢复、清空
This commit is contained in:
2026-04-03 14:05:18 +08:00
parent 4d0ce274e0
commit 51d607d970
15 changed files with 1494 additions and 679 deletions

View File

@@ -15,6 +15,7 @@
"jsencrypt": "^3.5.4",
"node-forge": "^1.3.1",
"pinia": "^3.0.3",
"pqs9100_tool": "file:..",
"socket.io-client": "^4.8.1",
"store2": "^2.14.4",
"vue": "^3.5.22",

View File

@@ -7,6 +7,10 @@ const ipcApiRoute = {
activateRecord: {
list: 'controller/activateRecord/list',
save: 'controller/activateRecord/save',
removeById: 'controller/activateRecord/removeById',
clear: 'controller/activateRecord/clear',
backup: 'controller/activateRecord/backup',
importBackup: 'controller/activateRecord/importBackup',
},
framework: {
checkForUpdater: 'controller/framework/checkForUpdater',

View File

@@ -1,144 +1,179 @@
<template>
<div class="activation-page">
<a-card v-if="hiddenKeys" style="margin-bottom: 10px;" title="RSA密钥配置">
<a-row :gutter="16">
<a-col :span="24">
<a-alert
message="注意:请妥善保管私钥,不要泄露给他人"
show-icon
style="margin-bottom: 16px;"
type="warning"
/>
</a-col>
<section v-if="hiddenKeys" class="activation-section">
<div class="section-title">
<h3>RSA 密钥配置</h3>
<p>仅在需要手动调整密钥时使用</p>
</div>
<a-col :span="12">
<a-form-item label="RSA公钥">
<a-textarea
v-model:value="rsaKeys.publicKey"
:readonly="readonly"
:rows="5"
placeholder="RSA公钥内容"
@blur="readonly = true"
@focus="readonly = false"
/>
<a-button
:disabled="!rsaKeys.publicKey"
ghost
size="small"
style="margin-top: 8px;"
type="primary"
@click="copyToClipboard(rsaKeys.publicKey)"
>
<a-alert
message="请妥善保管私钥,不要泄露给他人。"
show-icon
type="warning"
class="security-alert"
/>
<div class="form-grid">
<div class="field-block">
<label>RSA 公钥</label>
<a-textarea
v-model:value="rsaKeys.publicKey"
:readonly="readonly"
:rows="5"
placeholder="请输入 RSA 公钥"
@blur="readonly = true"
@focus="readonly = false"
/>
<div class="field-actions field-actions--single">
<a-button :disabled="!rsaKeys.publicKey" @click="copyToClipboard(rsaKeys.publicKey)">
复制公钥
</a-button>
</a-form-item>
</a-col>
</div>
</div>
<a-col :span="12">
<a-form-item label="RSA私钥">
<a-textarea
v-model:value="rsaKeys.privateKey"
:readonly="readonly"
:rows="5"
placeholder="RSA私钥内容"
@blur="readonly = true"
@focus="readonly = false"
/>
<a-button
:disabled="!rsaKeys.privateKey"
ghost
size="small"
style="margin-top: 8px;"
type="primary"
@click="copyToClipboard(rsaKeys.privateKey)"
>
<div class="field-block">
<label>RSA 私钥</label>
<a-textarea
v-model:value="rsaKeys.privateKey"
:readonly="readonly"
:rows="5"
placeholder="请输入 RSA 私钥"
@blur="readonly = true"
@focus="readonly = false"
/>
<div class="field-actions field-actions--single">
<a-button :disabled="!rsaKeys.privateKey" @click="copyToClipboard(rsaKeys.privateKey)">
复制私钥
</a-button>
</a-form-item>
</a-col>
</a-row>
</a-card>
<a-row :gutter="16">
<a-col :span="24">
<a-divider orientation="left" orientation-margin="0px">激活模块</a-divider>
<a-form :label-col="labelCol" layout="inline">
<a-form-item label="模拟式模块">
<a-switch v-model:checked="activationForm.simulate.permanently"/>
</a-form-item>
<a-form-item label="数字式模块">
<a-switch v-model:checked="activationForm.digital.permanently"/>
</a-form-item>
<a-form-item label="比对式模块">
<a-switch v-model:checked="activationForm.contrast.permanently"/>
</a-form-item>
</a-form>
</a-col>
<a-col :span="24">
<a-divider orientation="left" orientation-margin="0px">设备申请码</a-divider>
<a-form-item>
<a-textarea
v-model:value="activationForm.applicationCode"
:rows="3"
allow-clear
placeholder="请输入设备申请码"
/>
</a-form-item>
</a-col>
</div>
</div>
</div>
</section>
<a-col :span="24">
<a-divider orientation="left" orientation-margin="0px">设备激活码</a-divider>
<a-form-item>
<a-textarea
v-model:value="activationCode"
:rows="3"
placeholder="生成的激活码将显示在这里"
readonly
/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-divider orientation="left" orientation-margin="0px">备注</a-divider>
<a-form-item>
<a-textarea
v-model:value="activationForm.remark"
allow-clear
placeholder="添加备注内容"
maxlength="120"
show-count
/>
</a-form-item>
</a-col>
<section class="activation-section">
<div class="section-title">
<h3>激活模块</h3>
<p>至少选择一个模块</p>
</div>
<a-col :span="24">
<a-space>
<a-button
:disabled="!activationForm.applicationCode.trim()"
:loading="generating"
type="primary"
@click="generateActivationCode"
>
生成激活码
</a-button>
<div class="module-grid">
<div class="module-item" :class="{ active: activationForm.simulate.permanently }">
<div class="module-copy">
<strong>模拟式模块</strong>
<span>用于模拟式相关功能授权</span>
</div>
<a-switch v-model:checked="activationForm.simulate.permanently" />
</div>
<a-button
:disabled="!activationCode"
@click="copyToClipboard(activationCode)"
>
复制激活码
</a-button>
</a-space>
</a-col>
</a-row>
<div class="module-item" :class="{ active: activationForm.digital.permanently }">
<div class="module-copy">
<strong>数字式模块</strong>
<span>用于数字式相关功能授权</span>
</div>
<a-switch v-model:checked="activationForm.digital.permanently" />
</div>
<div class="module-item" :class="{ active: activationForm.contrast.permanently }">
<div class="module-copy">
<strong>比对式模块</strong>
<span>用于比对式相关功能授权</span>
</div>
<a-switch v-model:checked="activationForm.contrast.permanently" />
</div>
</div>
</section>
<section class="activation-section">
<div class="section-title">
<h3>激活信息</h3>
</div>
<div class="form-rows">
<div class="form-row form-row--compact">
<div class="field-block">
<label>申请方</label>
<p class="field-tip">用于标记本次激活记录归属</p>
<a-input
v-model:value="activationForm.applicant"
allow-clear
placeholder="请输入申请方"
/>
</div>
<div class="field-block">
<label>备注</label>
<p class="field-tip">记录额外说明例如客户或交付备注</p>
<a-textarea
v-model:value="activationForm.remark"
allow-clear
placeholder="添加备注内容"
maxlength="120"
show-count
:rows="4"
/>
</div>
</div>
<div class="form-row">
<div class="field-block field-block--code">
<label>设备申请码</label>
<p class="field-tip">粘贴设备侧生成的申请码系统会先解密校验</p>
<a-textarea
v-model:value="activationForm.applicationCode"
:rows="8"
allow-clear
class="code-textarea"
placeholder="请输入设备申请码"
/>
</div>
<div class="field-block field-block--code">
<div class="field-head">
<div>
<label>设备激活码</label>
<p class="field-tip">生成后可直接复制</p>
</div>
<div class="field-actions">
<a-button
:disabled="!activationForm.applicationCode.trim()"
:loading="generating"
type="primary"
@click="generateActivationCode"
>
生成激活码
</a-button>
<a-button
:disabled="!activationCode"
@click="copyToClipboard(activationCode)"
>
复制激活码
</a-button>
</div>
</div>
<a-textarea
v-model:value="activationCode"
:rows="8"
class="code-textarea"
placeholder="生成的激活码将显示在这里"
readonly
/>
</div>
</div>
</div>
</section>
</div>
</template>
<script lang="ts" setup>
import {ref} from 'vue'
import {message} from 'ant-design-vue'
import { ref, watch } from 'vue'
import { message } from 'ant-design-vue'
import dayjs from 'dayjs'
import rsa from '@/utils/rsa'
import {Activate} from "@/views/activate/index";
import dayjs from "dayjs";
import { Activate } from '@/views/activate/index'
const RSA_CAN_EDIT = import.meta.env.VITE_RSA_CAN_EDIT === 'true'
const rsaKeys = ref({
publicKey: rsa.publicKey,
privateKey: rsa.privateKey
@@ -146,93 +181,92 @@ const rsaKeys = ref({
const hiddenKeys = ref(RSA_CAN_EDIT)
const readonly = ref(true)
const activationForm = ref({
applicant: '',
applicationCode: '',
simulate: {permanently: false},
digital: {permanently: false},
contrast: {permanently: false},
remark:'',
simulate: { permanently: false },
digital: { permanently: false },
contrast: { permanently: false },
remark: ''
})
const activationCode = ref('')
const macAddress = ref('')
const generating = ref(false)
const labelCol = {style: {width: '120px'}}
// 生成激活码
const clearGeneratedResult = () => {
activationCode.value = ''
macAddress.value = ''
}
watch(
() => [
activationForm.value.applicationCode,
activationForm.value.simulate.permanently,
activationForm.value.digital.permanently,
activationForm.value.contrast.permanently
],
clearGeneratedResult
)
const generateActivationCode = () => {
if (!rsaKeys.value.publicKey || !rsaKeys.value.privateKey) {
message.error('请先配置RSA密钥')
message.error('请先配置 RSA 密钥')
return
}
if (!activationForm.value.applicationCode.trim()) {
message.error('请输入设备申请码')
return
}
let simulate = activationForm.value.simulate as Activate.ActivateModule
let digital = activationForm.value.digital as Activate.ActivateModule
let contrast = activationForm.value.contrast as Activate.ActivateModule
const simulate = activationForm.value.simulate as Activate.ActivateModule
const digital = activationForm.value.digital as Activate.ActivateModule
const contrast = activationForm.value.contrast as Activate.ActivateModule
if (!simulate.permanently && !digital.permanently && !contrast.permanently) {
message.error('请至少选择一种激活模块')
return
}
generating.value = true
let applicationCodePlaintext
try {
const applicationCode = rsa.decrypt(activationForm.value.applicationCode)
applicationCodePlaintext = JSON.parse(applicationCode)
} catch (e) {
console.error(e)
} catch (error) {
console.error(error)
}
if (!applicationCodePlaintext) {
if (!applicationCodePlaintext?.macAddress) {
generating.value = false
message.error('无效的设备申请码')
return
}
if (!applicationCodePlaintext.macAddress) {
message.error('无效的设备申请码')
return
const activationCodePlaintext: Activate.ActivationCodePlaintext = {
macAddress: applicationCodePlaintext.macAddress,
simulate: { permanently: simulate.permanently ? 1 : 0 },
digital: { permanently: digital.permanently ? 1 : 0 },
contrast: { permanently: contrast.permanently ? 1 : 0 }
}
let activationCodePlaintext: Activate.ActivationCodePlaintext = {
macAddress: '',
simulate: {permanently: 0},
digital: {permanently: 0},
contrast: {permanently: 0}
}
activationCodePlaintext.macAddress = applicationCodePlaintext.macAddress
macAddress.value = applicationCodePlaintext.macAddress
if (simulate.permanently) {
activationCodePlaintext.simulate = {
permanently: 1
}
}
if (digital.permanently) {
activationCodePlaintext.digital = {
permanently: 1
}
}
if (contrast.permanently) {
activationCodePlaintext.contrast = {
permanently: 1
}
}
const data = JSON.stringify(activationCodePlaintext)
// message.info(data)
try {
setTimeout(() => {
activationCode.value = rsa.encrypt(data)
activationCode.value = rsa.encrypt(JSON.stringify(activationCodePlaintext))
generating.value = false
}, 1000)
} catch (e) {
console.error(e)
} catch (error) {
console.error(error)
generating.value = false
message.error('生成激活码失败')
}
}
// 复制到剪贴板
const copyToClipboard = (text: string) => {
if (!text) {
message.warning('没有内容可复制')
message.warning('没有可复制的内容')
return
}
@@ -247,10 +281,12 @@ const getData = () => {
if (!activationCode.value) {
return
}
let simulate = activationForm.value.simulate as Activate.ActivateModule
let digital = activationForm.value.digital as Activate.ActivateModule
let contrast = activationForm.value.contrast as Activate.ActivateModule
let modules = []
const simulate = activationForm.value.simulate as Activate.ActivateModule
const digital = activationForm.value.digital as Activate.ActivateModule
const contrast = activationForm.value.contrast as Activate.ActivateModule
const modules: string[] = []
if (simulate.permanently) {
modules.push('simulate')
}
@@ -260,40 +296,230 @@ const getData = () => {
if (contrast.permanently) {
modules.push('contrast')
}
return {
applicant: activationForm.value.applicant.trim(),
macAddress: macAddress.value,
applicationCode: activationForm.value.applicationCode,
modules: modules,
modules,
activationCode: activationCode.value,
createTime: dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss'),
remark: activationForm.value.remark
remark: activationForm.value.remark
}
}
defineExpose( {getData})
defineExpose({ getData })
</script>
<style lang="less" scoped>
.activation-page {
text-align: center;
height: 100%;
background-color: #fff;
border-radius: 8px;
display: flex;
flex-direction: column;
gap: 12px;
}
:deep(textarea) {
font-family: consolas, monospace;
resize: none;
.activation-section {
background: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 14px;
padding: 16px;
}
.section-title {
margin-bottom: 12px;
}
.section-title h3 {
margin: 0;
font-size: 15px;
font-weight: 600;
color: #1f2329;
}
.section-title p {
margin: 4px 0 0;
font-size: 12px;
color: #8c8c8c;
}
.security-alert {
margin-bottom: 12px;
}
.module-grid,
.form-grid,
.form-row {
display: grid;
gap: 12px;
}
.module-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.form-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
align-items: start;
}
.form-rows {
display: flex;
flex-direction: column;
gap: 12px;
}
.form-row {
grid-template-columns: repeat(2, minmax(0, 1fr));
align-items: start;
}
.form-row--compact .field-block {
min-height: 0;
}
.module-item,
.field-block {
border: 1px solid #e5e7eb;
border-radius: 12px;
background: #ffffff;
}
.module-item {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
padding: 14px 16px;
}
.module-item.active {
border-color: #91caff;
background: #f7fbff;
}
.module-copy {
display: flex;
flex-direction: column;
gap: 4px;
}
.module-copy strong {
font-size: 14px;
font-weight: 600;
color: #1f2329;
}
.module-copy span {
font-size: 12px;
color: #8c8c8c;
}
.field-block {
align-self: start;
padding: 14px;
}
.field-head {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 12px;
margin-bottom: 8px;
}
.field-block label {
display: block;
margin-bottom: 6px;
font-size: 14px;
font-weight: 600;
color: #1f2329;
}
.field-tip {
margin: 0 0 8px;
font-size: 12px;
line-height: 1.5;
color: #8c8c8c;
}
.field-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.field-actions--single {
margin-top: 10px;
}
.field-block--code :deep(.ant-input-textarea textarea) {
min-height: 180px;
font-family: "Consolas", "Courier New", monospace;
}
:deep(.ant-input),
:deep(.ant-input-affix-wrapper),
:deep(.ant-input-textarea textarea) {
border-radius: 10px;
border-color: #d9d9d9;
box-shadow: none;
}
:deep(input.ant-input),
:deep(.ant-input-affix-wrapper) {
min-height: 40px;
}
:deep(.ant-input-affix-wrapper) {
display: flex;
align-items: center;
padding-top: 0;
padding-bottom: 0;
}
:deep(.ant-input-affix-wrapper input.ant-input) {
min-height: auto;
padding: 0;
border: 0;
box-shadow: none;
background: transparent;
}
:deep(.ant-input:hover),
:deep(.ant-input:focus),
:deep(.ant-input-affix-wrapper:hover),
:deep(.ant-input-affix-wrapper-focused),
:deep(.ant-input-textarea textarea:hover),
:deep(.ant-input-textarea textarea:focus) {
border-color: #4096ff;
box-shadow: none;
}
:deep(.ant-input-textarea textarea) {
resize: none;
}
:deep(.ant-btn) {
height: 36px;
border-radius: 10px;
}
@media (max-width: 640px) {
.module-grid,
.form-row {
grid-template-columns: 1fr;
}
:deep(.ant-form-item-label) {
font-weight: bold;
.field-head {
flex-direction: column;
}
:deep(.ant-card-body) {
padding: 18px;
.field-actions {
width: 100%;
}
:deep(.ant-card) {
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.field-actions :deep(.ant-btn) {
flex: 1;
}
}
</style>

View File

@@ -3,12 +3,23 @@
<a-card style="margin-bottom: 5px" title="激活记录">
<a-form ref="searchFormRef" :model="searchForm" layout="inline">
<a-form-item label="mac地址" name="macAddress">
<a-input v-model:value="searchForm.macAddress" allow-clear autocomplete="off" placeholder="请输入mac地址"
style="width: 200px"/>
<a-input
v-model:value="searchForm.macAddress"
allow-clear
autocomplete="off"
placeholder="请输入mac地址"
style="width: 200px"
/>
</a-form-item>
<a-form-item label="激活模块" name="modules">
<a-select v-model:value="searchForm.modules" :max-tag-count="1" allow-clear mode="multiple"
placeholder="全部" style="width: 200px">
<a-select
v-model:value="searchForm.modules"
:max-tag-count="1"
allow-clear
mode="multiple"
placeholder="全部"
style="width: 200px"
>
<a-select-option value="simulate">模拟式模块</a-select-option>
<a-select-option value="digital">数字式模块</a-select-option>
<a-select-option value="contrast">比对式模块</a-select-option>
@@ -33,37 +44,87 @@
</a-form>
</a-card>
<a-card>
<a-button style="margin-bottom: 10px;" type="primary" @click="visible = true">
<template #icon>
<file-protect-outlined/>
</template>
设备激活
</a-button>
<a-table :columns="columns" :dataSource="dataSource" :loading="loading" :pagination="pagination" bordered
size="small">
<a-space style="margin-bottom: 10px;" wrap>
<a-button type="primary" @click="visible = true">
<template #icon>
<file-protect-outlined/>
</template>
设备激活
</a-button>
<a-button @click="backupData">
<template #icon>
<download-outlined/>
</template>
备份数据
</a-button>
<a-button @click="confirmImportBackup">
<template #icon>
<upload-outlined/>
</template>
导入备份
</a-button>
<a-button danger @click="confirmClearData">
<template #icon>
<delete-outlined/>
</template>
清空数据
</a-button>
</a-space>
<a-table
:columns="columns"
:dataSource="dataSource"
:loading="loading"
:pagination="pagination"
:rowKey="(record: any) => record.id"
bordered
size="small"
>
<template #emptyText>
<a-empty description="暂无记录"/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'module'">
<a-tag color="green" v-for="m in record.module.split(',')">
<a-tag color="green" v-for="m in record.module.split(',')" :key="m">
{{ m === 'simulate' ? '模拟式模块' : m === 'digital' ? '数字式模块' : '比对式模块' }}
</a-tag>
</template>
<template v-else-if="column.key === 'action'">
<a-button type="primary" size="small" @click="copyToClipboard(record.activationCode)">
<template #icon>
<copy-outlined />
</template>
复制激活码
</a-button>
<a-space>
<a-button type="primary" size="small" @click="copyToClipboard(record.activationCode)">
<template #icon>
<copy-outlined />
</template>
复制激活码
</a-button>
<a-popconfirm
title="确认删除这条记录吗?"
ok-text="删除"
cancel-text="取消"
@confirm="removeRecord(record.id)"
>
<a-button danger size="small">
<template #icon>
<delete-outlined />
</template>
删除
</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
</a-card>
</div>
<a-modal v-model:visible="visible" :keyboard="false" :mask-closable="false" centered destroy-on-close width="70%">
<a-modal
v-model:visible="visible"
:keyboard="false"
:mask-closable="false"
centered
destroy-on-close
width="940px"
wrapClassName="apple-activation-modal"
>
<template #title>
<file-protect-outlined/>
设备激活
@@ -79,12 +140,11 @@
</template>
<script lang="ts" setup>
import {FormInstance, message} from "ant-design-vue";
import {onMounted, ref} from "vue";
import { FormInstance, Modal, message } from "ant-design-vue";
import { onMounted, ref } from "vue";
import ActiveForm from "@/views/activate/ActiveForm.vue";
import {ipc} from "@/utils/ipcRenderer";
import {ipcApiRoute} from "@/api";
import { ipc } from "@/utils/ipcRenderer";
import { ipcApiRoute } from "@/api";
const searchFormRef = ref<FormInstance>();
const activateFormRef = ref();
@@ -94,15 +154,20 @@ const searchForm = ref({
})
const loading = ref(false)
const visible = ref(false)
const dataSource = ref<any []>([])
const dataSource = ref<any[]>([])
const pagination = ref({
total: 0,
pageSize: 10,
// current: 1,
showTotal: (total: number) => `${total}`
})
const columns = [
{
title: '申请方',
dataIndex: 'applicant',
key: 'applicant',
align: 'center',
width: 120,
},
{
title: 'mac地址',
dataIndex: 'macAddress',
@@ -116,14 +181,14 @@ const columns = [
key: 'applicationCode',
align: 'center',
ellipsis: true,
width: 100,
width: 120,
},
{
title: '激活模块',
dataIndex: 'module',
key: 'module',
align: 'center',
width: 250,
width: 220,
},
{
title: '激活码',
@@ -131,7 +196,7 @@ const columns = [
key: 'activationCode',
align: 'center',
ellipsis: true,
width: 100,
width: 120,
},
{
title: '生成时间',
@@ -145,13 +210,13 @@ const columns = [
dataIndex: 'remark',
key: 'remark',
align: 'center',
width: 100,
width: 120,
},
{
title: '操作',
key: 'action',
align: 'center',
width: 120,
width: 220,
},
]
@@ -163,6 +228,7 @@ const resetForm = () => {
onMounted(() => {
resetForm()
})
const query = () => {
loading.value = true
const params: any = {
@@ -171,7 +237,6 @@ const query = () => {
}
setTimeout(() => {
ipc.invoke(ipcApiRoute.activateRecord.list, params).then((res: any[]) => {
console.log(res)
dataSource.value = res
pagination.value.total = dataSource.value.length
pagination.value.current = 1
@@ -180,8 +245,9 @@ const query = () => {
console.error(err)
loading.value = false
})
}, 500)
}, 300)
}
const save = () => {
const data = activateFormRef.value.getData()
if (!data) {
@@ -196,9 +262,79 @@ const save = () => {
console.error(err)
message.error('保存失败')
})
}
// 复制到剪贴板
const removeRecord = (id: number) => {
ipc.invoke(ipcApiRoute.activateRecord.removeById, { id }).then(() => {
message.success('删除成功')
query()
}).catch((err: any) => {
console.error(err)
message.error('删除失败')
})
}
const confirmClearData = () => {
Modal.confirm({
title: '确认清空全部数据吗?',
content: '清空后当前本地激活记录将被删除,请先确认是否需要备份。',
okText: '清空',
cancelText: '取消',
okButtonProps: {
danger: true
},
onOk: () => clearData()
})
}
const clearData = () => {
ipc.invoke(ipcApiRoute.activateRecord.clear).then(() => {
message.success('已清空本地数据')
query()
}).catch((err: any) => {
console.error(err)
message.error('清空失败')
})
}
const backupData = () => {
ipc.invoke(ipcApiRoute.activateRecord.backup).then((res: any) => {
if (res?.canceled) {
return
}
message.success(`备份成功,共导出 ${res.count} 条记录`)
}).catch((err: any) => {
console.error(err)
message.error('备份失败')
})
}
const confirmImportBackup = () => {
Modal.confirm({
title: '确认导入备份吗?',
content: '导入备份会先清空当前本地数据,再恢复备份内容。',
okText: '导入',
cancelText: '取消',
okButtonProps: {
danger: true
},
onOk: () => importBackup()
})
}
const importBackup = () => {
ipc.invoke(ipcApiRoute.activateRecord.importBackup).then((res: any) => {
if (res?.canceled) {
return
}
message.success(`导入成功,共恢复 ${res.count} 条记录`)
query()
}).catch((err: any) => {
console.error(err)
message.error('导入失败,请检查备份文件格式')
})
}
const copyToClipboard = (text: string) => {
if (!text) {
message.warning('没有内容可复制')
@@ -213,5 +349,52 @@ const copyToClipboard = (text: string) => {
}
</script>
<style lang="less" scoped>
:deep(.apple-activation-modal .ant-modal-content) {
border-radius: 30px;
overflow: hidden;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 28px 60px rgba(15, 23, 42, 0.22);
background:
linear-gradient(180deg, rgba(250, 250, 252, 0.98), rgba(243, 245, 247, 0.98));
}
:deep(.apple-activation-modal .ant-modal-header) {
padding: 18px 24px 14px;
background: transparent;
border-bottom: 1px solid rgba(15, 23, 42, 0.06);
}
:deep(.apple-activation-modal .ant-modal-title) {
font-size: 18px;
font-weight: 650;
color: #111827;
}
:deep(.apple-activation-modal .ant-modal-body) {
padding: 12px 14px 8px;
background: transparent;
max-height: 76vh;
overflow-y: auto;
}
:deep(.apple-activation-modal .ant-modal-footer) {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 12px 20px 18px;
border-top: 1px solid rgba(15, 23, 42, 0.06);
background: transparent;
}
:deep(.apple-activation-modal .ant-btn) {
min-width: 88px;
height: 38px;
border-radius: 12px;
}
:deep(.apple-activation-modal .ant-btn-primary) {
border: none;
background: linear-gradient(180deg, #0a84ff, #0071e3);
box-shadow: 0 10px 24px rgba(0, 113, 227, 0.22);
}
</style>