Private
Public Access
1
0
Files
u-desk/web/src/App.vue
绝尘 eb2cbad17b 优化:代码质量提升,修复重复逻辑和语法高亮支持
- 简化计算属性,删除重复代码
- 优化文件扩展名获取逻辑
- 新增文件工具函数库 fileHelpers.js
- 增强 CodeEditor 语法高亮(支持 30+ 语言)
- 修复 Office 文档文件服务器访问权限
- 添加特殊文件名支持(Dockerfile、Makefile 等)
2026-01-30 02:29:51 +08:00

483 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<a-layout class="layout">
<a-layout-header class="header">
<div class="header-content">
<div class="header-left">
<h2>U-Desk</h2>
</div>
<a-tabs v-model:active-key="activeTab" class="header-tabs">
<a-tab-pane
v-for="tab in visibleTabs"
:key="tab.key"
:title="tab.title"
/>
</a-tabs>
<div class="header-actions">
<a-tooltip content="设置">
<a-button type="text" @click="showSettings = true">
<template #icon>
<IconSettings />
</template>
</a-button>
</a-tooltip>
<ThemeToggle />
<!-- 窗口控制按钮 -->
<div class="window-controls">
<div class="window-control-btn" @click="handleMinimize" title="最小化">
<svg width="12" height="12" viewBox="0 0 12 12">
<rect x="0" y="5" width="12" height="2" fill="currentColor"/>
</svg>
</div>
<div class="window-control-btn" @click="handleMaximize" :title="isMaximized ? '还原' : '最大化'">
<svg v-if="!isMaximized" width="12" height="12" viewBox="0 0 12 12">
<rect x="1" y="1" width="10" height="10" fill="none" stroke="currentColor" stroke-width="1.5"/>
</svg>
<svg v-else width="12" height="12" viewBox="0 0 12 12">
<rect x="2" y="0" width="10" height="10" fill="none" stroke="currentColor" stroke-width="1.5"/>
<rect x="0" y="2" width="10" height="10" fill="none" stroke="currentColor" stroke-width="1.5"/>
</svg>
</div>
<div class="window-control-btn close-btn" @click="handleClose" title="关闭">
<svg width="12" height="12" viewBox="0 0 12 12">
<path d="M1 1L11 11M11 1L1 11" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</div>
</div>
</div>
</div>
</a-layout-header>
<a-layout-content class="content">
<!-- 动态渲染 Tab 内容 -->
<!-- 使用 KeepAlive 缓存组件状态避免切换时重新加载 -->
<KeepAlive include="FileSystem,DbCli,DeviceTest">
<component :is="getComponent(activeTab)" />
</KeepAlive>
</a-layout-content>
<!-- 设置抽屉 -->
<SettingsPanel
v-model="showSettings"
:config="appConfig"
@save="handleSaveConfig"
/>
<!-- 升级提示弹窗 -->
<UpdateNotification
v-model="showUpdateNotification"
:update-info="updateInfo"
@install="handleUpdateInstall"
@skip="handleUpdateSkip"
/>
</a-layout>
</template>
<script setup>
import { ref, watch, computed, onMounted } from 'vue'
import { IconSettings } from '@arco-design/web-vue/es/icon'
import { Message } from '@arco-design/web-vue'
import DeviceTest from './components/DeviceTest.vue'
import DbCli from './views/db-cli/index.vue'
import ThemeToggle from './components/ThemeToggle.vue'
import UpdatePanel from './components/UpdatePanel.vue'
import FileSystem from './components/FileSystem.vue'
import SettingsPanel from './components/SettingsPanel.vue'
import UpdateNotification from './components/UpdateNotification.vue'
// 存储键
const ACTIVE_TAB_STORAGE_KEY = 'app-active-tab'
// 从 localStorage 恢复上次打开的区域,默认为 'db-cli'
const savedTab = localStorage.getItem(ACTIVE_TAB_STORAGE_KEY)
const activeTab = ref((savedTab === 'user' ? 'db-cli' : savedTab) || 'db-cli')
const showSettings = ref(false)
const isMaximized = ref(false)
// 更新相关状态
const showUpdateNotification = ref(false)
const updateInfo = ref(null)
const checkedUpdate = ref(false)
// 应用配置
const appConfig = ref({
tabs: [],
visibleTabs: [],
defaultTab: 'db-cli'
})
// 可见 Tabs根据配置动态生成
const visibleTabs = computed(() => {
if (!appConfig.value.tabs || appConfig.value.tabs.length === 0) {
// 默认配置
return [
{ key: 'db-cli', title: '数据库' },
{ key: 'file-system', title: '文件管理' },
{ key: 'device', title: '设备调用测试' }
]
}
return appConfig.value.tabs
.filter(tab => tab.visible)
.sort((a, b) => {
const aIndex = appConfig.value.visibleTabs.indexOf(a.key)
const bIndex = appConfig.value.visibleTabs.indexOf(b.key)
return aIndex - bIndex
})
})
// 加载配置
const loadConfig = async () => {
try {
// 检查 Wails 绑定是否准备好
if (!window.go || !window.go.main || !window.go.main.App) {
console.warn('Wails 绑定未准备好,等待重试...')
setTimeout(() => loadConfig(), 100)
return
}
const result = await window.go.main.App.GetAppConfig()
if (result.success) {
const tabs = result.data.tabs || []
const visibleTabs = result.data.visibleTabs || []
// 确保 tabs 数组中的 visible 属性与 visibleTabs 同步
const syncedTabs = tabs.map(tab => ({
...tab,
visible: visibleTabs.includes(tab.key)
}))
appConfig.value = {
tabs: syncedTabs,
visibleTabs: visibleTabs,
defaultTab: result.data.defaultTab || 'db-cli'
}
// 设置默认 Tab
activeTab.value = appConfig.value.defaultTab
} else {
console.error('加载配置失败:', result.message)
// 使用默认配置
useDefaultConfig()
}
} catch (error) {
console.error('加载配置失败:', error)
// 使用默认配置
useDefaultConfig()
}
}
// 使用默认配置
const useDefaultConfig = () => {
appConfig.value = {
tabs: [
{ key: 'db-cli', title: '数据库', visible: true, enabled: true },
{ key: 'file-system', title: '文件管理', visible: true, enabled: true },
{ key: 'device', title: '设备调用测试', visible: true, enabled: true }
],
visibleTabs: ['db-cli', 'file-system', 'device'],
defaultTab: 'db-cli'
}
}
// 保存配置
const handleSaveConfig = async (config) => {
try {
const result = await window.go.main.App.SaveAppConfig({
tabs: config.tabs,
visibleTabs: config.visibleTabs,
defaultTab: config.defaultTab
})
if (result.success) {
// 更新本地配置
appConfig.value = {
tabs: [...config.tabs],
visibleTabs: [...config.visibleTabs],
defaultTab: config.defaultTab
}
// 如果当前激活的 Tab 被隐藏,切换到默认 Tab
if (!config.visibleTabs.includes(activeTab.value)) {
activeTab.value = config.defaultTab
}
Message.success('配置保存成功')
showSettings.value = false
} else {
Message.error(result.message || '保存配置失败')
throw new Error(result.message)
}
} catch (error) {
console.error('保存配置失败:', error)
throw error
}
}
// 获取组件
const getComponent = (key) => {
const components = {
'db-cli': DbCli,
'file-system': FileSystem,
'device': DeviceTest
}
return components[key] || null
}
// 检查更新
const checkForUpdates = async () => {
try {
// 等待 Wails 绑定准备好
if (!window.go || !window.go.main || !window.go.main.App) {
console.warn('Wails 绑定未准备好,延迟检查更新...')
setTimeout(() => checkForUpdates(), 1000)
return
}
// 获取更新配置
const configResult = await window.go.main.App.GetUpdateConfig()
if (!configResult.success) {
console.error('获取更新配置失败:', configResult.message)
return
}
const config = configResult.data
const shouldCheck = config.auto_check_enabled
if (!shouldCheck) {
console.log('自动更新检查已关闭')
return
}
console.log('[自动检查] 开始检查更新...')
// 检查更新
const result = await window.go.main.App.CheckUpdate()
if (result.success && result.data) {
checkedUpdate.value = true
// 检查是否已跳过此版本
const skippedVersion = localStorage.getItem('skipped_version')
if (result.data.has_update) {
// 如果是强制更新,或者未跳过此版本,则显示提示
if (result.data.force_update || skippedVersion !== result.data.latest_version) {
console.log('[自动检查] 发现新版本:', result.data.latest_version)
updateInfo.value = result.data
// 延迟显示,让用户先看到应用界面
setTimeout(() => {
showUpdateNotification.value = true
}, 2000)
} else {
console.log('[自动检查] 此版本已跳过')
}
} else {
console.log('[自动检查] 已是最新版本')
}
}
} catch (error) {
console.error('检查更新失败:', error)
}
}
// 组件挂载时加载配置和检查更新
onMounted(() => {
loadConfig()
// 延迟检查更新,避免阻塞应用启动
setTimeout(() => {
if (!checkedUpdate.value) {
checkForUpdates()
}
}, 3000)
})
// 监听 activeTab 变化,自动保存到 localStorage
watch(activeTab, (newTab) => {
localStorage.setItem(ACTIVE_TAB_STORAGE_KEY, newTab)
})
// 窗口控制方法
const handleMinimize = async () => {
try {
if (window.go?.main?.App?.WindowMinimize) {
await window.go.main.App.WindowMinimize()
}
} catch (error) {
console.error('最小化窗口失败:', error)
}
}
const handleMaximize = async () => {
try {
if (window.go?.main?.App?.WindowMaximize) {
await window.go.main.App.WindowMaximize()
isMaximized.value = await window.go.main.App.WindowIsMaximized()
}
} catch (error) {
console.error('最大化窗口失败:', error)
}
}
const handleClose = async () => {
try {
if (window.go?.main?.App?.WindowClose) {
await window.go.main.App.WindowClose()
}
} catch (error) {
console.error('关闭窗口失败:', error)
}
}
// 升级提示事件处理
const handleUpdateInstall = async (filePath) => {
try {
const result = await window.go.main.App.InstallUpdate(filePath, true)
if (result.success) {
Message.success({
content: '安装成功!应用将在几秒后重启...',
duration: 3000
})
} else {
Message.error(result.message || '安装失败')
}
} catch (error) {
console.error('安装失败:', error)
Message.error('安装失败:' + (error.message || error))
}
}
const handleUpdateSkip = () => {
// 清除跳过的版本记录(如果用户选择"稍后提醒"
// 版本记录在组件内部处理
}
// 监听 activeTab 变化,如果当前 Tab 不在可见列表中,切换到默认 Tab
watch(activeTab, (newTab) => {
// 保存到 localStorage
localStorage.setItem(ACTIVE_TAB_STORAGE_KEY, newTab)
// 检查 Tab 是否在可见列表中
const isVisible = appConfig.value.visibleTabs.includes(newTab)
if (!isVisible && appConfig.value.visibleTabs.length > 0) {
// 切换到默认 Tab
activeTab.value = appConfig.value.defaultTab
}
})
</script>
<style scoped>
.layout {
height: 100vh;
display: flex;
flex-direction: column;
}
.header {
background: var(--color-bg-2);
border-bottom: 1px solid var(--color-border);
user-select: none;
--wails-draggable: drag; /* Wails 拖拽属性 */
}
.header-content {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
padding: 0 20px;
}
.header-left {
display: flex;
align-items: center;
min-width: 150px;
--wails-draggable: drag; /* 左侧标题区域可拖拽 */
}
.header-content h2 {
margin: 0;
font-size: 18px;
font-weight: 500;
color: var(--color-text-1);
}
.header-tabs {
flex: 1;
margin-left: 40px;
}
.header-actions {
display: flex;
align-items: center;
gap: 8px;
margin-left: 20px;
min-width: 200px;
justify-content: flex-end;
--wails-draggable: no-drag; /* 按钮区域不响应拖拽 */
}
.window-controls {
display: flex;
align-items: center;
margin-left: 12px;
padding-left: 12px;
border-left: 1px solid var(--color-border);
}
.window-control-btn {
width: 40px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background-color 0.15s;
color: var(--color-text-2);
--wails-draggable: no-drag; /* 窗口控制按钮不响应拖拽 */
}
.window-control-btn:hover {
background: var(--color-fill-2);
color: var(--color-text-1);
}
.window-control-btn.close-btn:hover {
background: #e81123;
color: #ffffff;
}
.window-control-btn svg {
display: block;
pointer-events: none;
}
.content {
flex: 1;
overflow: auto;
background: var(--color-bg-1);
}
</style>
<!-- Wails 拖拽样式 -->
<style>
.header {
--wails-draggable: drag;
}
/* 所有按钮类元素都不可拖拽 */
.header-actions,
.window-control-btn {
--wails-draggable: no-drag;
}
/* tabs 的具体 tab 项不可拖拽,但空白区域可以拖拽 */
.arco-tabs-tab {
--wails-draggable: no-drag;
}
/* Arco Design 按钮不可拖拽 */
.arco-btn,
.arco-select,
.arco-tooltip {
--wails-draggable: no-drag;
}
</style>