V 3.3.9(2025.05.29)

1. 生图包适配,修复部分任务无法获取图片的问题。
2. 新增聚合推文原创重置功能,直接重置GPT提示词、合并提示词、出图等。
3. 优化加载界面,提升用户体验。
4. 优化主页显示,优化UI界面,提升用户体验。
This commit is contained in:
2025-05-29 14:44:37 +08:00
parent 50900d3465
commit 7a774c48da
42 changed files with 3256 additions and 2359 deletions
-6
View File
@@ -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
View File
@@ -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({
+49 -164
View File
@@ -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>
+5
View File
@@ -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',