V 3.3.9(2025.05.29)
1. 生图包适配,修复部分任务无法获取图片的问题。 2. 新增聚合推文原创重置功能,直接重置GPT提示词、合并提示词、出图等。 3. 优化加载界面,提升用户体验。 4. 优化主页显示,优化UI界面,提升用户体验。
This commit is contained in:
Vendored
-6
@@ -13,9 +13,7 @@ declare module 'vue' {
|
||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||
NCode: typeof import('naive-ui')['NCode']
|
||||
NColorPicker: typeof import('naive-ui')['NColorPicker']
|
||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||
NDataTable: typeof import('naive-ui')['NDataTable']
|
||||
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
|
||||
NDivider: typeof import('naive-ui')['NDivider']
|
||||
NDropdown: typeof import('naive-ui')['NDropdown']
|
||||
NDynamicInput: typeof import('naive-ui')['NDynamicInput']
|
||||
@@ -32,10 +30,6 @@ declare module 'vue' {
|
||||
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
||||
NLog: typeof import('naive-ui')['NLog']
|
||||
NMenu: typeof import('naive-ui')['NMenu']
|
||||
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||
NModal: typeof import('naive-ui')['NModal']
|
||||
NModalProvider: typeof import('naive-ui')['NModalProvider']
|
||||
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
||||
NPopover: typeof import('naive-ui')['NPopover']
|
||||
NProgress: typeof import('naive-ui')['NProgress']
|
||||
NSelect: typeof import('naive-ui')['NSelect']
|
||||
|
||||
+31
-40
@@ -7,7 +7,9 @@
|
||||
<n-modal-provider>
|
||||
<n-dialog-provider>
|
||||
<n-notification-provider>
|
||||
<RouterView></RouterView>
|
||||
<LoadingComponent v-if="loading" @loading-complete="onComplete" fullScreen>
|
||||
</LoadingComponent>
|
||||
<RouterView v-else></RouterView>
|
||||
</n-notification-provider>
|
||||
</n-dialog-provider>
|
||||
</n-modal-provider>
|
||||
@@ -35,7 +37,7 @@
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { defineComponent, onMounted } from 'vue'
|
||||
import hljs from 'highlight.js/lib/core'
|
||||
import javascript from 'highlight.js/lib/languages/javascript'
|
||||
@@ -51,49 +53,38 @@ import {
|
||||
} from 'naive-ui'
|
||||
import { useSoftwareStore } from '../../stores/software'
|
||||
import { SoftColor } from '../../define/enum/softwareEnum'
|
||||
import LoadingComponent from './components/Home/LoadingComponent.vue'
|
||||
|
||||
const loading = ref(true)
|
||||
|
||||
const onComplete = () => {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
hljs.registerLanguage('javascript', javascript)
|
||||
export default defineComponent({
|
||||
components: {
|
||||
NConfigProvider,
|
||||
NDialogProvider,
|
||||
NMessageProvider,
|
||||
NNotificationProvider,
|
||||
NModalProvider,
|
||||
NSpin,
|
||||
NModal
|
||||
},
|
||||
setup() {
|
||||
let softwareStore = useSoftwareStore()
|
||||
|
||||
onMounted(async () => {
|
||||
softwareStore.SoftColor = SoftColor
|
||||
window.api.getSettingDafultData(async (value) => {
|
||||
await window.darkMode.toggle(value.theme)
|
||||
softwareStore.globalSetting = value
|
||||
})
|
||||
let software = await window.setting.GetSoftwareSetting()
|
||||
if (software.code == 0) {
|
||||
throw new Error('初始化信息错误: ' + software.message)
|
||||
}
|
||||
if (software.data == null) {
|
||||
throw new Error('初始化信息错误: 未获取到数据')
|
||||
}
|
||||
if (software.data.length == 0) {
|
||||
throw new Error('初始化信息错误: 未获取到数据')
|
||||
}
|
||||
softwareStore.softWare = software.data
|
||||
console.log(softwareStore.softWare)
|
||||
await window.darkMode.toggle(softwareStore.softWare.theme)
|
||||
})
|
||||
let softwareStore = useSoftwareStore()
|
||||
|
||||
return {
|
||||
javascript,
|
||||
hljs,
|
||||
darkTheme,
|
||||
softwareStore
|
||||
}
|
||||
onMounted(async () => {
|
||||
loading.value = true
|
||||
softwareStore.SoftColor = SoftColor
|
||||
window.api.getSettingDafultData(async (value) => {
|
||||
console.log(value.theme)
|
||||
await window.darkMode.toggle(value.theme)
|
||||
softwareStore.globalSetting = value
|
||||
})
|
||||
let software = await window.setting.GetSoftwareSetting()
|
||||
if (software.code == 0) {
|
||||
throw new Error('初始化信息错误: ' + software.message)
|
||||
}
|
||||
if (software.data == null) {
|
||||
throw new Error('初始化信息错误: 未获取到数据')
|
||||
}
|
||||
if (software.data.length == 0) {
|
||||
throw new Error('初始化信息错误: 未获取到数据')
|
||||
}
|
||||
softwareStore.softWare = software.data
|
||||
console.log(softwareStore.softWare)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 91 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 895 KiB |
@@ -90,6 +90,7 @@ import {
|
||||
import { useRouter } from 'vue-router'
|
||||
import { DEFINE_STRING } from '../../../../../define/define_string'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { TimeDelay } from '@/define/Tools/time'
|
||||
let softwareStore = useSoftwareStore()
|
||||
let reverseManageStore = useReverseManageStore()
|
||||
let dialog = useDialog()
|
||||
@@ -357,6 +358,94 @@ async function ButtonSelect(key) {
|
||||
}
|
||||
}
|
||||
|
||||
// 重置所有的数据
|
||||
async function resetALLData() {
|
||||
let da = dialog.warning({
|
||||
title: '重置所有数据提示',
|
||||
content: `即将开始重置所有的数据,包括GPT提示词、合并提示词、生成图片等,当前的操作不可逆,是否继续?`,
|
||||
positiveText: '继续',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
da?.destroy()
|
||||
softwareStore.spin.spinning = true
|
||||
softwareStore.spin.tip = '正在重置GPT提示词数据,请稍后。。。'
|
||||
|
||||
let res = await window.book.ResetGptReverseData(
|
||||
reverseManageStore.selectBookTask.id,
|
||||
OperateBookType.BOOKTASK,
|
||||
BookType.ORIGINAL
|
||||
)
|
||||
softwareStore.spin.spinning = false
|
||||
if (res.code == 1) {
|
||||
// 删掉所有的数据
|
||||
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
|
||||
reverseManageStore.selectBookTaskDetail[i].gptPrompt = ''
|
||||
}
|
||||
}
|
||||
|
||||
message.success('重置GPT提示词成功')
|
||||
await TimeDelay(500)
|
||||
|
||||
softwareStore.spin.tip = '正在重置合并提示词数据,请稍后。。。'
|
||||
|
||||
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
|
||||
const element = reverseManageStore.selectBookTaskDetail[i]
|
||||
let res = await window.db.UpdateBookTaskDetailData(element.id, {
|
||||
prompt: ''
|
||||
})
|
||||
if (res.code == 1) {
|
||||
let findIndex = reverseManageStore.selectBookTaskDetail.findIndex(
|
||||
(item) => item.id == element.id
|
||||
)
|
||||
if (findIndex != -1) {
|
||||
reverseManageStore.selectBookTaskDetail[findIndex].prompt = ''
|
||||
}
|
||||
} else {
|
||||
message.error(res.message)
|
||||
}
|
||||
}
|
||||
message.success('重置合并提示词成功')
|
||||
await TimeDelay(500)
|
||||
|
||||
softwareStore.spin.tip = '正在重置生成图片数据,请稍后。。。'
|
||||
|
||||
res = await window.book.ResetGenerateImage(
|
||||
reverseManageStore.selectBookTask.id,
|
||||
OperateBookType.BOOKTASK,
|
||||
false
|
||||
)
|
||||
softwareStore.spin.spinning = false
|
||||
if (res.code == 1) {
|
||||
for (let i = 0; i < res.data.length; i++) {
|
||||
const element = res.data[i]
|
||||
let findIndex = reverseManageStore.selectBookTaskDetail.findIndex(
|
||||
(item) => item.id == element.id
|
||||
)
|
||||
if (findIndex != -1) {
|
||||
reverseManageStore.selectBookTaskDetail[findIndex].mjMessage = undefined
|
||||
reverseManageStore.selectBookTaskDetail[findIndex].outImagePath = ''
|
||||
reverseManageStore.selectBookTaskDetail[findIndex].subImagePath = []
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message.error(res.message)
|
||||
return
|
||||
}
|
||||
message.success('重置生成图片数据成功')
|
||||
await TimeDelay(500)
|
||||
softwareStore.spin.spinning = false
|
||||
message.success('重置所有数据成功')
|
||||
} catch (error) {
|
||||
softwareStore.spin.spinning = false
|
||||
message.error('重置所有数据失败,' + error.message)
|
||||
} finally {
|
||||
softwareStore.spin.spinning = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取所有的GPT提示词
|
||||
async function GetPromptAll() {
|
||||
let da = dialog.warning({
|
||||
|
||||
@@ -1,40 +1,38 @@
|
||||
<template>
|
||||
<n-spin :show="show">
|
||||
<n-space vertical>
|
||||
<n-layout has-sider style="height: 100vh; position: relative">
|
||||
<n-layout-sider
|
||||
bordered
|
||||
collapse-mode="width"
|
||||
:collapsed-width="64"
|
||||
:width="220"
|
||||
<n-space vertical>
|
||||
<n-layout has-sider style="height: 100vh; position: relative">
|
||||
<n-layout-sider
|
||||
bordered
|
||||
collapse-mode="width"
|
||||
:collapsed-width="64"
|
||||
:width="220"
|
||||
:collapsed="collapsed"
|
||||
show-trigger
|
||||
@collapse="collapsed = true"
|
||||
@expand="collapsed = false"
|
||||
style="position: relative"
|
||||
>
|
||||
<n-menu
|
||||
:collapsed="collapsed"
|
||||
show-trigger
|
||||
@collapse="collapsed = true"
|
||||
@expand="collapsed = false"
|
||||
style="position: relative"
|
||||
:collapsed-width="64"
|
||||
:collapsed-icon-size="22"
|
||||
:options="menuOptions"
|
||||
:render-icon="renderMenuIcon"
|
||||
:expand-icon="expandIcon"
|
||||
default-value="mainHome"
|
||||
>
|
||||
<n-menu
|
||||
:collapsed="collapsed"
|
||||
:collapsed-width="64"
|
||||
:collapsed-icon-size="22"
|
||||
:options="menuOptions"
|
||||
:render-icon="renderMenuIcon"
|
||||
:expand-icon="expandIcon"
|
||||
>
|
||||
</n-menu>
|
||||
</n-layout-sider>
|
||||
<n-layout-content content-style="padding: 5px 5px 5px 10px; height:100%">
|
||||
<!-- <Setting></Setting> -->
|
||||
<router-view></router-view>
|
||||
</n-layout-content>
|
||||
</n-layout>
|
||||
</n-space>
|
||||
<template #description> 正在加载软件授权信息。。。 </template>
|
||||
</n-spin>
|
||||
</n-menu>
|
||||
</n-layout-sider>
|
||||
<n-layout-content content-style="padding: 5px 5px 5px 10px; height:100%">
|
||||
<!-- <Setting></Setting> -->
|
||||
<router-view></router-view>
|
||||
</n-layout-content>
|
||||
</n-layout>
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, h, onMounted, defineComponent, toRaw, computed } from 'vue'
|
||||
import { ref, h, onMounted, toRaw, computed } from 'vue'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import {
|
||||
useDialog,
|
||||
@@ -45,12 +43,12 @@ import {
|
||||
NLayoutContent,
|
||||
NIcon,
|
||||
useNotification,
|
||||
useMessage,
|
||||
NSpin
|
||||
useMessage
|
||||
} from 'naive-ui'
|
||||
|
||||
import {
|
||||
CaretDownOutline,
|
||||
HomeOutline,
|
||||
PaperPlaneOutline,
|
||||
SettingsOutline,
|
||||
DuplicateOutline,
|
||||
@@ -58,7 +56,7 @@ import {
|
||||
RadioOutline,
|
||||
BookOutline
|
||||
} from '@vicons/ionicons5'
|
||||
import CheckMachineId from '../Components/CheckMachineId.vue'
|
||||
|
||||
import { DEFINE_STRING } from '../../../../define/define_string'
|
||||
import { MD5 } from 'crypto-js'
|
||||
import InputDialogContent from '../Original/Components/InputDialogContent.vue'
|
||||
@@ -66,16 +64,11 @@ import APIIcon from '../Icon/APIIcon.vue'
|
||||
import BackTaskIcon from '../Icon/BackTaskIcon.vue'
|
||||
import BackTask from '@/renderer/src/components/BackTask/BackTask.vue'
|
||||
import { useSystemStore } from '../../../../stores/system'
|
||||
import { TimeDelay } from '../../../../define/Tools/time'
|
||||
import { BookBackTaskStatus } from '../../../../define/enum/bookEnum'
|
||||
|
||||
let collapsed = ref(false)
|
||||
let dialog = useDialog()
|
||||
let machineRef = ref()
|
||||
let message = useMessage()
|
||||
let notification = useNotification()
|
||||
let show = ref(true)
|
||||
const systemStore = useSystemStore()
|
||||
|
||||
let key_down_ref = ref(null)
|
||||
let showMenu = ref(true)
|
||||
@@ -87,6 +80,7 @@ function renderMenuIcon(option) {
|
||||
if (option.key == 'sdoriginal') return h(NIcon, null, { default: () => h(PaperPlaneOutline) })
|
||||
if (option.key == 'setting') return h(NIcon, null, { default: () => h(SettingsOutline) })
|
||||
if (option.key == 'gptCopywriting') return h(NIcon, null, { default: () => h(BookOutline) })
|
||||
if (option.key == 'mainHome') return h(NIcon, null, { default: () => h(HomeOutline) })
|
||||
if (option.key == 'book_management') return h(NIcon, null, { default: () => h(GridOutline) })
|
||||
if (option.key == 'lai_api') return h(NIcon, null, { default: () => h(APIIcon) })
|
||||
if (option.key == 'backward_matrix') return h(NIcon, null, { default: () => h(DuplicateOutline) })
|
||||
@@ -103,128 +97,7 @@ function renderMenuIcon(option) {
|
||||
if (option.key == 'TTS_Services') return h(NIcon, null, { default: () => h(RadioOutline) })
|
||||
}
|
||||
|
||||
// 启动后台任务
|
||||
async function StartBackTask(isGiveUp) {
|
||||
let startRes = await window.task.StartBackTask(isGiveUp)
|
||||
if (startRes.code == 0) {
|
||||
message.error(startRes.message)
|
||||
} else {
|
||||
message.success(startRes.message)
|
||||
}
|
||||
}
|
||||
|
||||
/** 判断是不是还是后台任务,用户确认是不是丢弃 */
|
||||
async function CheckWaitTask() {
|
||||
await TimeDelay(1000)
|
||||
let getWaitTaskCountRes = await window.task.GetAllStatusTaskCount([
|
||||
BookBackTaskStatus.WAIT,
|
||||
BookBackTaskStatus.RECONNECT
|
||||
])
|
||||
// 报错,默认丢弃
|
||||
if (getWaitTaskCountRes.code == 0) {
|
||||
message.error(getWaitTaskCountRes.message)
|
||||
await StartBackTask(true)
|
||||
return
|
||||
}
|
||||
if (getWaitTaskCountRes.data && getWaitTaskCountRes.data > 0) {
|
||||
// 这边提示用户是否丢弃
|
||||
dialog.warning({
|
||||
title: '后台任务',
|
||||
content: `检测到后台未完成的任务,任务数量为 ${getWaitTaskCountRes.data},请选择操作!`,
|
||||
positiveText: '继续执行',
|
||||
negativeText: '丢弃所有',
|
||||
onPositiveClick: async () => {
|
||||
// 启动任务
|
||||
await StartBackTask(false)
|
||||
},
|
||||
onNegativeClick: async () => {
|
||||
// 丢弃任务
|
||||
await StartBackTask(true)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// 启动任务
|
||||
await StartBackTask(true)
|
||||
}
|
||||
}
|
||||
|
||||
/** 校验机器码的逻辑 */
|
||||
async function GetMachineStatus() {
|
||||
try {
|
||||
// 获取机械码
|
||||
const machineRes = await window.system.GetMachineId()
|
||||
if (machineRes.code == 0) {
|
||||
message.error(machineRes.message)
|
||||
return
|
||||
}
|
||||
systemStore.machineId = machineRes.data
|
||||
|
||||
const checkMachineSatatusRes = await window.system.CheckMachineStatus(machineRes.data)
|
||||
if (checkMachineSatatusRes.code == 1) {
|
||||
// 自动检测成功,不同在弹窗手动进入
|
||||
message.success('自动检测机器码成功')
|
||||
// 判断是不是又后台任务
|
||||
await CheckWaitTask()
|
||||
return
|
||||
} else {
|
||||
message.error('自动检测机器码失败,请手动确认')
|
||||
}
|
||||
|
||||
// 这边是调用手动检测机器码
|
||||
// 弹窗,获取机器码
|
||||
const da = dialog.create({
|
||||
showIcon: false,
|
||||
content: () => h(CheckMachineId, { ref: machineRef }),
|
||||
style: `width : 400px; height: 300px`,
|
||||
maskClosable: false,
|
||||
positiveText: '确定',
|
||||
onClose: () => {
|
||||
// 关闭程序
|
||||
window.api.QuitApp()
|
||||
return false
|
||||
},
|
||||
onEsc: () => {
|
||||
// 关闭程序
|
||||
window.api.QuitApp()
|
||||
return false
|
||||
},
|
||||
onPositiveClick: async () => {
|
||||
const checkMachineSatatusRes = await window.system.CheckMachineStatus(machineRes.data)
|
||||
if (checkMachineSatatusRes.code == 1) {
|
||||
// 自动检测成功,不同在弹窗手动进入
|
||||
message.success('机器码校验成功')
|
||||
da.destroy()
|
||||
// 判断是不是又后台任务
|
||||
await CheckWaitTask()
|
||||
} else {
|
||||
message.error(checkMachineSatatusRes.message)
|
||||
return false
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
message.error('获取机器状态码失败,请联系管理员,错误信息如下:' + error.toString())
|
||||
} finally {
|
||||
show.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 加载版本和GPU信息 */
|
||||
async function GetViosionAndGpuMessage() {
|
||||
// 加载版本
|
||||
let res = await window.system.GetViosionAndGpuMessage()
|
||||
console.log(res)
|
||||
if (res.code == 1) {
|
||||
document.title = 'LAITool ' + res.data
|
||||
} else {
|
||||
message.success(res.message)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// 加载版本
|
||||
await GetViosionAndGpuMessage()
|
||||
|
||||
// 添加监听事件,同时按下ctrl + alt + l 打开控制台
|
||||
window.addEventListener('keydown', (e) => {
|
||||
if (e.ctrlKey && e.altKey && e.key === 'l') {
|
||||
@@ -306,7 +179,6 @@ onMounted(async () => {
|
||||
|
||||
window.api.setEventListen([DEFINE_STRING.SHOW_MAIN_MESSAGE], (value) => {
|
||||
let mess = value.message
|
||||
let type = 'success'
|
||||
if (value.code == 0) {
|
||||
message.error(mess)
|
||||
} else if (value.code == 2) {
|
||||
@@ -318,9 +190,6 @@ onMounted(async () => {
|
||||
}
|
||||
})
|
||||
|
||||
// 加载机器码状态
|
||||
await GetMachineStatus()
|
||||
|
||||
window.api.getSettingDafultData(async (value) => {
|
||||
window.config = value
|
||||
if (!(window.config && window.config.showOriginal)) {
|
||||
@@ -335,6 +204,22 @@ function expandIcon(value) {
|
||||
|
||||
const menuOptions = computed(() => {
|
||||
let me = [
|
||||
{
|
||||
label: () =>
|
||||
h(
|
||||
RouterLink,
|
||||
{
|
||||
to: {
|
||||
name: 'mainHome'
|
||||
},
|
||||
class: 'router-link-a'
|
||||
},
|
||||
{
|
||||
default: () => '首页'
|
||||
}
|
||||
),
|
||||
key: 'mainHome'
|
||||
},
|
||||
{
|
||||
label: () =>
|
||||
h(
|
||||
|
||||
@@ -0,0 +1,842 @@
|
||||
<!-- LoadingComponent.vue -->
|
||||
<template>
|
||||
<div class="loading-container" :class="{ 'full-screen': fullScreen }">
|
||||
<div class="loading-content">
|
||||
<!-- 主要加载动画 -->
|
||||
<div class="loading-spinner">
|
||||
<div class="spinner-ring">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载文本 -->
|
||||
<div class="loading-text">
|
||||
<h3>{{ title }}</h3>
|
||||
<p>{{ currentStepText }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 进度条 -->
|
||||
<div class="loading-progress">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" :style="{ width: progress + '%' }"></div>
|
||||
</div>
|
||||
<span class="progress-text">{{ progress }}%</span>
|
||||
</div>
|
||||
|
||||
<!-- 加载步骤 -->
|
||||
<div class="loading-steps">
|
||||
<div
|
||||
v-for="(step, index) in loadingSteps"
|
||||
:key="index"
|
||||
class="step-item"
|
||||
:class="{
|
||||
active: index === currentStep,
|
||||
completed: index < currentStep
|
||||
}"
|
||||
>
|
||||
<div class="step-icon">
|
||||
<n-icon v-if="index < currentStep" size="14">
|
||||
<checkmark-outline />
|
||||
</n-icon>
|
||||
<div v-else-if="index === currentStep" class="step-loading"></div>
|
||||
<span v-else class="step-number">{{ index + 1 }}</span>
|
||||
</div>
|
||||
<span class="step-text">{{ step.text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 背景装饰 -->
|
||||
<div class="loading-background">
|
||||
<div class="bg-circle bg-circle-1"></div>
|
||||
<div class="bg-circle bg-circle-2"></div>
|
||||
<div class="bg-circle bg-circle-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, nextTick, readonly } from 'vue'
|
||||
import { NIcon, useMessage, useDialog } from 'naive-ui'
|
||||
import { CheckmarkOutline } from '@vicons/ionicons5'
|
||||
import { useSystemStore } from '@/stores/system'
|
||||
import CheckMachineId from '@/renderer/src/components/Components/CheckMachineId.vue'
|
||||
import { TimeDelay } from '@/define/Tools/time'
|
||||
import { BookBackTaskStatus } from '@/define/enum/bookEnum'
|
||||
import { define } from '@/define/define'
|
||||
import { ValidateJson } from '@/define/Tools/validate'
|
||||
|
||||
const message = useMessage()
|
||||
const dialog = useDialog()
|
||||
const systemStore = useSystemStore()
|
||||
|
||||
const props = defineProps({
|
||||
// 是否全屏显示
|
||||
fullScreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 加载标题
|
||||
title: {
|
||||
type: String,
|
||||
default: 'LAI Tool 启动中'
|
||||
},
|
||||
// 主题色
|
||||
themeColor: {
|
||||
type: String,
|
||||
default: '#18a058'
|
||||
},
|
||||
// 自动开始加载
|
||||
autoStart: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['loading-complete'])
|
||||
|
||||
// 内部状态
|
||||
const progress = ref(0)
|
||||
const currentStep = ref(0)
|
||||
const isCompleted = ref(false)
|
||||
const isLoading = ref(false) // 添加加载状态标记
|
||||
|
||||
// 加载步骤配置
|
||||
const loadingSteps = ref([
|
||||
{
|
||||
text: '初始化系统组件',
|
||||
duration: 1000,
|
||||
action: async () => {
|
||||
// 模拟初始化系统组件
|
||||
await new Promise((resolve) => setTimeout(resolve, 800))
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
text: '获取设备硬件信息',
|
||||
duration: 900,
|
||||
action: async () => {
|
||||
// 模拟获取硬件信息
|
||||
await GetViosionAndGpuMessage()
|
||||
await new Promise((resolve) => setTimeout(resolve, 700))
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '加载软件信息',
|
||||
duration: 1200,
|
||||
action: async () => {
|
||||
// 模拟加载配置
|
||||
await GetRemoteSystemInformation()
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '检查软件授权状态',
|
||||
duration: 800,
|
||||
action: async () => {
|
||||
await GetMachineStatus()
|
||||
// 模拟检查授权
|
||||
await new Promise((resolve) => setTimeout(resolve, 600))
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '准备用户界面',
|
||||
duration: 600,
|
||||
action: async () => {
|
||||
// 模拟准备界面
|
||||
await new Promise((resolve) => setTimeout(resolve, 400))
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
// 获取远程的系统信息
|
||||
async function GetRemoteSystemInformation() {
|
||||
try {
|
||||
// 获取远程版本
|
||||
let remoteVersionRes = await window.axios.get(
|
||||
define.lms + `/lms/Options/GetOptionsByKey/0/2/LaitoolVersion`
|
||||
)
|
||||
console.log('remoteVersionRes', remoteVersionRes)
|
||||
if (remoteVersionRes.success == false) {
|
||||
throw new Error('获取远程版本信息失败: ' + remoteVersionRes.message)
|
||||
}
|
||||
if (remoteVersionRes.data && remoteVersionRes.data.code == 1) {
|
||||
systemStore.remoteVersion = remoteVersionRes.data.data.value
|
||||
} else {
|
||||
throw new Error('获取远程版本信息失败: ' + remoteVersionRes.data.message)
|
||||
}
|
||||
|
||||
// 获取更新信息
|
||||
let remoteUpdateJsonContentRes = await window.axios.get(
|
||||
define.lms + `/lms/Options/GetOptionsByKey/0/2/LaitoolUpdateJsonContent`
|
||||
)
|
||||
console.log('remoteUpdateJsonContentRes', remoteUpdateJsonContentRes)
|
||||
if (remoteUpdateJsonContentRes.success == false) {
|
||||
throw new Error('获取远程更新信息失败: ' + remoteUpdateJsonContentRes.message)
|
||||
}
|
||||
if (
|
||||
remoteUpdateJsonContentRes.data &&
|
||||
remoteUpdateJsonContentRes.data.code == 1 &&
|
||||
ValidateJson(remoteUpdateJsonContentRes.data.data.value)
|
||||
) {
|
||||
systemStore.versionInfo.updateInfo = JSON.parse(remoteUpdateJsonContentRes.data.data.value)
|
||||
} else {
|
||||
throw new Error('获取远程更新信息失败: ' + remoteUpdateJsonContentRes.data.message)
|
||||
}
|
||||
|
||||
systemStore.versionInfo.currentVersion = 'v' + systemStore.version
|
||||
systemStore.versionInfo.latestVersion = 'v' + systemStore.remoteVersion
|
||||
if (systemStore.version < systemStore.remoteVersion) {
|
||||
// 如果远程版本大于本地版本,更新本地版本
|
||||
systemStore.versionInfo.canUpdate = true
|
||||
} else {
|
||||
systemStore.versionInfo.canUpdate = false
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/** 加载版本和GPU信息 */
|
||||
async function GetViosionAndGpuMessage() {
|
||||
// 加载版本
|
||||
let res = await window.system.GetViosionAndGpuMessage()
|
||||
console.log(res)
|
||||
if (res.code == 1) {
|
||||
document.title = 'LAITool v' + res.data.version
|
||||
systemStore.version = res.data.version
|
||||
systemStore.gpu = res.data.gpu
|
||||
} else {
|
||||
message.success(res.message)
|
||||
}
|
||||
}
|
||||
|
||||
const machineRef = ref(null)
|
||||
|
||||
/** 校验机器码的逻辑 */
|
||||
async function GetMachineStatus() {
|
||||
try {
|
||||
// 获取机械码
|
||||
const machineRes = await window.system.GetMachineId()
|
||||
if (machineRes.code == 0) {
|
||||
message.error(machineRes.message)
|
||||
return
|
||||
}
|
||||
systemStore.machineId = machineRes.data
|
||||
|
||||
const checkMachineSatatusRes = await window.system.CheckMachineStatus(machineRes.data)
|
||||
if (checkMachineSatatusRes.code == 1) {
|
||||
// 自动检测成功,不同在弹窗手动进入
|
||||
message.success('自动检测机器码成功')
|
||||
// 判断是不是又后台任务
|
||||
await CheckWaitTask()
|
||||
return
|
||||
} else {
|
||||
message.error('自动检测机器码失败,请手动确认')
|
||||
}
|
||||
|
||||
// 这边是调用手动检测机器码
|
||||
// 弹窗,获取机器码
|
||||
const da = dialog.create({
|
||||
showIcon: false,
|
||||
content: () => h(CheckMachineId, { ref: machineRef }),
|
||||
style: `width : 400px; height: 300px`,
|
||||
maskClosable: false,
|
||||
positiveText: '确定',
|
||||
onClose: () => {
|
||||
// 关闭程序
|
||||
window.api.QuitApp()
|
||||
return false
|
||||
},
|
||||
onEsc: () => {
|
||||
// 关闭程序
|
||||
window.api.QuitApp()
|
||||
return false
|
||||
},
|
||||
onPositiveClick: async () => {
|
||||
const checkMachineSatatusRes = await window.system.CheckMachineStatus(machineRes.data)
|
||||
if (checkMachineSatatusRes.code == 1) {
|
||||
// 自动检测成功,不同在弹窗手动进入
|
||||
message.success('机器码校验成功')
|
||||
da.destroy()
|
||||
// 继续执行后续步骤
|
||||
jumpToStep(4)
|
||||
await nextTick() // 等待DOM更新
|
||||
continue_() // 从第3步开始继续执行
|
||||
// 判断是不是又后台任务
|
||||
await CheckWaitTask()
|
||||
} else {
|
||||
message.error(checkMachineSatatusRes.message)
|
||||
return false
|
||||
}
|
||||
}
|
||||
})
|
||||
throw new Error('手动验证机器码')
|
||||
} catch (error) {
|
||||
message.error('获取机器状态码失败,请联系管理员,错误信息如下:' + error.toString())
|
||||
throw error
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
/** 判断是不是还是后台任务,用户确认是不是丢弃 */
|
||||
async function CheckWaitTask() {
|
||||
await TimeDelay(1000)
|
||||
let getWaitTaskCountRes = await window.task.GetAllStatusTaskCount([
|
||||
BookBackTaskStatus.WAIT,
|
||||
BookBackTaskStatus.RECONNECT
|
||||
])
|
||||
// 报错,默认丢弃
|
||||
if (getWaitTaskCountRes.code == 0) {
|
||||
message.error(getWaitTaskCountRes.message)
|
||||
await StartBackTask(true)
|
||||
return
|
||||
}
|
||||
if (getWaitTaskCountRes.data && getWaitTaskCountRes.data > 0) {
|
||||
// 这边提示用户是否丢弃
|
||||
dialog.warning({
|
||||
title: '后台任务',
|
||||
content: `检测到后台未完成的任务,任务数量为 ${getWaitTaskCountRes.data},请选择操作!`,
|
||||
positiveText: '继续执行',
|
||||
negativeText: '丢弃所有',
|
||||
onPositiveClick: async () => {
|
||||
// 启动任务
|
||||
await StartBackTask(false)
|
||||
},
|
||||
onNegativeClick: async () => {
|
||||
// 丢弃任务
|
||||
await StartBackTask(true)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// 启动任务
|
||||
await StartBackTask(true)
|
||||
}
|
||||
}
|
||||
|
||||
// 启动后台任务
|
||||
async function StartBackTask(isGiveUp) {
|
||||
let startRes = await window.task.StartBackTask(isGiveUp)
|
||||
if (startRes.code == 0) {
|
||||
message.error(startRes.message)
|
||||
} else {
|
||||
message.success(startRes.message)
|
||||
}
|
||||
}
|
||||
|
||||
// 当前步骤描述文本
|
||||
const currentStepText = computed(() => {
|
||||
if (isCompleted.value) {
|
||||
return '加载完成,即将进入应用'
|
||||
}
|
||||
if (currentStep.value < loadingSteps.value.length) {
|
||||
return loadingSteps.value[currentStep.value].text
|
||||
}
|
||||
return '正在准备...'
|
||||
})
|
||||
|
||||
// 启动加载流程(从头开始)
|
||||
const startLoading = async () => {
|
||||
if (isLoading.value) return // 防止重复执行
|
||||
|
||||
isLoading.value = true
|
||||
currentStep.value = 0
|
||||
progress.value = 0
|
||||
isCompleted.value = false
|
||||
|
||||
await executeStepsFrom(0)
|
||||
}
|
||||
|
||||
// 继续加载流程(从当前步骤继续)
|
||||
const continueLoading = async () => {
|
||||
if (isLoading.value) return // 防止重复执行
|
||||
if (isCompleted.value) return // 已完成则不继续
|
||||
|
||||
isLoading.value = true
|
||||
await executeStepsFrom(currentStep.value)
|
||||
}
|
||||
|
||||
// 从指定步骤开始执行
|
||||
const executeStepsFrom = async (startStep) => {
|
||||
const totalSteps = loadingSteps.value.length
|
||||
|
||||
for (let i = startStep; i < totalSteps; i++) {
|
||||
currentStep.value = i
|
||||
const step = loadingSteps.value[i]
|
||||
|
||||
try {
|
||||
// 执行当前步骤的操作
|
||||
const startTime = Date.now()
|
||||
|
||||
// 同时进行进度条动画和实际操作
|
||||
await Promise.all([
|
||||
// 进度条动画
|
||||
animateProgress(i, totalSteps),
|
||||
// 执行步骤操作
|
||||
step.action()
|
||||
])
|
||||
|
||||
// 确保最少执行时间
|
||||
const elapsed = Date.now() - startTime
|
||||
const minDuration = step.duration || 800
|
||||
if (elapsed < minDuration) {
|
||||
await new Promise((resolve) => setTimeout(resolve, minDuration - elapsed))
|
||||
}
|
||||
} catch (error) {
|
||||
// 如果步骤执行失败,停止加载但保持当前状态
|
||||
console.error(`步骤 ${i + 1} 执行失败:`, error)
|
||||
isLoading.value = false
|
||||
return // 停止执行后续步骤
|
||||
}
|
||||
}
|
||||
|
||||
// 完成所有步骤
|
||||
currentStep.value = totalSteps
|
||||
progress.value = 100
|
||||
isCompleted.value = true
|
||||
isLoading.value = false
|
||||
|
||||
// 稍微延迟后触发完成事件
|
||||
setTimeout(() => {
|
||||
emit('loading-complete')
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 进度条动画
|
||||
const animateProgress = async (stepIndex, totalSteps) => {
|
||||
const startProgress = Math.floor((stepIndex / totalSteps) * 100)
|
||||
const endProgress = Math.floor(((stepIndex + 1) / totalSteps) * 100)
|
||||
|
||||
const duration = 300 // 动画持续时间
|
||||
const steps = 20 // 动画步数
|
||||
const stepDuration = duration / steps
|
||||
const progressStep = (endProgress - startProgress) / steps
|
||||
|
||||
for (let i = 0; i <= steps; i++) {
|
||||
progress.value = Math.min(startProgress + Math.floor(progressStep * i), endProgress)
|
||||
await new Promise((resolve) => setTimeout(resolve, stepDuration))
|
||||
}
|
||||
}
|
||||
|
||||
// 手动启动加载(从头开始)
|
||||
const start = () => {
|
||||
startLoading()
|
||||
}
|
||||
|
||||
// 继续执行剩余步骤
|
||||
const continue_ = () => {
|
||||
continueLoading()
|
||||
}
|
||||
|
||||
// 跳过当前步骤并继续
|
||||
const skipCurrentStep = () => {
|
||||
if (isLoading.value || isCompleted.value) return
|
||||
|
||||
// 标记当前步骤为完成状态
|
||||
currentStep.value += 1
|
||||
|
||||
// 继续执行后续步骤
|
||||
continueLoading()
|
||||
}
|
||||
|
||||
// 跳到指定步骤
|
||||
const jumpToStep = (stepIndex) => {
|
||||
if (isLoading.value || isCompleted.value) return
|
||||
if (stepIndex < 0 || stepIndex >= loadingSteps.value.length) return
|
||||
|
||||
currentStep.value = stepIndex
|
||||
// 更新进度条
|
||||
const totalSteps = loadingSteps.value.length
|
||||
progress.value = Math.floor((stepIndex / totalSteps) * 100)
|
||||
}
|
||||
|
||||
// 重置加载状态
|
||||
const reset = () => {
|
||||
isLoading.value = false
|
||||
progress.value = 0
|
||||
currentStep.value = 0
|
||||
isCompleted.value = false
|
||||
}
|
||||
|
||||
// 暂停加载(保持当前状态)
|
||||
const pause = () => {
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
// 恢复加载(从当前步骤继续)
|
||||
const resume = () => {
|
||||
if (!isCompleted.value) {
|
||||
continueLoading()
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
start, // 从头开始
|
||||
continue: continue_, // 继续执行
|
||||
skipCurrentStep, // 跳过当前步骤
|
||||
jumpToStep, // 跳到指定步骤
|
||||
reset, // 重置状态
|
||||
pause, // 暂停
|
||||
resume, // 恢复
|
||||
// 状态属性
|
||||
isLoading: readonly(isLoading),
|
||||
isCompleted: readonly(isCompleted),
|
||||
currentStep: readonly(currentStep),
|
||||
progress: readonly(progress)
|
||||
})
|
||||
|
||||
// 自动启动
|
||||
onMounted(() => {
|
||||
if (props.autoStart) {
|
||||
nextTick(() => {
|
||||
startLoading()
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loading-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 200px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.loading-container.full-screen {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
min-height: 100vh;
|
||||
max-height: 100vh;
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.loading-content {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
max-width: 400px;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 确保全屏时内容居中 */
|
||||
.loading-container.full-screen .loading-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: auto;
|
||||
max-width: 500px;
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.loading-spinner {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.spinner-ring {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.spinner-ring div {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 51px;
|
||||
height: 51px;
|
||||
margin: 6px;
|
||||
border: 6px solid v-bind(themeColor);
|
||||
border-radius: 50%;
|
||||
animation: spinner-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||
border-color: v-bind(themeColor) transparent transparent transparent;
|
||||
}
|
||||
|
||||
.spinner-ring div:nth-child(1) {
|
||||
animation-delay: -0.45s;
|
||||
}
|
||||
.spinner-ring div:nth-child(2) {
|
||||
animation-delay: -0.3s;
|
||||
}
|
||||
.spinner-ring div:nth-child(3) {
|
||||
animation-delay: -0.15s;
|
||||
}
|
||||
|
||||
@keyframes spinner-ring {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 加载文本 */
|
||||
.loading-text h3 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.loading-text p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
/* 进度条 */
|
||||
.loading-progress {
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, v-bind(themeColor), #40a9ff);
|
||||
border-radius: 3px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 加载步骤 */
|
||||
.loading-steps {
|
||||
text-align: left;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 0;
|
||||
opacity: 0.5;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.step-item.active {
|
||||
opacity: 1;
|
||||
color: v-bind(themeColor);
|
||||
}
|
||||
|
||||
.step-item.completed {
|
||||
opacity: 0.8;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.step-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f0f0f0;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-item.active .step-icon {
|
||||
background: v-bind(themeColor);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.step-item.completed .step-icon {
|
||||
background: #52c41a;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.step-loading {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border: 2px solid white;
|
||||
border-top: 2px solid transparent;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
}
|
||||
|
||||
.step-item.active .step-number {
|
||||
}
|
||||
|
||||
.step-text {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 背景装饰 */
|
||||
.loading-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bg-circle {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(45deg, rgba(24, 160, 88, 0.1), rgba(64, 169, 255, 0.1));
|
||||
animation: float 6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.bg-circle-1 {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
top: 10%;
|
||||
left: 10%;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.bg-circle-2 {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
top: 60%;
|
||||
right: 10%;
|
||||
animation-delay: 2s;
|
||||
}
|
||||
|
||||
.bg-circle-3 {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
bottom: 20%;
|
||||
left: 60%;
|
||||
animation-delay: 4s;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0px) rotate(0deg);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-20px) rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 深色模式 */
|
||||
.dark .loading-container {
|
||||
background: rgba(26, 26, 26, 0.95);
|
||||
}
|
||||
|
||||
.dark .loading-container.full-screen {
|
||||
background: rgba(26, 26, 26, 0.98);
|
||||
}
|
||||
|
||||
.dark .loading-text h3 {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dark .loading-text p {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.dark .progress-bar {
|
||||
background: #3d3d3d;
|
||||
}
|
||||
|
||||
.dark .progress-text {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.dark .step-icon {
|
||||
background: #3d3d3d;
|
||||
}
|
||||
|
||||
.dark .step-number {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.dark .step-text {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dark .step-item {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
/* 响应式适配 */
|
||||
@media (max-width: 768px) {
|
||||
.loading-container.full-screen .loading-content {
|
||||
min-width: 300px;
|
||||
max-width: 90vw;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.loading-text h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.step-text {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 确保覆盖所有可能的父级样式 */
|
||||
.loading-container.full-screen {
|
||||
position: fixed !important;
|
||||
inset: 0 !important;
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
min-width: 100vw !important;
|
||||
min-height: 100vh !important;
|
||||
max-width: 100vw !important;
|
||||
max-height: 100vh !important;
|
||||
left: 0 !important;
|
||||
top: 0 !important;
|
||||
right: 0 !important;
|
||||
bottom: 0 !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
border-radius: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<div class="contact-developer">
|
||||
<!-- 头部说明 -->
|
||||
<div class="header-section">
|
||||
<div class="title">👨💻 开发者微信</div>
|
||||
<div class="description">扫描下方二维码添加开发者微信,获取专业技术支持</div>
|
||||
</div>
|
||||
|
||||
<!-- 二维码图片容器 -->
|
||||
<div class="qrcode-container">
|
||||
<img
|
||||
src="../../assets//dev-user.jpg"
|
||||
alt="开发者微信二维码"
|
||||
class="qrcode-image"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
<div v-if="showPlaceholder" class="qrcode-placeholder">
|
||||
<div class="placeholder-icon">📱</div>
|
||||
<div class="placeholder-text">二维码图片</div>
|
||||
<div class="placeholder-subtext">请联系管理员获取</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 联系说明 -->
|
||||
<div class="contact-info">
|
||||
<div class="contact-title">
|
||||
<span class="contact-icon">💡</span>
|
||||
联系须知
|
||||
</div>
|
||||
<ul class="contact-list">
|
||||
<li>添加时请备注:LAITool用户</li>
|
||||
<li>工作时间:周一至周五 9:00-18:00</li>
|
||||
<li>支持技术咨询、BUG反馈、功能建议</li>
|
||||
<li>请详细描述您遇到的问题,以便快速解决</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 底部提示 -->
|
||||
<div class="footer-info">
|
||||
<div>如无法扫码,可复制微信号:</div>
|
||||
<div class="wechat-id" @click="copyWechatId">{{ wechatId }}</div>
|
||||
<div>或发送邮件至:{{ email }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
const props = defineProps({
|
||||
wechatId: {
|
||||
type: String,
|
||||
default: 'xiangbie88'
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
default: '2769838458@qq.com'
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['copy-success', 'copy-error'])
|
||||
|
||||
const message = useMessage()
|
||||
const showPlaceholder = ref(false)
|
||||
|
||||
const handleImageError = () => {
|
||||
showPlaceholder.value = true
|
||||
}
|
||||
|
||||
const copyWechatId = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(props.wechatId)
|
||||
message.success('微信号已复制到剪贴板')
|
||||
emit('copy-success', props.wechatId)
|
||||
} catch (error) {
|
||||
message.error(`复制失败,请手动复制:${props.wechatId}`)
|
||||
emit('copy-error', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.contact-developer {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.header-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.qrcode-container {
|
||||
margin: 24px 0;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #f0f9ff 0%, #e6f7ff 100%);
|
||||
border-radius: 12px;
|
||||
border: 2px dashed #1890ff;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.qrcode-image {
|
||||
width: 200px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.qrcode-placeholder {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background: #f5f5f5;
|
||||
border: 2px dashed #d9d9d9;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.placeholder-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.placeholder-subtext {
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.contact-info {
|
||||
margin-top: 24px;
|
||||
padding: 16px;
|
||||
background: #fff7e6;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #fa8c16;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.contact-title {
|
||||
font-weight: 600;
|
||||
color: #fa8c16;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.contact-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.contact-list {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.contact-list li {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.footer-info {
|
||||
margin-top: 16px;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.wechat-id {
|
||||
font-weight: 600;
|
||||
color: #1890ff;
|
||||
margin-top: 4px;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
user-select: all;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
/* 深色模式适配 */
|
||||
.dark .title,
|
||||
.dark .contact-title {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dark .description {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.dark .qrcode-container {
|
||||
background: linear-gradient(135deg, #1a2332 0%, #2a3441 100%);
|
||||
border-color: #3a4a5c;
|
||||
}
|
||||
|
||||
.dark .qrcode-placeholder {
|
||||
background: #2d2d2d;
|
||||
border-color: #4a4a4a;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.dark .contact-info {
|
||||
background: #2a2418;
|
||||
border-color: #5a4a2c;
|
||||
}
|
||||
|
||||
.dark .contact-list {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.dark .footer-info {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.dark .wechat-id:hover {
|
||||
background-color: #1a2332;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<div class="wechat-group">
|
||||
<!-- 群信息卡片 -->
|
||||
<div class="group-info">
|
||||
<div class="group-title">🎯 加入VIP用户交流群</div>
|
||||
<div class="group-description">{{ groupInfo }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 群内福利 -->
|
||||
<div class="benefits-section">
|
||||
<div class="benefits-title">💡 群内福利:</div>
|
||||
<ul class="benefits-list">
|
||||
<li class="benefit-item">
|
||||
<span class="benefit-icon">📢</span>
|
||||
获取最新版本信息和更新通知
|
||||
</li>
|
||||
<li class="benefit-item">
|
||||
<span class="benefit-icon">💬</span>
|
||||
与其他用户交流使用技巧和经验
|
||||
</li>
|
||||
<li class="benefit-item">
|
||||
<span class="benefit-icon">🐛</span>
|
||||
快速反馈问题和建议
|
||||
</li>
|
||||
<li class="benefit-item">
|
||||
<span class="benefit-icon">🛠️</span>
|
||||
获得开发团队的技术支持
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 联系方式 -->
|
||||
<div class="contact-info">联系管理员邀请入群或者联系对应代理</div>
|
||||
|
||||
<!-- 群规提醒 -->
|
||||
<div class="rules-notice">注:请勿在群内发布广告或无关内容,保持良好交流氛围</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
groupInfo: {
|
||||
type: String,
|
||||
default: '仅限永久VIP用户可加入'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.wechat-group {
|
||||
line-height: 1.6;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.group-info {
|
||||
margin-bottom: 16px;
|
||||
padding: 12px;
|
||||
background: #f0f9ff;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #1890ff;
|
||||
}
|
||||
|
||||
.group-title {
|
||||
font-weight: 600;
|
||||
color: #1890ff;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.group-description {
|
||||
font-size: 13px;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.benefits-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.benefits-title {
|
||||
margin-bottom: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.benefits-list {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.benefit-item {
|
||||
margin-bottom: 8px;
|
||||
padding: 4px 0;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.benefit-icon {
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.benefit-item:nth-child(1) .benefit-icon {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.benefit-item:nth-child(2) .benefit-icon {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.benefit-item:nth-child(3) .benefit-icon {
|
||||
color: #f0a020;
|
||||
}
|
||||
|
||||
.benefit-item:nth-child(4) .benefit-icon {
|
||||
color: #d03050;
|
||||
}
|
||||
|
||||
.contact-info {
|
||||
margin-top: 16px;
|
||||
padding: 8px;
|
||||
background: #fff7e6;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
color: #fa8c16;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.rules-notice {
|
||||
margin-top: 8px;
|
||||
padding: 8px;
|
||||
background: #fff7e6;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
color: #fa8c16;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 深色模式适配 */
|
||||
.dark .group-info {
|
||||
background: #1a2332;
|
||||
border-color: #3a4a5c;
|
||||
}
|
||||
|
||||
.dark .group-title {
|
||||
color: #40a9ff;
|
||||
}
|
||||
|
||||
.dark .group-description {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.dark .benefits-title {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dark .benefits-list {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.dark .contact-info,
|
||||
.dark .rules-notice {
|
||||
background: #2a2418;
|
||||
color: #ffa940;
|
||||
}
|
||||
</style>
|
||||
@@ -13,6 +13,11 @@ const routes = [
|
||||
path: '/',
|
||||
component: () => import('./components/Home/Home.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '/mainHome',
|
||||
name: 'mainHome',
|
||||
component: () => import('./components/Home/SoftwareHome.vue')
|
||||
},
|
||||
{
|
||||
path: '/gptCopywriting',
|
||||
name: 'gptCopywriting',
|
||||
|
||||
Reference in New Issue
Block a user