- 简化计算属性,删除重复代码 - 优化文件扩展名获取逻辑 - 新增文件工具函数库 fileHelpers.js - 增强 CodeEditor 语法高亮(支持 30+ 语言) - 修复 Office 文档文件服务器访问权限 - 添加特殊文件名支持(Dockerfile、Makefile 等)
483 lines
13 KiB
Vue
483 lines
13 KiB
Vue
<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>
|