Private
Public Access
1
0

修复: OSS收藏打开+连接指示器根目录+字段映射+侧边栏重构+文件监听+首屏优化

This commit is contained in:
2026-05-16 17:55:59 +08:00
parent 316e517989
commit d17c20c579
37 changed files with 1667 additions and 1566 deletions

57
app.go
View File

@@ -5,6 +5,7 @@ package main
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
@@ -16,6 +17,7 @@ import (
"u-desk/internal/api"
"u-desk/internal/common"
"u-desk/internal/filewatch"
"u-desk/internal/filesystem"
"u-desk/internal/hotkey"
osssvc "u-desk/internal/ossdrv"
@@ -44,6 +46,7 @@ type App struct {
sftpService *sftp.Service
ossService *osssvc.Service
profileSvc *service.ProfileService
fileWatcher *filewatch.Watcher
isAlwaysOnTop bool
mu sync.Mutex
unregisterHotkey func()
@@ -61,6 +64,11 @@ func NewApp() *App {
// SetMainWindow 设置主窗口引用(由 main.go 在创建窗口后调用)
func (a *App) SetMainWindow(w *application.WebviewWindow) {
a.mainWindow = w
a.fileWatcher = filewatch.NewWatcher(func(name string, data ...any) {
if a.mainWindow != nil {
a.mainWindow.EmitEvent(name, data...)
}
})
}
// RegisterGlobalHotkey 注册 Ctrl+Shift+B 全局热键(需在窗口创建后调用)
@@ -69,11 +77,10 @@ func (a *App) RegisterGlobalHotkey() {
return
}
a.mu.Lock()
defer a.mu.Unlock()
if a.unregisterHotkey != nil {
a.mu.Unlock()
return
}
a.mu.Unlock()
hwnd := uintptr(a.mainWindow.NativeWindow())
if hwnd == 0 {
fmt.Println("[全局热键] HWND 为 0注册跳过")
@@ -85,9 +92,7 @@ func (a *App) RegisterGlobalHotkey() {
return
}
fmt.Println("[全局热键] Ctrl+Shift+B 已注册")
a.mu.Lock()
a.unregisterHotkey = func() { hotkey.Unregister(hwnd, id) }
a.mu.Unlock()
}
// HandleHotkey 处理全局热键回调:切换 BgmBar 显示/隐藏
@@ -102,6 +107,9 @@ func (a *App) HandleHotkey() {
func (a *App) ServiceStartup(ctx context.Context, _ application.ServiceOptions) error {
a.ctx = ctx
// dev 模式打开 DevTools
openDevTools(a.mainWindow)
// 1. 核心初始化SQLite必须同步很快
if _, err := storage.InitFast(); err != nil {
return fmt.Errorf("SQLite 初始化失败,应用无法启动: %w", err)
@@ -310,6 +318,21 @@ func (a *App) ReadFile(path string) (string, error) {
return a.filesystem.ReadFile(path)
}
// WatchFile 开始监听指定文件的变化,变化时发送 file-changed 事件
func (a *App) WatchFile(path string) error {
if a.fileWatcher == nil {
return fmt.Errorf("文件监听器未初始化")
}
return a.fileWatcher.WatchFile(path)
}
// UnwatchFile 停止监听文件变化
func (a *App) UnwatchFile() {
if a.fileWatcher != nil {
a.fileWatcher.UnwatchFile()
}
}
// WriteFileRequest 写入文件请求结构体
type WriteFileRequest struct {
Path string `json:"path"`
@@ -435,6 +458,7 @@ func (a *App) ResolveShortcut(lnkPath string) (map[string]interface{}, error) {
}, nil
}
// getWindowsSpecialFolder 从注册表读取 Windows 特殊文件夹的真实路径
func getWindowsSpecialFolder(guid string, fallbackName string) string {
key, err := registry.OpenKey(registry.CURRENT_USER,
@@ -1194,28 +1218,9 @@ func (a *App) LoadConnectionProfiles() ([]map[string]interface{}, error) {
if err != nil {
return nil, err
}
result := make([]map[string]interface{}, len(list))
for i, p := range list {
result[i] = map[string]interface{}{
"id": float64(p.ID),
"name": p.Name,
"host": p.Host,
"port": p.Port,
"username": p.Username,
"password": p.Password,
"keyPath": p.KeyPath,
"type": p.Type,
"provider": p.Provider,
"token": p.Token,
"accessKey": p.AccessKey,
"secretKey": p.SecretKey,
"bucket": p.Bucket,
"region": p.Region,
"endpoint": p.Endpoint,
"lastConnected": p.LastConnected,
"sortOrder": float64(p.SortOrder),
}
}
var result []map[string]interface{}
data, _ := json.Marshal(list)
json.Unmarshal(data, &result)
return result, nil
}

View File

@@ -48,7 +48,7 @@ tasks:
- cmd: rm -f *.syso
platforms: [linux, darwin]
vars:
BUILD_FLAGS: '{{if eq .DEV "true"}}{{if .EXTRA_TAGS}}-tags {{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production,devtools -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{end}}'
BUILD_FLAGS: '{{if eq .DEV "true"}}{{if .EXTRA_TAGS}}-tags {{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{end}}'
env:
GOOS: windows
CGO_ENABLED: '{{.CGO_ENABLED | default "0"}}'

View File

@@ -1,15 +1,15 @@
{
"fixed": {
"file_version": "0.1.0"
"file_version": "0.4.0"
},
"info": {
"0000": {
"ProductVersion": "0.1.0",
"CompanyName": "My Company",
"FileDescription": "A u-desk application",
"LegalCopyright": "© 2026, My Company",
"ProductVersion": "0.4.0",
"CompanyName": "1216.top",
"FileDescription": "U-Desk 桌面文件管理器",
"LegalCopyright": "© 2026, 1216.top",
"ProductName": "U-Desk",
"Comments": "This is a comment"
"Comments": "桌面文件管理器"
}
}
}

53
cmd/dbread/main.go Normal file
View File

@@ -0,0 +1,53 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
)
func main() {
home, _ := os.UserHomeDir()
dbPath := filepath.Join(home, ".u-desk", "app.db")
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
if err != nil {
fmt.Fprintln(os.Stderr, "open db:", err)
os.Exit(1)
}
// 所有列
type Profile struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"column:name"`
Type string `gorm:"column:type"`
Host string `gorm:"column:host"`
Port int `gorm:"column:port"`
Username string `gorm:"column:username"`
Provider string `gorm:"column:provider"`
Token string `gorm:"column:token"`
AccessKey string `gorm:"column:access_key"`
SecretKey string `gorm:"column:secret_key"`
Bucket string `gorm:"column:bucket"`
Region string `gorm:"column:region"`
Endpoint string `gorm:"column:endpoint"`
}
var profiles []Profile
db.Table("connection_profiles").Find(&profiles)
// 脱敏 secret_key
for i := range profiles {
if len(profiles[i].SecretKey) > 8 {
profiles[i].SecretKey = profiles[i].SecretKey[:4] + "****"
}
}
b, _ := json.MarshalIndent(profiles, "", " ")
fmt.Println("=== Connection Profiles ===")
fmt.Println(string(b))
}

16
devtools.go Normal file
View File

@@ -0,0 +1,16 @@
//go:build !production
package main
import (
"time"
"github.com/wailsapp/wails/v3/pkg/application"
)
func openDevTools(window *application.WebviewWindow) {
go func() {
time.Sleep(2 * time.Second)
window.OpenDevTools()
}()
}

7
devtools_prod.go Normal file
View File

@@ -0,0 +1,7 @@
//go:build production
package main
import "github.com/wailsapp/wails/v3/pkg/application"
func openDevTools(window *application.WebviewWindow) {}

View File

@@ -0,0 +1,23 @@
# 代码改进清单
> 基于 Wails 开发文档 vs 项目现状审查2026-05-16
## 已完成
- [x] 🔴① `main.go` 添加 `SingleInstance` 单实例锁(`top.1216.udesk`
- [x] 🔴② `app.go:74-97` RegisterGlobalHotkey 竞态修复(合并为单一 `defer mu.Unlock()`
- [x] 🔴③ `build/windows/info.json` 版本号 0.1.0→0.4.0公司名→1216.top
- [x] 🟡⑤ 删除 `frontend/src/wailsjs/wailsjs/` v2 遗留绑定目录
- [x] 🟡⑥ `LoadConnectionProfiles` 手动 map 转换改用 `json.Marshal/Unmarshal`
- [x] 🟡⑨ `App.vue` onMounted 添加 `contextmenu` 事件拦截(禁用浏览器默认右键菜单)
- [x] 🟡⑫ `main.go` Run() 错误输出改用 `fmt.Fprintf(os.Stderr, ...)`
- [x] Sidebar 设置按钮 `···` 点击无响应修复:移除 `.stop` + Teleport 重构 + 增大点击区域
## 待处理
- [ ] ④ App 结构体拆分 — 1344 行,应拆为 FilesystemService / ProfileService / BgmService / UpdateService 等 v3 Service
- [ ]`internal/api/pdf_api.go:371` SelectDirectory 改用 Wails 原生对话框 `application.Get().Dialog.OpenFile()`
- [ ]`app.go:176-189` HWND 轮询改事件驱动 — v3 alpha 暂无对应 API后续跟进
- [ ]`app.go:29` Windows 专用导入 `golang.org/x/sys/windows/registry` 加构建标签拆到 `*_windows.go`
- [ ] ⑪ 全局结构化日志 — `fmt.Println` 替换为 `log/slog`,按优先级分批替换
- [ ]`app.go:158` 更新检查 URL `https://c.1216.top/last-version.json` 移入配置

View File

@@ -674,6 +674,13 @@ export function SftpWriteFile(req: $models.SftpWriteFileRequest): $CancellablePr
return $Call.ByID(2401472593, req);
}
/**
* UnwatchFile 停止监听文件变化
*/
export function UnwatchFile(): $CancellablePromise<void> {
return $Call.ByID(3006906623);
}
/**
* VerifyUpdateFile 验证更新文件哈希值
*/
@@ -683,6 +690,13 @@ export function VerifyUpdateFile(filePath: string, expectedHash: string, hashTyp
});
}
/**
* WatchFile 开始监听指定文件的变化,变化时发送 file-changed 事件
*/
export function WatchFile(path: string): $CancellablePromise<void> {
return $Call.ByID(325055910, path);
}
/**
* WindowClose 关闭窗口
*/

View File

@@ -176,6 +176,9 @@ onMounted(() => {
// 禁止 Ctrl+滚轮缩放
document.addEventListener('wheel', preventZoom, { passive: false })
// 禁用浏览器默认右键菜单(桌面应用体验)
document.addEventListener('contextmenu', e => e.preventDefault())
// 延迟检查更新(启动后 3 秒,静默模式)
setTimeout(() => {
updateStore.checkForUpdates(true)

View File

@@ -91,11 +91,14 @@ class ConnectionManagerImpl {
try {
const list = await LoadConnectionProfiles()
if (list && list.length > 0) {
this._profiles = list.map((p: any) => ({
...p,
id: String(p.id),
lastConnected: p.lastConnected || p.last_connected ? new Date(p.lastConnected || p.last_connected).getTime() : undefined,
}))
this._profiles = list.map((p: any) => {
const camel = snakeToCamel(p)
return {
...camel,
id: String(p.id),
lastConnected: camel.lastConnected ? new Date(camel.lastConnected).getTime() : undefined,
}
})
const hasLocal = this._profiles.some(p => p.type === 'local')
if (!hasLocal) {
this._profiles.unshift({
@@ -105,17 +108,20 @@ class ConnectionManagerImpl {
}
} catch { /* 首次使用 */ }
this.notifyChange()
this._profiles.forEach(p => {
if (p.type === 'local') { this.fetchSystemInfo(p.id).catch(() => {}) }
})
const autoConnect = localStorage.getItem('desk:autoConnect')
if (autoConnect !== 'false') {
for (const p of this._profiles) {
if (p.type !== 'local') {
this.buildAndPool(String(p.id), p).catch(() => {})
// 延迟执行系统信息采集和自动连接,不阻塞首屏渲染
setTimeout(() => {
this._profiles.forEach(p => {
if (p.type === 'local') { this.fetchSystemInfo(p.id).catch(() => {}) }
})
const autoConnect = localStorage.getItem('desk:autoConnect')
if (autoConnect !== 'false') {
for (const p of this._profiles) {
if (p.type !== 'local') {
this.buildAndPool(String(p.id), p).catch(() => {})
}
}
}
}
}, 500)
}
/** 保存/更新单个 profile 到 SQLite */
@@ -269,9 +275,16 @@ class ConnectionManagerImpl {
return
}
// 新建连接并入池(成功后再设 activeId
await this.buildAndPool(profileId, profile)
// 先设 activeId确保回调读到正确的 activeProfile失败时回退
const prevActiveId = this._activeId
this._activeId = profileId
try {
await this.buildAndPool(profileId, profile)
} catch (err) {
this._activeId = prevActiveId
this.notifyChange()
throw err
}
}
/** 断开指定 profile 并从池移除 */

View File

@@ -108,3 +108,20 @@ export async function detectFileTypeByContent(path: string) {
export async function getCommonPaths() {
return t().getCommonPaths()
}
// 文件监听(仅本地模式,直接调用 Wails 绑定)
export async function watchFile(path: string): Promise<void> {
if (connectionManager.isRemote()) return
try {
const { WatchFile } = await import('../wailsjs/v3-bindings/u-desk/app')
await WatchFile(path)
} catch { /* 忽略绑定未生成的场景 */ }
}
export async function unwatchFile(): Promise<void> {
if (connectionManager.isRemote()) return
try {
const { UnwatchFile } = await import('../wailsjs/v3-bindings/u-desk/app')
await UnwatchFile()
} catch { /* 忽略 */ }
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -75,7 +75,7 @@
</template>
<script setup lang="ts">
import { ref, computed, shallowRef, onMounted, onUnmounted, provide, watch } from 'vue'
import { ref, computed, shallowRef, onMounted, onUnmounted, provide, inject, watch, type Ref } from 'vue'
import { IconCloud, IconFolder, IconExclamationCircle } from '@arco-design/web-vue/es/icon'
import { Message } from '@arco-design/web-vue'
import { connectionManager } from '@/api/connection-manager'
@@ -98,6 +98,15 @@ const emit = defineEmits<{
const { setTimeout: delay, clearTimeout: clearDelay } = useTimeout()
// 下拉互斥:面包屑 vs 连接指示器
const dropdownOwner = inject<Ref<'connection' | 'breadcrumb' | null>>('dropdownOwner', ref(null))
watch(dropdownOwner, (owner) => {
if (owner === 'breadcrumb') {
showDirDropdown.value = false
closeAllMenusFn()
}
})
// === 连接菜单(原有逻辑) ===
const showMenu = ref(false)
const moreOpenId = ref<string | null>(null)
@@ -144,8 +153,9 @@ const dirHoverTimer = ref<NodeJS.Timeout | null>(null)
const dirCloseTimer = ref<NodeJS.Timeout | null>(null)
const rootPath = computed(() => {
const path = props.filePath?.replace(/\\/g, '/') || ''
if (/^[A-Za-z]:/.test(path)) return path.substring(0, 2) + '/'
const active = connectionManager.activeProfile
if (!active || active.type === 'local') return '/'
// 远程连接:根目录始终是 /
return '/'
})
@@ -159,11 +169,14 @@ const loadRootChildren = async () => {
try {
const files = await listDir(path)
dirLastLoadedPath.value = path
dirChildren.value = sortFileList(files.map(f => ({
name: f.name,
path: f.path,
isDir: f.isDir
})))
// 只保留目录限制最多200条
dirChildren.value = sortFileList(
files.filter(f => f.isDir).map(f => ({
name: f.name,
path: f.path,
isDir: f.isDir
}))
).slice(0, 200)
} catch (err) {
console.error('[ConnectionIndicator] 加载根目录失败:', err)
dirError.value = '加载失败'
@@ -177,6 +190,7 @@ const onDirHover = () => {
if (dirCloseTimer.value) clearDelay(dirCloseTimer.value)
dirHoverTimer.value = delay(() => {
dropdownOwner.value = 'connection'
showDirDropdown.value = true
showMenu.value = false
closeAllMenusFn()
@@ -293,7 +307,7 @@ watch(() => props.filePath, () => {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 2px 10px;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;

View File

@@ -373,9 +373,18 @@
</div>
</template>
<!-- 多文件预览浮动 Tab -->
<!-- 空状态无文件预览 -->
<div v-if="!config.currentFileName" class="empty-preview">
<div class="empty-preview-content">
<img src="@/assets/logo.png" alt="U-Desk" class="empty-preview-icon" />
<div class="empty-preview-title">U-Desk</div>
<div class="empty-preview-subtitle">文件管理器</div>
</div>
</div>
<!-- 文件预览浮动 Tab -->
<div
v-if="previewTabs.length > 1"
v-if="previewTabs.length > 0"
class="preview-tabs"
>
<div
@@ -1811,4 +1820,40 @@ body[arco-theme*='dark'] .markdown-preview-content :deep(.hljs-link) {
background: var(--color-fill-3);
color: var(--color-text-1);
}
.empty-preview {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
user-select: none;
}
.empty-preview-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
opacity: 0.3;
}
.empty-preview-icon {
width: 80px;
height: 80px;
object-fit: contain;
opacity: 0.6;
}
.empty-preview-title {
font-size: 24px;
font-weight: 600;
color: var(--color-text-2);
letter-spacing: 2px;
}
.empty-preview-subtitle {
font-size: 13px;
color: var(--color-text-3);
}
</style>

View File

@@ -54,7 +54,7 @@
</template>
<script setup lang="ts">
import { ref, computed, watch, provide, type Ref } from 'vue'
import { ref, computed, watch, provide, inject, type Ref } from 'vue'
import { IconRight, IconFolder } from '@arco-design/web-vue/es/icon'
import { listDir } from '@/api/system'
import { sortFileList } from '@/utils/fileUtils'
@@ -63,6 +63,14 @@ import DropdownItem from './DropdownItem.vue'
const { setTimeout: delay, clearTimeout } = useTimeout()
// 下拉互斥:面包屑 vs 连接指示器
const dropdownOwner = inject<Ref<'connection' | 'breadcrumb' | null>>('dropdownOwner', ref(null))
watch(dropdownOwner, (owner) => {
if (owner === 'connection') {
resetAndClose()
}
})
const openMenus = ref<Map<number, string>>(new Map())
const closeMenu = (level: number) => {
@@ -122,22 +130,31 @@ const closeTimer = ref<NodeJS.Timeout | null>(null)
const children = ref<Array<{ name: string; path: string; isDir: boolean }>>([])
const loading = ref(false)
const error = ref('')
const lastLoadedPath = ref('')
// 缓存:路径 → 子目录列表(只缓存目录,跨导航保留)
const dirCache = new Map<string, Array<{ name: string; path: string; isDir: boolean }>>()
const loadChildren = async (path: string) => {
if (path === lastLoadedPath.value) return
if (dirCache.has(path)) {
children.value = dirCache.get(path)!
return
}
loading.value = true
error.value = ''
try {
const files = await listDir(path)
lastLoadedPath.value = path
children.value = sortFileList(files.map(f => ({
name: f.name,
path: f.path,
isDir: f.isDir
})))
// 只保留目录面包屑只用于导航限制最多200条
const dirs = sortFileList(
files.filter(f => f.isDir).map(f => ({
name: f.name,
path: f.path,
isDir: f.isDir
}))
).slice(0, 200)
dirCache.set(path, dirs)
children.value = dirs
} catch (err) {
console.error('[Breadcrumb] 加载子目录失败:', err)
error.value = '加载失败'
@@ -159,6 +176,7 @@ const onHover = (segment: PathSegment, index: number) => {
if (closeTimer.value) clearTimeout(closeTimer.value)
hoverTimer.value = delay(() => {
dropdownOwner.value = 'breadcrumb'
activeIndex.value = index
loadChildren(segment.path)
}, 200)
@@ -194,7 +212,6 @@ const onOpenFile = (path: string) => {
watch(() => props.path, () => {
activeIndex.value = null
children.value = []
lastLoadedPath.value = ''
openMenus.value = new Map()
})
</script>

View File

@@ -3,7 +3,7 @@
<div v-show="config.visible" class="sidebar">
<template v-for="section in sectionOrder" :key="section">
<!-- 服务器区块 -->
<div v-if="section === 'server' && showServer" class="sidebar-section" :class="{ 'section-on-top': settingsOpen || moreOpenId }">
<div v-if="section === 'server' && showServer" class="sidebar-section">
<div class="section-header" @click="serverCollapsed = !serverCollapsed">
<span class="section-title">🖥 服务器</span>
<icon-down v-if="!serverCollapsed" class="section-toggle" />
@@ -17,7 +17,7 @@
<span class="col-metric">磁盘</span>
<span class="col-metric">CPU</span>
<span class="col-metric">内存</span>
<span class="col-action settings-btn" @click.stop="settingsOpen = !settingsOpen" title="管理">···</span>
<span class="col-action settings-btn" @click="toggleSettings($event)" title="管理">···</span>
</div>
<!-- 表格行 -->
<div
@@ -35,46 +35,17 @@
v-if="p.type !== 'local'"
class="col-action more-btn"
title="更多操作"
@click.stop="toggleMore(p.id)"
@click.stop="toggleMore(p, $event)"
>···</span>
<span v-else class="col-action"></span>
<!-- 更多操作子菜单 -->
<div v-if="moreOpenId === p.id" class="more-menu" @click.stop>
<div class="more-item" @click="handleEdit(p)">编辑</div>
<div class="more-item danger" @click="handleDelete(p)">删除</div>
</div>
</div>
</div>
</div>
<div v-if="settingsOpen" class="settings-panel" @click.stop>
<div class="settings-item" @click="$emit('openConnectionDialog'); settingsOpen = false">
<icon-plus /> 添加服务器
</div>
<div class="settings-item" @click="toggleAutoConnect">
<icon-check-circle :style="{ opacity: autoConnect ? 1 : 0.3 }" />
启动时自动连接所有服务器
</div>
<div class="settings-item" @click="toggleAutoRefresh">
<icon-check-circle :style="{ opacity: autoRefresh ? 1 : 0.3 }" />
自动刷新系统信息 (15s)
</div>
<div class="settings-divider" />
<div class="settings-label">显示服务器</div>
<div
v-for="p in profiles"
:key="'vis-' + p.id"
class="settings-item"
@click="toggleServerVisibility(p.id)"
>
<icon-check-circle :style="{ opacity: !hiddenServerIds.includes(String(p.id)) ? 1 : 0.3 }" />
{{ p.name }}
</div>
</div>
</div>
<!-- 收藏夹区块 -->
<div v-if="section === 'favorites' && showFavorites" class="sidebar-section fav-section">
<div v-if="section === 'favorites' && showFavorites" class="sidebar-section fav-section" :class="{ 'fav-collapsed': favCollapsed }">
<div class="section-header" @click="favCollapsed = !favCollapsed">
<span class="section-title"> 收藏夹</span>
<span class="section-count">{{ config.favoriteFiles.length }}</span>
@@ -159,6 +130,38 @@
</template>
</div>
</transition>
<!-- 更多操作子菜单 Teleport body -->
<Teleport to="body">
<div v-if="moreOpenId" class="sidebar-popup more-menu" :style="moreMenuStyle" @click.stop>
<div class="more-item" @click="handleEdit(moreProfile!)">编辑</div>
<div class="more-item danger" @click="handleDelete(moreProfile!)">删除</div>
</div>
<div v-if="settingsOpen" class="sidebar-popup settings-panel" :style="settingsPanelStyle" @click.stop>
<div class="settings-item" @click="$emit('openConnectionDialog'); settingsOpen = false">
<icon-plus /> 添加服务器
</div>
<div class="settings-item" @click="toggleAutoConnect">
<icon-check-circle :style="{ opacity: autoConnect ? 1 : 0.3 }" />
启动时自动连接所有服务器
</div>
<div class="settings-item" @click="toggleAutoRefresh">
<icon-check-circle :style="{ opacity: autoRefresh ? 1 : 0.3 }" />
自动刷新系统信息 (15s)
</div>
<div class="settings-divider" />
<div class="settings-label">显示服务器</div>
<div
v-for="p in profiles"
:key="'vis-' + p.id"
class="settings-item"
@click="toggleServerVisibility(p.id)"
>
<icon-check-circle :style="{ opacity: !hiddenServerIds.includes(String(p.id)) ? 1 : 0.3 }" />
{{ p.name }}
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
@@ -182,8 +185,8 @@ const props = defineProps<Props>()
// 折叠状态(组件内部,不污染父组件)
const serverCollapsed = ref(false)
const favCollapsed = ref(false)
const helpCollapsed = ref(false)
const favCollapsed = ref(true)
const helpCollapsed = ref(true)
// 侧边栏区块可见性(从配置读取)
const showServer = computed(() => configStore.appConfig.sidebarSections?.includes('server') ?? true)
@@ -192,13 +195,55 @@ const showHelp = computed(() => configStore.appConfig.sidebarSections?.includes(
// 区块排序
const sectionOrder = computed(() => configStore.appConfig.sidebarSections || ['server', 'favorites', 'help'])
// 管理设置
// === 弹出面板定位Teleport 到 bodyfixed 定位) ===
const settingsOpen = ref(false)
const settingsPanelStyle = ref<Record<string, string>>({})
const moreOpenId = ref<string | null>(null)
const moreProfile = ref<{ id: string; name: string } | null>(null)
const moreMenuStyle = ref<Record<string, string>>({})
const autoConnect = ref(localStorage.getItem('desk:autoConnect') !== 'false')
const autoRefresh = ref(localStorage.getItem('desk:autoRefresh') === 'true')
const hiddenServerIds = ref<string[]>(JSON.parse(localStorage.getItem('desk:hiddenServers') || '[]'))
let refreshTimer: ReturnType<typeof setInterval> | null = null
function calcPopupStyle(el: HTMLElement): Record<string, string> {
const rect = el.getBoundingClientRect()
return {
position: 'fixed',
top: `${rect.bottom + 2}px`,
right: `${window.innerWidth - rect.right}px`,
}
}
function toggleSettings(e: MouseEvent) {
if (settingsOpen.value) {
settingsOpen.value = false
return
}
moreOpenId.value = null
const btn = (e.target as HTMLElement).closest<HTMLElement>('.settings-btn')
if (!btn) return
settingsPanelStyle.value = calcPopupStyle(btn)
settingsOpen.value = true
}
function toggleMore(p: { id: string; name: string }, e: MouseEvent) {
if (moreOpenId.value === p.id) {
moreOpenId.value = null
moreProfile.value = null
return
}
settingsOpen.value = false
moreOpenId.value = p.id
moreProfile.value = p
const target = (e.target as HTMLElement).closest('.more-btn') as HTMLElement
if (target) {
moreMenuStyle.value = calcPopupStyle(target)
}
}
function toggleAutoConnect() {
autoConnect.value = !autoConnect.value
localStorage.setItem('desk:autoConnect', String(autoConnect.value))
@@ -230,13 +275,14 @@ onMounted(() => {
})
onUnmounted(() => stopAutoRefresh())
// 点击外部关闭更多菜单
// 点击外部关闭弹出面板
function handleClickOutside(e: MouseEvent) {
const el = e.target as HTMLElement
if (!el.closest('.server-table-row')) {
if (!el.closest('.sidebar-popup') && !el.closest('.more-btn')) {
moreOpenId.value = null
moreProfile.value = null
}
if (!el.closest('.settings-panel') && !el.closest('.settings-btn')) {
if (!el.closest('.sidebar-popup') && !el.closest('.settings-btn')) {
settingsOpen.value = false
}
}
@@ -246,7 +292,6 @@ onUnmounted(() => document.removeEventListener('click', handleClickOutside))
// 服务器 Profile 列表状态
const profiles = shallowRef(connectionManager.profiles)
const activeId = shallowRef(connectionManager.activeProfile?.id ?? '')
const moreOpenId = ref<string | null>(null)
const sysInfoMap = ref<Record<string, SystemInfo>>({})
const visibleProfiles = computed(() =>
@@ -289,6 +334,7 @@ const helpItems = [
{ key: 'Ctrl+B', desc: '切换侧边栏' },
{ key: 'Ctrl+H', desc: '历史记录' },
{ key: 'Ctrl+F', desc: '聚焦搜索' },
{ key: 'Ctrl+⇧+C~H', desc: '快速定位本机盘符' },
{ key: 'Click ⭐', desc: '收藏文件' },
{ key: 'Drag', desc: '排序收藏' },
]
@@ -428,6 +474,7 @@ function stateText(profileId: string): string {
async function handleSelect(p: { id: string; type: string }) {
moreOpenId.value = null
moreProfile.value = null
if (p.id === activeId.value) return
try {
await connectionManager.connect(p.id)
@@ -436,12 +483,9 @@ async function handleSelect(p: { id: string; type: string }) {
}
}
function toggleMore(id: string) {
moreOpenId.value = moreOpenId.value === id ? null : id
}
function handleEdit(p: { id: string }) {
moreOpenId.value = null
moreProfile.value = null
emit('openConnectionDialog', p.id)
}
@@ -449,6 +493,7 @@ function handleDelete(p: { id: string; name: string }) {
if (!window.confirm(`确定删除「${p.name}」?`)) return
connectionManager.removeProfile(p.id)
moreOpenId.value = null
moreProfile.value = null
}
</script>
@@ -476,11 +521,20 @@ function handleDelete(p: { id: string; name: string }) {
flex-shrink: 0;
}
/* 服务器区块不需要 max-height 限制 */
.sidebar-section:first-child > .section-content-wrap {
max-height: none;
}
/* 收藏夹区块弹性填充剩余空间 */
.fav-section {
flex: 1;
flex: 1 1 0;
min-height: 0;
overflow: hidden;
}
/* 收藏夹折叠时收缩为 header 高度 */
.fav-section.fav-collapsed {
flex: 0 0 auto;
}
/* 收藏夹 section-content-wrap 由 flex 控制高度 */
@@ -501,11 +555,7 @@ function handleDelete(p: { id: string; name: string }) {
flex-direction: column;
}
.sidebar-section.section-on-top {
z-index: 30;
}
/* 帮助区块固定在底部,不被推出窗口 */
/* 帮助区块固定在底部 */
.sidebar-section:last-child {
flex-shrink: 0;
}
@@ -544,15 +594,15 @@ function handleDelete(p: { id: string; name: string }) {
transition: transform 0.2s;
}
/* 区块折叠容器 - grid 动画精确匹配内容高度 */
/* 区块折叠容器 */
.section-content-wrap {
display: grid;
grid-template-rows: 1fr;
transition: grid-template-rows 0.2s ease;
overflow: hidden;
max-height: 500px;
transition: max-height 0.25s ease;
}
.section-content-wrap.collapsed {
grid-template-rows: 0fr;
max-height: 0;
}
.section-content-wrap > .section-content {
@@ -603,7 +653,6 @@ function handleDelete(p: { id: string; name: string }) {
background: var(--color-fill-1);
border-radius: 0 0 6px 6px;
border-left: 3px solid var(--color-primary-6);
overflow: visible;
}
/* 表头 */
@@ -624,7 +673,6 @@ function handleDelete(p: { id: string; name: string }) {
/* 表格行 */
.server-table-row {
position: relative;
display: flex;
align-items: center;
padding: 4px 4px;
@@ -695,76 +743,19 @@ function handleDelete(p: { id: string; name: string }) {
}
.server-table-row:hover .more-btn { opacity: 1; }
/* 更多操作子菜单 */
.more-menu {
position: absolute;
right: 4px;
top: 100%;
min-width: 80px;
background: var(--color-bg-popup);
border: 1px solid var(--color-border);
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
z-index: 10;
padding: 2px 0;
}
.more-item {
padding: 5px 12px;
font-size: 13px;
cursor: pointer;
transition: background 0.15s;
}
.more-item:hover { background: var(--color-fill-1); }
.more-item.danger { color: var(--color-danger-6); }
/* 设置按钮 */
.settings-btn {
cursor: pointer;
font-size: 14px;
color: var(--color-text-3);
padding: 0 2px;
padding: 3px 4px;
line-height: 1;
letter-spacing: 1px;
transition: color 0.15s;
overflow: visible;
}
.settings-btn:hover { color: var(--color-primary-6); }
/* 设置面板 — 绝对定位浮在按钮下方 */
.settings-panel {
position: absolute;
right: 4px;
top: 50px;
z-index: 20;
min-width: 200px;
background: var(--color-bg-popup);
border: 1px solid var(--color-border);
border-radius: 6px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
padding: 2px 0;
}
.settings-item {
display: flex;
align-items: center;
gap: 8px;
padding: 7px 12px;
font-size: 13px;
cursor: pointer;
transition: background 0.12s;
color: var(--color-text-2);
}
.settings-item:hover { background: var(--color-fill-1); }
.settings-divider {
height: 1px;
background: var(--color-border-2);
margin: 4px 12px;
}
.settings-label {
padding: 4px 12px 2px;
font-size: 11px;
color: var(--color-text-3);
user-select: none;
}
/* 区块操作图标 */
.section-action {
margin-left: auto;
@@ -894,3 +885,67 @@ function handleDelete(p: { id: string; name: string }) {
opacity: 0;
}
</style>
<!-- scopedTeleport body 的面板样式 -->
<style>
/* 弹出面板公共 */
.sidebar-popup {
z-index: 9999;
min-width: 80px;
background: var(--color-bg-popup) !important;
border: 1px solid var(--color-border);
border-radius: 4px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
padding: 2px 0;
color: var(--color-text-2);
}
.sidebar-popup.more-menu {
min-width: 80px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.sidebar-popup.settings-panel {
min-width: 200px;
border-radius: 6px;
}
.more-item {
padding: 5px 12px;
font-size: 13px;
cursor: pointer;
color: var(--color-text-2) !important;
transition: background 0.15s;
}
.more-item:hover { background: var(--color-fill-1); }
.more-item.danger { color: var(--color-danger-6) !important; }
.more-item svg { color: inherit !important; fill: currentColor; }
.settings-item {
display: flex;
align-items: center;
gap: 8px;
padding: 7px 12px;
font-size: 13px;
cursor: pointer;
transition: background 0.12s;
color: var(--color-text-2) !important;
}
.settings-item svg {
color: var(--color-text-2) !important;
fill: currentColor;
}
.settings-item:hover { background: var(--color-fill-1); }
.settings-divider {
height: 1px;
background: var(--color-border-2);
margin: 4px 12px;
}
.settings-label {
padding: 4px 12px 2px;
font-size: 11px;
color: var(--color-text-3);
user-select: none;
}
</style>

View File

@@ -137,7 +137,7 @@
</template>
<script setup lang="ts">
import { nextTick } from 'vue'
import { nextTick, provide, ref } from 'vue'
import { IconForward, IconHistory, IconRefresh, IconMenu, IconClose, IconRight, IconCopy, IconCheck } from '@arco-design/web-vue/es/icon'
import type { ToolbarConfig } from '@/types/file-system'
import PathBreadcrumb from './PathBreadcrumb.vue'
@@ -145,6 +145,10 @@ import { useClipboardCopy } from '../composables/useClipboardCopy'
import ConnectionIndicator from './ConnectionIndicator.vue'
import ConnectionDialog from './ConnectionDialog.vue'
// 面包屑/连接指示器下拉互斥:任一方打开时关闭另一方
const dropdownOwner = ref<'connection' | 'breadcrumb' | null>(null)
provide('dropdownOwner', dropdownOwner)
// Props
interface Props {
config: ToolbarConfig

View File

@@ -60,7 +60,10 @@
/>
<!-- 分隔条 -->
<div class="resizer" @mousedown="handleHorizontalResize"></div>
<div class="resizer" @mousedown="handleResizeWithOverlay"></div>
<!-- 拖拽遮罩防止 iframe 捕获鼠标事件 -->
<div v-if="isResizing" class="resize-overlay"></div>
<!-- 文件编辑器面板始终显示无选中文件时为空白预览区 -->
<FileEditorPanel
@@ -116,7 +119,8 @@
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
import { getPathSeparator } from '@/utils/fileUtils'
import { Message, Modal } from '@arco-design/web-vue'
import { marked, renderMermaidDiagrams, rerenderMermaidDiagrams, setCurrentFileDir, setFileServerBase } from '@/utils/markedExtensions'
import { On, Off } from '@wailsio/events'
import { renderMarkdown, parseMarkdown, isMarkedReady, preloadMarked, renderMermaidDiagrams, rerenderMermaidDiagrams, setCurrentFileDir, setFileServerBase } from '@/utils/markedExtensions'
import { useThemeStore } from '@/stores/theme'
// 导入子组件
@@ -138,7 +142,7 @@ import { useMultiPreview, type PreviewTab, isDirty } from './composables/useMult
// 导入工具函数
import { getFileName, sortFileList } from '@/utils/fileUtils'
import { isImageFile, isVideoFile, isAudioFile, isPdfFile, isHtmlFile, isMarkdownFile, isExcelFile, isWordFile, isCsvFile } from '@/utils/fileTypeHelpers'
import { listDir, saveBase64File } from '@/api/system'
import { listDir, saveBase64File, watchFile, unwatchFile } from '@/api/system'
import { connectionManager } from '@/api/connection-manager'
import { STORAGE_KEYS, DEFAULTS, UI_TEXT, VALIDATION_RULES, FILE_EXTENSIONS, FILE_SIZE_THRESHOLDS } from '@/utils/constants'
import { createResizeHandler } from '@/utils/resize'
@@ -356,36 +360,52 @@ const fileListPanelConfig = computed(() => ({
editingFileName: editingFileName.value
}))
// 计算渲染内容
// Markdown 渲染状态(异步加载 marked 后触发重渲染)
const markdownRendered = ref('')
const setupMarkdownContext = () => {
const fullPath = selectedFileItem.value?.path || ''
const dir = fullPath ? fullPath.replace(/[/\\][^/\\]+$/, '') : (filePath.value || '')
setCurrentFileDir(dir)
const isRemote = connectionManager.isRemote()
const base = resolveFileServerBase()
setFileServerBase(isRemote ? base : base + '/localfs')
}
const computeRendered = computed(() => {
const currentFileName = selectedFileItem.value?.name || ''
if (isHtmlFile(currentFileName)) {
return fileContent.value || ''
} else if (isMarkdownFile(currentFileName)) {
// 使用配置好的 marked 渲染 Markdown支持 mermaid + 图片相对路径转换)
try {
const content = fileContent.value || ''
// 设置图片路径转换所需的上下文renderer.image 钩子中读取)
// dir: 当前 md 文件所在目录(从文件完整路径中去掉文件名)
const fullPath = selectedFileItem.value?.path || ''
const dir = fullPath ? fullPath.replace(/[/\\][^/\\]+$/, '') : (filePath.value || '')
setCurrentFileDir(dir)
// 设置文件服务器 Base URL
const isRemote = connectionManager.isRemote()
const base = resolveFileServerBase()
setFileServerBase(isRemote ? base : base + '/localfs')
return marked.parse(content) as string
} catch (error) {
console.error('Markdown 解析失败:', error)
return fileContent.value || ''
if (isMarkedReady()) {
try {
setupMarkdownContext()
return parseMarkdown(fileContent.value || '')
} catch (error) {
console.error('Markdown 解析失败:', error)
return fileContent.value || ''
}
}
// marked 未就绪时返回上次渲染结果或原文
preloadMarked()
return markdownRendered.value || fileContent.value || ''
}
return ''
})
// marked 加载完成后异步渲染并缓存
watch([() => selectedFileItem.value?.name, fileContent], async () => {
const currentFileName = selectedFileItem.value?.name || ''
if (!isMarkdownFile(currentFileName)) return
if (isMarkedReady()) return // 同步 computed 已处理
try {
setupMarkdownContext()
const html = await renderMarkdown(fileContent.value || '')
markdownRendered.value = html
} catch (e) {
console.error('Markdown 异步渲染失败:', e)
}
})
// 文件编辑器面板配置
const fileEditorPanelConfig = computed(() => {
const currentFileName = selectedFileItem.value?.name || ''
@@ -440,17 +460,38 @@ const handleRefresh = async () => {
await loadDirectory(filePath.value)
}
// 文件变化事件处理:自动刷新预览(有未保存修改时跳过)
const handleFileChanged = async (event: any) => {
// Wails v3 On 回调参数是 WailsEvent 对象,路径在 event.data
const changedPath = event?.data ?? ''
if (!changedPath || !selectedFileItem.value) return
// 标准化路径比较
const normalize = (p: string) => p.replace(/\\/g, '/').toLowerCase().replace(/\/+$/, '')
if (normalize(changedPath) !== normalize(selectedFileItem.value.path)) return
// 有未保存修改时不覆盖用户编辑
if (originalContent.value !== undefined && fileContent.value !== originalContent.value) return
await loadFileContent(changedPath)
Message.success('文件已更新')
}
// 程序化切换 profile 时抑制自动导航(如打开收藏/tab切换
let _suppressAutoNav = false
let _lastActiveId = connectionManager.activeProfile?.id
// 连接切换后重置路径并刷新文件列表
// 连接切换后重置路径并刷新文件列表(仅在活跃连接实际变化时触发)
connectionManager.onStateChange(async (state) => {
if (state === 'connected' && !_suppressAutoNav) {
await loadCommonPaths()
const targetPath = connectionManager.isRemote() ? '/' : 'C:/'
filePath.value = targetPath
await loadDirectory(targetPath)
}
const currentId = connectionManager.activeProfile?.id
if (state !== 'connected') return
if (currentId === _lastActiveId) return
_lastActiveId = currentId
if (_suppressAutoNav) return
await loadCommonPaths()
const targetPath = connectionManager.isRemote() ? '/' : 'C:/'
filePath.value = targetPath
await loadDirectory(targetPath)
})
const handleSearchKeywordUpdate = (keyword: string) => {
@@ -580,6 +621,8 @@ const handleOpenFavorite = async (file: FavoriteFile) => {
await loadCommonPaths()
} catch (e) {
console.error('切换连接失败:', e)
Message.error(`无法连接: ${e instanceof Error ? e.message : String(e)}`)
return
} finally {
_suppressAutoNav = false
}
@@ -600,7 +643,6 @@ const handleOpenFavorite = async (file: FavoriteFile) => {
await connectionManager.connect(remoteProfile.id)
}
} else {
// disconnect() 也会触发 onStateChange需要 suppress
connectionManager.disconnect()
}
await loadCommonPaths()
@@ -613,7 +655,6 @@ const handleOpenFavorite = async (file: FavoriteFile) => {
if (file.isDir) {
await navigate(file.path)
} else {
// 先导航到父目录,再选中文件
const parentPath = file.path.substring(0, Math.max(file.path.lastIndexOf('/'), file.path.lastIndexOf('\\')))
if (parentPath && parentPath !== filePath.value) {
await navigate(parentPath)
@@ -1222,8 +1263,11 @@ const selectFile = async (path: string) => {
}
const loadFileContent = async (path: string) => {
// 切换文件时先取消前一个文件的监听
unwatchFile().catch(() => {})
try {
updatePreviewUrl(path)
await updatePreviewUrl(path)
const fileName = getFileName(path)
const fileItem = selectedFileItem.value
@@ -1278,6 +1322,7 @@ const loadFileContent = async (path: string) => {
// 其他情况:加载为文本文件
await loadFile(path)
watchFile(path).catch(() => {})
} catch (error) {
Message.error(`读取文件失败: ${error}`)
}
@@ -1396,6 +1441,8 @@ const extractZipTextAndRead = async (zipPath: string, filePath: string): Promise
}
}
const isResizing = ref(false)
const handleHorizontalResize = createResizeHandler(
() => workspaceRef.value,
() => panelWidth.value.left,
@@ -1406,14 +1453,29 @@ const handleHorizontalResize = createResizeHandler(
onResize: (percent) => {
panelWidth.value = { left: percent, right: 100 - percent }
},
onResizeEnd: () => savePanelWidth(panelWidth.value),
onResizeEnd: () => {
isResizing.value = false
savePanelWidth(panelWidth.value)
},
}
)
// mousedown 时即激活遮罩,防止 iframe 捕获鼠标
const handleResizeWithOverlay = (e: MouseEvent) => {
isResizing.value = true
handleHorizontalResize(e)
}
// ========== 生命周期 ==========
onMounted(async () => {
// 加载系统路径(阻塞,确保快捷入口就绪)
// 1. 先注册全局事件,不阻塞渲染
window.addEventListener('keydown', handleKeyDown)
window.addEventListener('click', hideContextMenu)
window.addEventListener('paste', handlePaste)
On('file-changed', handleFileChanged)
// 2. 加载系统路径
await loadCommonPaths()
// SFTP 连接是异步的,未就绪时跳过初始加载,由 onStateChange('connected') 触发
@@ -1422,10 +1484,9 @@ onMounted(async () => {
return
}
// 初始化加载:远程模式强制用根路径,避免 localStorage 残留 Windows 路径
// 3. 加载目录列表
const startPath = connectionManager.isRemote() ? '/'
: (commonPaths.value.length > 0 ? commonPaths.value[0].path : 'C:/')
// 本地模式下只恢复 Windows 路径,跳过 Linux/OSS 路径残留
const isLocalPath = filePath.value && /^[A-Za-z]:/.test(filePath.value)
if (isLocalPath && !connectionManager.isRemote()) {
await loadDirectory(filePath.value)
@@ -1434,10 +1495,16 @@ onMounted(async () => {
await loadDirectory(startPath)
}
// 恢复多文件预览会话
// 4. 会话恢复延迟到下一帧,先让界面渲染出来
requestAnimationFrame(() => {
restoreSession()
})
})
// 会话恢复(不阻塞首屏)
async function restoreSession() {
const session = multiPreview.restoreSession()
if (session.paths.length > 0) {
// 找到激活 tab 的 profileId先切换到该连接
const activeId = session.activePath
? session.profileMap.get(session.activePath.replace(/\\/g, '/').toLowerCase())
: undefined
@@ -1456,7 +1523,6 @@ onMounted(async () => {
multiPreview.applyUnsavedContent(tab, session.unsavedMap)
}
// 加载激活 tab 的内容
const active = multiPreview.activeTab.value
if (active) {
selectedFileItem.value = active.fileItem
@@ -1468,7 +1534,6 @@ onMounted(async () => {
}
}
} else {
// 无会话记录,回退到旧逻辑:恢复上次打开的单个文件
const lastFile = localStorage.getItem(STORAGE_KEYS.FILESYSTEM.LAST_OPENED_FILE)
if (lastFile) {
const normalized = lastFile.replace(/\\/g, '/').replace(/\/+$/, '')
@@ -1482,19 +1547,14 @@ onMounted(async () => {
}
}
}
// 添加键盘快捷键
window.addEventListener('keydown', handleKeyDown)
window.addEventListener('click', hideContextMenu)
// 添加粘贴事件监听(剪贴板图片)
window.addEventListener('paste', handlePaste)
})
}
onUnmounted(() => {
window.removeEventListener('keydown', handleKeyDown)
window.removeEventListener('click', hideContextMenu)
window.removeEventListener('paste', handlePaste)
Off('file-changed')
unwatchFile()
// 应用关闭时立即持久化当前会话
cacheCurrentTabState()
multiPreview.persistSession()
@@ -1528,7 +1588,7 @@ const handleKeyDown = async (event: KeyboardEvent) => {
return
}
// Ctrl+Shift+C/D/E/F/G/H 快速打开对应盘符
// Ctrl+Shift+C/D/E/F/G/H 快速定位到本机对应盘符
if ((event.ctrlKey || event.metaKey) && event.shiftKey) {
const driveLetter = event.key.toUpperCase()
if (['C', 'D', 'E', 'F', 'G', 'H'].includes(driveLetter)) {
@@ -1536,6 +1596,16 @@ const handleKeyDown = async (event: KeyboardEvent) => {
const drivePath = `${driveLetter}:\\`
isNavigating.value = true
try {
// 如果当前在远程连接,先切回本地
if (connectionManager.isRemote()) {
_suppressAutoNav = true
try {
await connectionManager.connect('local-default')
} finally {
_suppressAutoNav = false
}
await loadCommonPaths()
}
await navigate(drivePath)
} finally {
isNavigating.value = false
@@ -1772,6 +1842,7 @@ watch(() => themeStore.isDark, async () => {
flex: 1;
display: flex;
overflow: hidden;
position: relative;
}
.resizer {
@@ -1785,4 +1856,11 @@ watch(() => themeStore.isDark, async () => {
.resizer:hover {
background: var(--color-primary-light-1);
}
.resize-overlay {
position: absolute;
inset: 0;
z-index: 100;
cursor: col-resize;
}
</style>

View File

@@ -5,7 +5,7 @@
</template>
<script>
import { marked } from '@/utils/markedExtensions'
import { parseMarkdown, renderMarkdown, isMarkedReady, preloadMarked } from '@/utils/markedExtensions'
function sanitizeHtml(html) {
return html
@@ -27,9 +27,27 @@ export default {
default: ''
}
},
data() {
return { rendered: '' }
},
computed: {
renderedMarkdown() {
return sanitizeHtml(marked(this.content))
if (isMarkedReady()) {
return sanitizeHtml(parseMarkdown(this.content))
}
preloadMarked()
return sanitizeHtml(this.rendered || this.content)
}
},
watch: {
content: {
immediate: true,
async handler(val) {
if (!isMarkedReady()) {
const html = await renderMarkdown(val)
this.rendered = sanitizeHtml(html)
}
}
}
}
}

View File

@@ -6,7 +6,7 @@
import { computed, watch, onMounted, onUnmounted, h, nextTick } from 'vue'
import { Modal, Message, Progress } from '@arco-design/web-vue'
import { useUpdateStore } from '../stores/update'
import { marked } from '../utils/markedExtensions'
import { parseMarkdown, isMarkedReady, preloadMarked } from '../utils/markedExtensions'
import { sanitizeHtml } from '@/utils/fileUtils'
import { DownloadUpdate } from '../wailsjs/v3-bindings/u-desk/app'
import { On, Off } from '@wailsio/events'
@@ -80,7 +80,7 @@ const showUpdateModal = () => {
// 更新日志
if (changelog.value) {
const changelogHtml = (() => { try { return sanitizeHtml(String(marked.parse(changelog.value))) } catch { return changelog.value } })()
const changelogHtml = (() => { try { if (isMarkedReady()) return sanitizeHtml(String(parseMarkdown(changelog.value))); preloadMarked(); return sanitizeHtml(changelog.value) } catch { return changelog.value } })()
elements.push(
h('div', { style: { marginBottom: '8px' } }, [
h('div', { style: { fontSize: '12px', color: 'var(--color-text-2)', marginBottom: '4px' } }, '更新内容:'),

View File

@@ -118,7 +118,7 @@ import { Message, Modal } from '@arco-design/web-vue'
import { storeToRefs } from 'pinia'
import { IconHistory } from '@arco-design/web-vue/es/icon'
import { useUpdateStore } from '../stores/update'
import { marked } from '../utils/markedExtensions'
import { parseMarkdown, isMarkedReady, preloadMarked } from '../utils/markedExtensions'
import { sanitizeHtml } from '@/utils/fileUtils'
import { GetCurrentVersion, GetUpdateConfig, InstallUpdate } from '../wailsjs/v3-bindings/u-desk/app'
import { On, Off } from '@wailsio/events'
@@ -141,7 +141,7 @@ const downloadedFile = ref(null)
/** 渲染 changelogMarkdown → HTML */
function renderChangelog(text: string): string {
if (!text) return ''
try { return sanitizeHtml(marked.parse(text) as string) } catch { return text }
try { if (isMarkedReady()) return sanitizeHtml(parseMarkdown(text)); preloadMarked(); return sanitizeHtml(text) } catch { return text }
}
// 加载当前版本

View File

@@ -1,20 +1,176 @@
import { marked } from 'marked'
import hljs from 'highlight.js'
import 'highlight.js/lib/common'
// 按需导入 common 包不包含的语言
import 'highlight.js/lib/languages/powershell'
import 'highlight.js/lib/languages/dos'
import 'highlight.js/lib/languages/autohotkey'
import 'highlight.js/lib/languages/latex'
import 'highlight.js/lib/languages/dockerfile'
import 'highlight.js/lib/languages/cmake'
import 'highlight.js/lib/languages/scala'
import 'highlight.js/lib/languages/dart'
import { getHljsLanguage } from './languageMap'
let mermaidInstance: typeof import('mermaid').default | null = null
let mermaidTheme: string | null = null
// 懒加载 marked + hljs首次调用时才初始化
let _marked: typeof import('marked').marked | null = null
let _initPromise: Promise<typeof import('marked').marked> | null = null
async function ensureMarked(): Promise<typeof import('marked').marked> {
if (_marked) return _marked
if (_initPromise) return _initPromise
_initPromise = (async () => {
const [markedModule, hljs, ...extras] = await Promise.all([
import('marked'),
import('highlight.js'),
import('highlight.js/lib/languages/powershell'),
import('highlight.js/lib/languages/dos'),
import('highlight.js/lib/languages/autohotkey'),
import('highlight.js/lib/languages/latex'),
import('highlight.js/lib/languages/dockerfile'),
import('highlight.js/lib/languages/cmake'),
import('highlight.js/lib/languages/scala'),
import('highlight.js/lib/languages/dart'),
])
// 注册额外语言到 hljs
const langNames = ['powershell', 'dos', 'autohotkey', 'latex', 'dockerfile', 'cmake', 'scala', 'dart']
extras.forEach((mod, i) => {
hljs.default.registerLanguage(langNames[i], (mod as any).default)
})
const marked = markedModule.marked
const renderer = new marked.Renderer()
renderer.code = function(token: any) {
if (token.lang === 'mermaid') {
return `<pre class="mermaid">${token.text}</pre>`
}
const lang = getHljsLanguage(token.lang)
let highlighted: string
try {
highlighted = hljs.default.highlight(token.text, { language: lang }).value
} catch {
highlighted = token.text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
}
return `<pre><code class="hljs language-${lang}" data-theme="auto">${highlighted}</code></pre>`
}
renderer.heading = function(token: any) {
const raw = token.raw || ''
const depth = token.depth || 1
const text = token.text || ''
const id = raw
.toLowerCase()
.replace(/[^一-龥a-z0-9\s-]/g, '')
.trim()
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.replace(/^-+|-+$/g, '') || `heading-${Math.random().toString(36).slice(2, 11)}`
return `<h${depth} id="${id}" class="heading">
${text}<a href="#${id}" class="heading-anchor" aria-hidden="true" title="跳转到此标题">#</a>
</h${depth}>`
}
// 图片/链接渲染器
renderer.image = createImageRenderer()
renderer.link = createLinkRenderer()
marked.use({ renderer, breaks: true, gfm: true, async: false })
_marked = marked
return marked
})()
return _initPromise
}
// ========== 图片相对路径转换支持 ==========
let _currentFileDir: string = ''
let _fileServerBase: string = 'http://localhost:2652/localfs'
export function setCurrentFileDir(dir: string): void {
_currentFileDir = dir
}
export function getCurrentFileDir(): string {
return _currentFileDir
}
export function setFileServerBase(base: string): void {
_fileServerBase = base
}
function resolveImageUrl(src: string, fileServerBase: string): string {
if (!src) return src
if (/^(?:[a-zA-Z]:[/\\]|\/(?:[^/]|$)|https?:|ftp:|data:|#)/i.test(src)) return src
const dir = _currentFileDir || '/'
const sep = dir.includes('\\') ? '\\' : '/'
let resolved = normalizeRelativePath(dir, src, sep)
const encoded = encodeURIComponent(resolved).replace(/%2F/gi, '/').replace(/%5C/gi, '\\')
return `${fileServerBase}/${encoded}`
}
function normalizeRelativePath(base: string, relative: string, sep: string): string {
let baseNormalized = base.replace(/[\\/]+$/, '')
if (!baseNormalized) baseNormalized = sep === '/' ? '/' : 'C:\\'
const baseParts = baseNormalized.split(sep).filter(Boolean)
const relParts = relative.split(/[\\/]/).filter(Boolean)
for (const part of relParts) {
if (part === '..') {
baseParts.pop()
} else if (part !== '.') {
baseParts.push(part)
}
}
if (/^[a-zA-Z]:$/i.test(baseNormalized.split(sep)[0] || '')) {
return baseParts.join(sep)
}
return sep + baseParts.join(sep)
}
const isLocalFileLink = (href: string): boolean => {
if (!href) return false
if (/^(https?|ftp|mailto|tel|data):/i.test(href)) return false
if (href.startsWith('#')) return false
return true
}
function createImageRenderer() {
return function(token: any) {
const src = token.href || ''
const title = token.title || ''
const alt = token.text || ''
const titleAttr = title ? ` title="${title}"` : ''
if (_currentFileDir && !/^(?:[a-zA-Z]:[/\\]|\/(?:[^/]|$)|https?:|ftp:|data:|#)/i.test(src)) {
const resolvedSrc = resolveImageUrl(src, _fileServerBase)
return `<img src="${resolvedSrc}" alt="${alt}"${titleAttr}>`
}
return `<img src="${src}" alt="${alt}"${titleAttr}>`
}
}
function createLinkRenderer() {
return function(this: any, token: any) {
const href = token.href || ''
const text = this.parser.parseInline(token.tokens) || token.text || ''
const title = token.title || ''
const titleAttr = title ? ` title="${title}"` : ''
if (href.startsWith('#')) {
return `<a href="${href}${titleAttr}">${text}</a>`
}
if (isLocalFileLink(href)) {
return `<a href="javascript:void(0)" data-local-link="${href}" class="local-file-link"${titleAttr}>${text}</a>`
}
return `<a href="${href}" target="_blank" rel="noopener noreferrer"${titleAttr}>${text}</a>`
}
}
// 检测当前是否为暗色主题
function isDarkTheme(): boolean {
if (typeof document === 'undefined') return false
@@ -64,167 +220,31 @@ async function loadMermaid() {
return mermaidInstance
}
const renderer = new marked.Renderer()
renderer.code = function(token: any) {
if (token.lang === 'mermaid') {
return `<pre class="mermaid">${token.text}</pre>`
}
const lang = getHljsLanguage(token.lang)
let highlighted: string
try {
highlighted = hljs.highlight(token.text, { language: lang }).value
} catch {
highlighted = token.text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
}
return `<pre><code class="hljs language-${lang}" data-theme="auto">${highlighted}</code></pre>`
/** 同步渲染 Markdown需先调用 ensureMarked 初始化) */
export async function renderMarkdown(content: string): Promise<string> {
const marked = await ensureMarked()
return marked.parse(content) as string
}
renderer.heading = function(token: any) {
const raw = token.raw || ''
const depth = token.depth || 1
const text = token.text || ''
const id = raw
.toLowerCase()
.replace(/[^\u4e00-\u9fa5a-z0-9\s-]/g, '')
.trim()
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.replace(/^-+|-+$/g, '') || `heading-${Math.random().toString(36).slice(2, 11)}`
return `<h${depth} id="${id}" class="heading">
${text}<a href="#${id}" class="heading-anchor" aria-hidden="true" title="跳转到此标题">#</a>
</h${depth}>`
/** 检查 marked 是否已初始化(同步渲染用) */
export function isMarkedReady(): boolean {
return _marked !== null
}
// ========== 图片相对路径转换支持 ==========
// 当前 Markdown 文件所在目录(由调用方在渲染前设置)
let _currentFileDir: string = ''
// 文件服务器 Base URL由调用方在渲染前设置
let _fileServerBase: string = 'http://localhost:2652/localfs'
/**
* 设置当前 Markdown 文件所在目录(用于图片相对路径→文件服务器 URL 转换)
* @param dir 文件所在目录的绝对路径,如 "D:/docs" 或 "/"(根目录)
*/
export function setCurrentFileDir(dir: string): void {
_currentFileDir = dir
/** 同步渲染(仅当已初始化时可用) */
export function parseMarkdown(content: string): string {
if (!_marked) return ''
return _marked.parse(content) as string
}
/** 获取当前设置的文件目录 */
export function getCurrentFileDir(): string {
return _currentFileDir
/** 触发懒加载(可在空闲时调用) */
export function preloadMarked(): void {
ensureMarked().catch(() => {})
}
/**
* 设置文件服务器 Base URL用于图片相对路径转换
* @param base 完整的 base URL 前缀,如 "http://localhost:2652/localfs" 或 "https://host:port/api/v1/proxy/localfs"
*/
export function setFileServerBase(base: string): void {
_fileServerBase = base
}
/**
* 将相对路径图片 src 解析为文件服务器 URL
* - 绝对路径Windows: D:/...、Unix: /usr/...、网络URL、data URI → 不转换
* - 相对路径 → 基于当前文件目录解析为绝对路径,再编码为文件服务器 URL
*/
function resolveImageUrl(src: string, fileServerBase: string): string {
if (!src) return src
// 不转换绝对路径Windows 盘符、网络协议、锚点、data URI
if (/^(?:[a-zA-Z]:[/\\]|\/(?:[^/]|$)|https?:|ftp:|data:|#)/i.test(src)) return src
// 解析相对路径(处理 ../ 和 ./
const dir = _currentFileDir || '/'
const sep = dir.includes('\\') ? '\\' : '/'
let resolved = normalizeRelativePath(dir, src, sep)
// 编码路径(保留 / 分隔符)
const encoded = encodeURIComponent(resolved).replace(/%2F/gi, '/').replace(/%5C/gi, '\\')
return `${fileServerBase}/${encoded}`
}
/**
* 规范化相对路径,处理 .. 和 . 段
*/
function normalizeRelativePath(base: string, relative: string, sep: string): string {
// 确保基础路径不以分隔符结尾
let baseNormalized = base.replace(/[\\/]+$/, '')
if (!baseNormalized) baseNormalized = sep === '/' ? '/' : 'C:\\'
const baseParts = baseNormalized.split(sep).filter(Boolean)
const relParts = relative.split(/[\\/]/).filter(Boolean)
for (const part of relParts) {
if (part === '..') {
baseParts.pop() // 向上一级
} else if (part !== '.') {
baseParts.push(part)
}
}
// 重建路径Windows 绝对路径保留盘符前缀
if (/^[a-zA-Z]:$/i.test(baseNormalized.split(sep)[0] || '')) {
return baseParts.join(sep)
}
// Unix 风格:以 / 开头
return sep + baseParts.join(sep)
}
// 判断是否为本地文件链接(相对路径或本地绝对路径)
const isLocalFileLink = (href: string): boolean => {
if (!href) return false
if (/^(https?|ftp|mailto|tel|data):/i.test(href)) return false
if (href.startsWith('#')) return false
return true
}
// 自定义图片渲染器 - 转换相对路径为文件服务器 URL
renderer.image = function(token: any) {
const src = token.href || ''
const title = token.title || ''
const alt = token.text || ''
const titleAttr = title ? ` title="${title}"` : ''
// 判断是否需要转换(仅处理相对路径,且当前目录已设置)
if (_currentFileDir && !/^(?:[a-zA-Z]:[/\\]|\/(?:[^/]|$)|https?:|ftp:|data:|#)/i.test(src)) {
const resolvedSrc = resolveImageUrl(src, _fileServerBase)
return `<img src="${resolvedSrc}" alt="${alt}"${titleAttr}>`
}
// 默认渲染(绝对路径 / 网络 URL / data URI / 未设置目录时原样输出)
return `<img src="${src}" alt="${alt}"${titleAttr}>`
}
// 自定义链接渲染器 - 支持本地文件链接
renderer.link = function(token: any) {
const href = token.href || ''
const text = this.parser.parseInline(token.tokens) || token.text || ''
const title = token.title || ''
const titleAttr = title ? ` title="${title}"` : ''
if (href.startsWith('#')) {
return `<a href="${href}${titleAttr}">${text}</a>`
}
if (isLocalFileLink(href)) {
return `<a href="javascript:void(0)" data-local-link="${href}" class="local-file-link"${titleAttr}>${text}</a>`
}
return `<a href="${href}" target="_blank" rel="noopener noreferrer"${titleAttr}>${text}</a>`
}
marked.use({ renderer, breaks: true, gfm: true, async: false })
export { marked }
export async function renderMermaidDiagrams() {
const mermaid = await loadMermaid()
if (mermaid) {
// 渲染前保存原始源码textContent 在 SVG 渲染后会变成 CSS 垃圾)
document.querySelectorAll('.mermaid:not([data-mermaid-src])').forEach(pre => {
;(pre as HTMLElement).setAttribute('data-mermaid-src', pre.textContent || '')
})
@@ -232,9 +252,7 @@ export async function renderMermaidDiagrams() {
}
}
/** 清除已渲染内容并重新渲染(用于主题切换后刷新) */
export async function rerenderMermaidDiagrams(container?: HTMLElement | null) {
// 强制重新加载(清除缓存,让下次 loadMermaid 重新初始化新主题)
mermaidInstance = null
mermaidTheme = null
@@ -242,7 +260,6 @@ export async function rerenderMermaidDiagrams(container?: HTMLElement | null) {
target.querySelectorAll('.mermaid').forEach(pre => {
const el = pre as HTMLElement
if (el.getAttribute('data-processed')) {
// 从保存的原始源码恢复,而非 textContentSVG 的 textContent 是 CSS 垃圾)
el.innerHTML = el.getAttribute('data-mermaid-src') || ''
el.removeAttribute('data-processed')
}

View File

@@ -52,52 +52,52 @@ export function createResizeHandler(
const container = getContainer()
if (!container) return
// 初始值pixels 模式下从 getter 获取像素值percent 模式下获取百分比
// mousedown 时缓存容器尺寸,避免每帧 getBoundingClientRect 强制回流
const cachedRect = container.getBoundingClientRect()
const containerSize = isHorizontal ? cachedRect.width : cachedRect.height
// 初始值
let startValue = getInitialPercentage()
if (usePixels) {
// pixels 模式:将初始像素值转换为百分比用于拖拽计算
const rect = container.getBoundingClientRect()
const containerSize = isHorizontal ? rect.width : rect.height
if (containerSize > 0) {
startValue = (startValue / containerSize) * 100
}
}
const minPercentFromPixels = containerSize > 0 ? (minPixels / containerSize) * 100 : 0
let rafId = 0
const handleMouseMove = (moveEvent: MouseEvent) => {
const currentRect = container.getBoundingClientRect()
let rawValue: number
cancelAnimationFrame(rafId)
rafId = requestAnimationFrame(() => {
let rawValue: number
if (isHorizontal) {
rawValue = ((moveEvent.clientX - currentRect.left) / currentRect.width) * 100
} else {
rawValue = ((moveEvent.clientY - currentRect.top) / currentRect.height) * 100
}
if (isHorizontal) {
rawValue = ((moveEvent.clientX - cachedRect.left) / containerSize) * 100
} else {
rawValue = ((moveEvent.clientY - cachedRect.top) / containerSize) * 100
}
const minPercentFromPixels = (minPixels / (isHorizontal ? currentRect.width : currentRect.height)) * 100
const clamped = Math.max(
Math.max(minPercent, minPercentFromPixels),
Math.min(maxPercent, rawValue)
)
const clamped = Math.max(
Math.max(minPercent, minPercentFromPixels),
Math.min(maxPercent, rawValue)
)
if (usePixels) {
// 转回像素值传给回调
const containerSize = isHorizontal ? currentRect.width : currentRect.height
onResize?.(Math.round((clamped / 100) * containerSize))
} else {
onResize?.(clamped)
}
if (usePixels) {
onResize?.(Math.round((clamped / 100) * containerSize))
} else {
onResize?.(clamped)
}
})
}
const handleMouseUp = () => {
cancelAnimationFrame(rafId)
document.removeEventListener('mousemove', handleMouseMove)
document.removeEventListener('mouseup', handleMouseUp)
document.body.classList.remove('resizing')
if (usePixels) {
// pixels 模式传回当前像素值onResize 已更新,读 getter 获取最新值)
onResizeEnd?.(getInitialPercentage())
} else {
onResizeEnd?.(getInitialPercentage())
}
onResizeEnd?.(getInitialPercentage())
}
document.addEventListener('mousemove', handleMouseMove)

View File

@@ -21,12 +21,28 @@ import * as filesystem$0 from "./internal/filesystem/models.js";
// @ts-ignore: Unused imports
import * as $models from "./models.js";
/**
* BgmGetPlaylist 获取播放列表
*/
export function BgmGetPlaylist(): $CancellablePromise<$models.BgmPlaylistItem[]> {
return $Call.ByID(3200870077).then(($result: any) => {
return $$createType1($result);
});
}
/**
* BgmSavePlaylist 全量保存播放列表(前端调用时传完整列表)
*/
export function BgmSavePlaylist(items: $models.BgmPlaylistItem[]): $CancellablePromise<void> {
return $Call.ByID(2929660002, items);
}
/**
* CheckUpdate 检查更新
*/
export function CheckUpdate(): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(586574094).then(($result: any) => {
return $$createType0($result);
return $$createType2($result);
});
}
@@ -42,7 +58,7 @@ export function ClearCache(): $CancellablePromise<void> {
*/
export function CreateDir(path: string): $CancellablePromise<filesystem$0.FileOperationResult | null> {
return $Call.ByID(632035444, path).then(($result: any) => {
return $$createType2($result);
return $$createType4($result);
});
}
@@ -51,16 +67,20 @@ export function CreateDir(path: string): $CancellablePromise<filesystem$0.FileOp
*/
export function CreateFile(path: string): $CancellablePromise<filesystem$0.FileOperationResult | null> {
return $Call.ByID(3418645411, path).then(($result: any) => {
return $$createType2($result);
return $$createType4($result);
});
}
export function DeleteConnectionProfile(id: number): $CancellablePromise<void> {
return $Call.ByID(2675016907, id);
}
/**
* DeletePath 删除文件或目录
*/
export function DeletePath(path: string): $CancellablePromise<filesystem$0.FileOperationResult | null> {
return $Call.ByID(1564637217, path).then(($result: any) => {
return $$createType2($result);
return $$createType4($result);
});
}
@@ -76,7 +96,7 @@ export function DeletePermanently(recyclePath: string): $CancellablePromise<void
*/
export function DetectFileTypeByContent(path: string): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(3067282982, path).then(($result: any) => {
return $$createType0($result);
return $$createType2($result);
});
}
@@ -85,7 +105,7 @@ export function DetectFileTypeByContent(path: string): $CancellablePromise<{ [_
*/
export function DownloadUpdate(downloadURL: string): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(115027584, downloadURL).then(($result: any) => {
return $$createType0($result);
return $$createType2($result);
});
}
@@ -101,7 +121,7 @@ export function EmptyRecycleBin(): $CancellablePromise<void> {
*/
export function ExportPDF(content: string, title: string, fileName: string, fontSize: number, pageWidth: number, pageHeight: number): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(1770450987, content, title, fileName, fontSize, pageWidth, pageHeight).then(($result: any) => {
return $$createType0($result);
return $$createType2($result);
});
}
@@ -124,7 +144,7 @@ export function ExtractFileFromZipToTemp(zipPath: string, filePath: string): $Ca
*/
export function GetAppConfig(): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(2006534548).then(($result: any) => {
return $$createType0($result);
return $$createType2($result);
});
}
@@ -133,7 +153,7 @@ export function GetAppConfig(): $CancellablePromise<{ [_ in string]?: any }> {
*/
export function GetAuditLogs(limit: number): $CancellablePromise<{ [_ in string]?: any }[]> {
return $Call.ByID(3554903517, limit).then(($result: any) => {
return $$createType3($result);
return $$createType5($result);
});
}
@@ -142,7 +162,7 @@ export function GetAuditLogs(limit: number): $CancellablePromise<{ [_ in string]
*/
export function GetCPUInfo(): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(2509681007).then(($result: any) => {
return $$createType0($result);
return $$createType2($result);
});
}
@@ -151,7 +171,7 @@ export function GetCPUInfo(): $CancellablePromise<{ [_ in string]?: any }> {
*/
export function GetCommonPaths(): $CancellablePromise<{ [_ in string]?: string }> {
return $Call.ByID(3953343786).then(($result: any) => {
return $$createType4($result);
return $$createType6($result);
});
}
@@ -160,7 +180,7 @@ export function GetCommonPaths(): $CancellablePromise<{ [_ in string]?: string }
*/
export function GetCurrentVersion(): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(1827245900).then(($result: any) => {
return $$createType0($result);
return $$createType2($result);
});
}
@@ -169,7 +189,7 @@ export function GetCurrentVersion(): $CancellablePromise<{ [_ in string]?: any }
*/
export function GetDiskInfo(): $CancellablePromise<{ [_ in string]?: any }[]> {
return $Call.ByID(3756377758).then(($result: any) => {
return $$createType3($result);
return $$createType5($result);
});
}
@@ -178,7 +198,7 @@ export function GetDiskInfo(): $CancellablePromise<{ [_ in string]?: any }[]> {
*/
export function GetEnvVars(): $CancellablePromise<{ [_ in string]?: string }> {
return $Call.ByID(363814436).then(($result: any) => {
return $$createType4($result);
return $$createType6($result);
});
}
@@ -187,7 +207,7 @@ export function GetEnvVars(): $CancellablePromise<{ [_ in string]?: string }> {
*/
export function GetFileInfo(path: string): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(2071650585, path).then(($result: any) => {
return $$createType0($result);
return $$createType2($result);
});
}
@@ -198,12 +218,18 @@ export function GetFileServerURL(): $CancellablePromise<string> {
return $Call.ByID(4117667287);
}
export function GetLocalSystemInfo(): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(2203542363).then(($result: any) => {
return $$createType2($result);
});
}
/**
* GetMemoryInfo 获取内存信息
*/
export function GetMemoryInfo(): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(2096905876).then(($result: any) => {
return $$createType0($result);
return $$createType2($result);
});
}
@@ -212,7 +238,7 @@ export function GetMemoryInfo(): $CancellablePromise<{ [_ in string]?: any }> {
*/
export function GetRecycleBinEntries(): $CancellablePromise<{ [_ in string]?: any }[]> {
return $Call.ByID(2312855399).then(($result: any) => {
return $$createType3($result);
return $$createType5($result);
});
}
@@ -221,7 +247,7 @@ export function GetRecycleBinEntries(): $CancellablePromise<{ [_ in string]?: an
*/
export function GetSystemInfo(): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(1347250254).then(($result: any) => {
return $$createType0($result);
return $$createType2($result);
});
}
@@ -230,7 +256,7 @@ export function GetSystemInfo(): $CancellablePromise<{ [_ in string]?: any }> {
*/
export function GetUpdateConfig(): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(680804904).then(($result: any) => {
return $$createType0($result);
return $$createType2($result);
});
}
@@ -239,16 +265,23 @@ export function GetUpdateConfig(): $CancellablePromise<{ [_ in string]?: any }>
*/
export function GetZipFileInfo(zipPath: string, filePath: string): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(2031617692, zipPath, filePath).then(($result: any) => {
return $$createType0($result);
return $$createType2($result);
});
}
/**
* HandleHotkey 处理全局热键回调:切换 BgmBar 显示/隐藏
*/
export function HandleHotkey(): $CancellablePromise<void> {
return $Call.ByID(420101833);
}
/**
* InstallUpdate 安装更新包
*/
export function InstallUpdate(installerPath: string, autoRestart: boolean): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(2443992793, installerPath, autoRestart).then(($result: any) => {
return $$createType0($result);
return $$createType2($result);
});
}
@@ -257,7 +290,7 @@ export function InstallUpdate(installerPath: string, autoRestart: boolean): $Can
*/
export function InstallUpdateWithHash(installerPath: string, autoRestart: boolean, expectedHash: string, hashType: string): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(3787276601, installerPath, autoRestart, expectedHash, hashType).then(($result: any) => {
return $$createType0($result);
return $$createType2($result);
});
}
@@ -266,7 +299,7 @@ export function InstallUpdateWithHash(installerPath: string, autoRestart: boolea
*/
export function ListDir(path: string): $CancellablePromise<{ [_ in string]?: any }[]> {
return $Call.ByID(2120475736, path).then(($result: any) => {
return $$createType3($result);
return $$createType5($result);
});
}
@@ -275,7 +308,13 @@ export function ListDir(path: string): $CancellablePromise<{ [_ in string]?: any
*/
export function ListZipContents(zipPath: string): $CancellablePromise<{ [_ in string]?: any }[]> {
return $Call.ByID(3013109042, zipPath).then(($result: any) => {
return $$createType3($result);
return $$createType5($result);
});
}
export function LoadConnectionProfiles(): $CancellablePromise<{ [_ in string]?: any }[]> {
return $Call.ByID(454364767).then(($result: any) => {
return $$createType5($result);
});
}
@@ -286,6 +325,129 @@ export function OpenPath(path: string): $CancellablePromise<void> {
return $Call.ByID(1591734570, path);
}
export function OssConnect(req: $models.OssConnectRequest): $CancellablePromise<string> {
return $Call.ByID(3667022538, req);
}
/**
* OssCreateDir OSS 创建目录
*/
export function OssCreateDir(connID: string, dirPath: string): $CancellablePromise<filesystem$0.FileOperationResult | null> {
return $Call.ByID(605668951, connID, dirPath).then(($result: any) => {
return $$createType4($result);
});
}
/**
* OssCreateFile OSS 创建文件
*/
export function OssCreateFile(connID: string, filePath: string): $CancellablePromise<filesystem$0.FileOperationResult | null> {
return $Call.ByID(4148593430, connID, filePath).then(($result: any) => {
return $$createType4($result);
});
}
/**
* OssDeletePath OSS 删除
*/
export function OssDeletePath(connID: string, key: string): $CancellablePromise<filesystem$0.FileOperationResult | null> {
return $Call.ByID(4285234744, connID, key).then(($result: any) => {
return $$createType4($result);
});
}
/**
* OssDisconnect 断开 OSS 连接
*/
export function OssDisconnect(connID: string): $CancellablePromise<void> {
return $Call.ByID(3427288622, connID);
}
/**
* OssDownloadSiteForPreview OSS 下载 HTML 及其引用的资源到临时目录
*/
export function OssDownloadSiteForPreview(connID: string, key: string): $CancellablePromise<string> {
return $Call.ByID(1387550222, connID, key);
}
/**
* OssDownloadToTemp OSS 下载到临时文件
*/
export function OssDownloadToTemp(connID: string, key: string): $CancellablePromise<string> {
return $Call.ByID(370656471, connID, key);
}
/**
* OssDownloadToTempCached 带缓存的 OSS 下载(命中缓存直接返回本地路径)
*/
export function OssDownloadToTempCached(connID: string, key: string, fileSize: number, modTime: string): $CancellablePromise<string> {
return $Call.ByID(1312098141, connID, key, fileSize, modTime);
}
/**
* OssGetCommonPaths OSS 获取常用路径
*/
export function OssGetCommonPaths(connID: string): $CancellablePromise<{ [_ in string]?: string }> {
return $Call.ByID(3525024115, connID).then(($result: any) => {
return $$createType6($result);
});
}
/**
* OssGetFileInfo OSS 获取文件信息
*/
export function OssGetFileInfo(connID: string, key: string): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(852430614, connID, key).then(($result: any) => {
return $$createType2($result);
});
}
/**
* OssGetSignedURL OSS 获取预签名 URL
*/
export function OssGetSignedURL(connID: string, key: string): $CancellablePromise<string> {
return $Call.ByID(1344953417, connID, key);
}
/**
* OssListDir OSS 列出目录
*/
export function OssListDir(connID: string, prefix: string): $CancellablePromise<{ [_ in string]?: any }[]> {
return $Call.ByID(3013212019, connID, prefix).then(($result: any) => {
return $$createType5($result);
});
}
/**
* OssReadFile OSS 读取文件
*/
export function OssReadFile(connID: string, key: string): $CancellablePromise<string> {
return $Call.ByID(1629576606, connID, key);
}
/**
* OssRenamePath OSS 重命名
*/
export function OssRenamePath(req: $models.OssRenamePathRequest): $CancellablePromise<filesystem$0.FileOperationResult | null> {
return $Call.ByID(4218061693, req).then(($result: any) => {
return $$createType4($result);
});
}
/**
* OssWriteBase64File OSS 写入 base64 编码文件
*/
export function OssWriteBase64File(connID: string, key: string, base64Content: string): $CancellablePromise<void> {
return $Call.ByID(1772140162, connID, key, base64Content);
}
/**
* OssWriteFile OSS 写入文件
*/
export function OssWriteFile(connID: string, key: string, content: string): $CancellablePromise<void> {
return $Call.ByID(39773277, connID, key, content);
}
/**
* ReadFile 读取文件
*/
@@ -293,6 +455,13 @@ export function ReadFile(path: string): $CancellablePromise<string> {
return $Call.ByID(1160596971, path);
}
/**
* RegisterGlobalHotkey 注册 Ctrl+Shift+B 全局热键(需在窗口创建后调用)
*/
export function RegisterGlobalHotkey(): $CancellablePromise<void> {
return $Call.ByID(2089930789);
}
/**
* Reload 重新加载窗口(用于菜单项)
*/
@@ -305,7 +474,7 @@ export function Reload(): $CancellablePromise<void> {
*/
export function RenamePath(req: $models.RenamePathRequest): $CancellablePromise<filesystem$0.FileOperationResult | null> {
return $Call.ByID(1959759948, req).then(($result: any) => {
return $$createType2($result);
return $$createType4($result);
});
}
@@ -314,7 +483,7 @@ export function RenamePath(req: $models.RenamePathRequest): $CancellablePromise<
*/
export function ResolveShortcut(lnkPath: string): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(4051288361, lnkPath).then(($result: any) => {
return $$createType0($result);
return $$createType2($result);
});
}
@@ -330,7 +499,7 @@ export function RestoreFromRecycleBin(recyclePath: string): $CancellablePromise<
*/
export function SaveAppConfig(req: $models.SaveAppConfigRequest): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(1942219977, req).then(($result: any) => {
return $$createType0($result);
return $$createType2($result);
});
}
@@ -341,6 +510,12 @@ export function SaveBase64File(req: $models.SaveBase64FileRequest): $Cancellable
return $Call.ByID(1355120553, req);
}
export function SaveConnectionProfile(req: $models.SaveProfileRequest): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(3622685069, req).then(($result: any) => {
return $$createType2($result);
});
}
/**
* SelectPDFSaveDirectory 选择PDF保存目录
*/
@@ -360,7 +535,7 @@ export function SetMainWindow(w: application$0.WebviewWindow | null): $Cancellab
*/
export function SetUpdateConfig(autoCheckEnabled: boolean, checkIntervalMinutes: number, checkURL: string): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(4271731092, autoCheckEnabled, checkIntervalMinutes, checkURL).then(($result: any) => {
return $$createType0($result);
return $$createType2($result);
});
}
@@ -371,15 +546,157 @@ export function SetWindowTitleBarColor(color: number, isDark: boolean): $Cancell
return $Call.ByID(1570627619, color, isDark);
}
/**
* SftpConnect 建立 SFTP 连接,返回连接标识符 connID
*/
export function SftpConnect(req: $models.SftpConnectRequest): $CancellablePromise<string> {
return $Call.ByID(2742828454, req);
}
/**
* SftpCreateDir SFTP 创建目录
*/
export function SftpCreateDir(connID: string, dirPath: string): $CancellablePromise<filesystem$0.FileOperationResult | null> {
return $Call.ByID(586600875, connID, dirPath).then(($result: any) => {
return $$createType4($result);
});
}
/**
* SftpCreateFile SFTP 创建文件
*/
export function SftpCreateFile(connID: string, filePath: string): $CancellablePromise<filesystem$0.FileOperationResult | null> {
return $Call.ByID(623026146, connID, filePath).then(($result: any) => {
return $$createType4($result);
});
}
/**
* SftpDeletePath SFTP 删除文件或目录
*/
export function SftpDeletePath(connID: string, filePath: string): $CancellablePromise<filesystem$0.FileOperationResult | null> {
return $Call.ByID(1833619836, connID, filePath).then(($result: any) => {
return $$createType4($result);
});
}
/**
* SftpDisconnect 断开 SFTP 连接
*/
export function SftpDisconnect(connID: string): $CancellablePromise<void> {
return $Call.ByID(597628874, connID);
}
/**
* SftpDownloadSiteForPreview 下载 HTML 及其网站资源到本地临时目录
*/
export function SftpDownloadSiteForPreview(connID: string, remotePath: string): $CancellablePromise<string> {
return $Call.ByID(1591575570, connID, remotePath);
}
/**
* SftpDownloadToTemp 下载远程文件到本地临时目录(用于预览)
*/
export function SftpDownloadToTemp(connID: string, remotePath: string): $CancellablePromise<string> {
return $Call.ByID(1159267603, connID, remotePath);
}
/**
* SftpDownloadToTempCached 带缓存的 SFTP 下载(命中缓存直接返回本地路径)
*/
export function SftpDownloadToTempCached(connID: string, remotePath: string, fileSize: number, modTime: string): $CancellablePromise<string> {
return $Call.ByID(3935472409, connID, remotePath, fileSize, modTime);
}
/**
* SftpGetCommonPaths 获取 SFTP 远程主机常用路径
*/
export function SftpGetCommonPaths(connID: string): $CancellablePromise<{ [_ in string]?: string }> {
return $Call.ByID(2874386183, connID).then(($result: any) => {
return $$createType6($result);
});
}
/**
* SftpGetFileInfo SFTP 获取文件信息
*/
export function SftpGetFileInfo(connID: string, filePath: string): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(1959840482, connID, filePath).then(($result: any) => {
return $$createType2($result);
});
}
/**
* SftpGetSystemInfo 获取 SFTP 远程主机系统信息CPU/内存/磁盘)
*/
export function SftpGetSystemInfo(connID: string): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(1950143653, connID).then(($result: any) => {
return $$createType2($result);
});
}
/**
* SftpListDir SFTP 列出目录
*/
export function SftpListDir(connID: string, dirPath: string): $CancellablePromise<{ [_ in string]?: any }[]> {
return $Call.ByID(2061863855, connID, dirPath).then(($result: any) => {
return $$createType5($result);
});
}
/**
* SftpReadFile SFTP 读取文件内容
*/
export function SftpReadFile(connID: string, filePath: string): $CancellablePromise<string> {
return $Call.ByID(3068590994, connID, filePath);
}
/**
* SftpRenamePath SFTP 重命名文件或目录
*/
export function SftpRenamePath(req: $models.SftpRenamePathRequest): $CancellablePromise<filesystem$0.FileOperationResult | null> {
return $Call.ByID(183173937, req).then(($result: any) => {
return $$createType4($result);
});
}
/**
* SftpWriteBase64File SFTP 写入 base64 编码的二进制文件(粘贴图片等)
*/
export function SftpWriteBase64File(sessionID: string, filePath: string, base64Content: string): $CancellablePromise<void> {
return $Call.ByID(139141998, sessionID, filePath, base64Content);
}
/**
* SftpWriteFile SFTP 写入文件
*/
export function SftpWriteFile(req: $models.SftpWriteFileRequest): $CancellablePromise<void> {
return $Call.ByID(2401472593, req);
}
/**
* UnwatchFile 停止监听文件变化
*/
export function UnwatchFile(): $CancellablePromise<void> {
return $Call.ByID(3006906623);
}
/**
* VerifyUpdateFile 验证更新文件哈希值
*/
export function VerifyUpdateFile(filePath: string, expectedHash: string, hashType: string): $CancellablePromise<{ [_ in string]?: any }> {
return $Call.ByID(2181909867, filePath, expectedHash, hashType).then(($result: any) => {
return $$createType0($result);
return $$createType2($result);
});
}
/**
* WatchFile 开始监听指定文件的变化,变化时发送 file-changed 事件
*/
export function WatchFile(path: string): $CancellablePromise<void> {
return $Call.ByID(325055910, path);
}
/**
* WindowClose 关闭窗口
*/
@@ -423,8 +740,10 @@ export function WriteFile(req: $models.WriteFileRequest): $CancellablePromise<vo
}
// Private type creation functions
const $$createType0 = $Create.Map($Create.Any, $Create.Any);
const $$createType1 = filesystem$0.FileOperationResult.createFrom;
const $$createType2 = $Create.Nullable($$createType1);
const $$createType3 = $Create.Array($$createType0);
const $$createType4 = $Create.Map($Create.Any, $Create.Any);
const $$createType0 = $models.BgmPlaylistItem.createFrom;
const $$createType1 = $Create.Array($$createType0);
const $$createType2 = $Create.Map($Create.Any, $Create.Any);
const $$createType3 = filesystem$0.FileOperationResult.createFrom;
const $$createType4 = $Create.Nullable($$createType3);
const $$createType5 = $Create.Array($$createType2);
const $$createType6 = $Create.Map($Create.Any, $Create.Any);

View File

@@ -7,8 +7,15 @@ export {
};
export {
BgmPlaylistItem,
OssConnectRequest,
OssRenamePathRequest,
RenamePathRequest,
SaveAppConfigRequest,
SaveBase64FileRequest,
SaveProfileRequest,
SftpConnectRequest,
SftpRenamePathRequest,
SftpWriteFileRequest,
WriteFileRequest
} from "./models.js";

View File

@@ -9,6 +9,103 @@ import { Create as $Create } from "@wailsio/runtime";
// @ts-ignore: Unused imports
import * as api$0 from "./internal/api/models.js";
/**
* BgmPlaylistItem 播放列表条目
*/
export class BgmPlaylistItem {
"name": string;
"path": string;
"profile_id": string;
/** Creates a new BgmPlaylistItem instance. */
constructor($$source: Partial<BgmPlaylistItem> = {}) {
if (!("name" in $$source)) {
this["name"] = "";
}
if (!("path" in $$source)) {
this["path"] = "";
}
if (!("profile_id" in $$source)) {
this["profile_id"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new BgmPlaylistItem instance from a string or object.
*/
static createFrom($$source: any = {}): BgmPlaylistItem {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new BgmPlaylistItem($$parsedSource as Partial<BgmPlaylistItem>);
}
}
export class OssConnectRequest {
"provider": string;
"access_key": string;
"secret_key": string;
"endpoint": string;
/** Creates a new OssConnectRequest instance. */
constructor($$source: Partial<OssConnectRequest> = {}) {
if (!("provider" in $$source)) {
this["provider"] = "";
}
if (!("access_key" in $$source)) {
this["access_key"] = "";
}
if (!("secret_key" in $$source)) {
this["secret_key"] = "";
}
if (!("endpoint" in $$source)) {
this["endpoint"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new OssConnectRequest instance from a string or object.
*/
static createFrom($$source: any = {}): OssConnectRequest {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new OssConnectRequest($$parsedSource as Partial<OssConnectRequest>);
}
}
/**
* OssRenamePathRequest OSS 重命名请求
*/
export class OssRenamePathRequest {
"conn_id": string;
"old_path": string;
"new_path": string;
/** Creates a new OssRenamePathRequest instance. */
constructor($$source: Partial<OssRenamePathRequest> = {}) {
if (!("conn_id" in $$source)) {
this["conn_id"] = "";
}
if (!("old_path" in $$source)) {
this["old_path"] = "";
}
if (!("new_path" in $$source)) {
this["new_path"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new OssRenamePathRequest instance from a string or object.
*/
static createFrom($$source: any = {}): OssRenamePathRequest {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new OssRenamePathRequest($$parsedSource as Partial<OssRenamePathRequest>);
}
}
/**
* RenamePathRequest 重命名文件或目录请求结构体
*/
@@ -109,6 +206,195 @@ export class SaveBase64FileRequest {
}
}
export class SaveProfileRequest {
"id": number | null;
"name": string;
"host": string;
"port": number;
"username": string;
"password": string;
"key_path": string;
"type": string;
"provider": string;
"token": string;
"access_key": string;
"secret_key": string;
"bucket": string;
"region": string;
"endpoint": string;
"last_connected": number | null;
/** Creates a new SaveProfileRequest instance. */
constructor($$source: Partial<SaveProfileRequest> = {}) {
if (!("id" in $$source)) {
this["id"] = null;
}
if (!("name" in $$source)) {
this["name"] = "";
}
if (!("host" in $$source)) {
this["host"] = "";
}
if (!("port" in $$source)) {
this["port"] = 0;
}
if (!("username" in $$source)) {
this["username"] = "";
}
if (!("password" in $$source)) {
this["password"] = "";
}
if (!("key_path" in $$source)) {
this["key_path"] = "";
}
if (!("type" in $$source)) {
this["type"] = "";
}
if (!("provider" in $$source)) {
this["provider"] = "";
}
if (!("token" in $$source)) {
this["token"] = "";
}
if (!("access_key" in $$source)) {
this["access_key"] = "";
}
if (!("secret_key" in $$source)) {
this["secret_key"] = "";
}
if (!("bucket" in $$source)) {
this["bucket"] = "";
}
if (!("region" in $$source)) {
this["region"] = "";
}
if (!("endpoint" in $$source)) {
this["endpoint"] = "";
}
if (!("last_connected" in $$source)) {
this["last_connected"] = null;
}
Object.assign(this, $$source);
}
/**
* Creates a new SaveProfileRequest instance from a string or object.
*/
static createFrom($$source: any = {}): SaveProfileRequest {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new SaveProfileRequest($$parsedSource as Partial<SaveProfileRequest>);
}
}
/**
* SftpConnectRequest SFTP 连接请求
*/
export class SftpConnectRequest {
"host": string;
"port": number;
"username": string;
"password": string;
"key_path": string;
"key_passphrase": string;
/** Creates a new SftpConnectRequest instance. */
constructor($$source: Partial<SftpConnectRequest> = {}) {
if (!("host" in $$source)) {
this["host"] = "";
}
if (!("port" in $$source)) {
this["port"] = 0;
}
if (!("username" in $$source)) {
this["username"] = "";
}
if (!("password" in $$source)) {
this["password"] = "";
}
if (!("key_path" in $$source)) {
this["key_path"] = "";
}
if (!("key_passphrase" in $$source)) {
this["key_passphrase"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new SftpConnectRequest instance from a string or object.
*/
static createFrom($$source: any = {}): SftpConnectRequest {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new SftpConnectRequest($$parsedSource as Partial<SftpConnectRequest>);
}
}
/**
* SftpRenamePathRequest SFTP 重命名请求
*/
export class SftpRenamePathRequest {
"session_id": string;
"old_path": string;
"new_path": string;
/** Creates a new SftpRenamePathRequest instance. */
constructor($$source: Partial<SftpRenamePathRequest> = {}) {
if (!("session_id" in $$source)) {
this["session_id"] = "";
}
if (!("old_path" in $$source)) {
this["old_path"] = "";
}
if (!("new_path" in $$source)) {
this["new_path"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new SftpRenamePathRequest instance from a string or object.
*/
static createFrom($$source: any = {}): SftpRenamePathRequest {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new SftpRenamePathRequest($$parsedSource as Partial<SftpRenamePathRequest>);
}
}
/**
* SftpWriteFileRequest SFTP 写入请求
*/
export class SftpWriteFileRequest {
"session_id": string;
"path": string;
"content": string;
/** Creates a new SftpWriteFileRequest instance. */
constructor($$source: Partial<SftpWriteFileRequest> = {}) {
if (!("session_id" in $$source)) {
this["session_id"] = "";
}
if (!("path" in $$source)) {
this["path"] = "";
}
if (!("content" in $$source)) {
this["content"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new SftpWriteFileRequest instance from a string or object.
*/
static createFrom($$source: any = {}): SftpWriteFileRequest {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new SftpWriteFileRequest($$parsedSource as Partial<SftpWriteFileRequest>);
}
}
/**
* WriteFileRequest 写入文件请求结构体
*/

View File

@@ -1,98 +0,0 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {filesystem} from '../models';
import {main} from '../models';
export function CheckUpdate():Promise<Record<string, any>>;
export function ClearCache():Promise<void>;
export function CreateDir(arg1:string):Promise<filesystem.FileOperationResult>;
export function CreateFile(arg1:string):Promise<filesystem.FileOperationResult>;
export function DeletePath(arg1:string):Promise<filesystem.FileOperationResult>;
export function DeletePermanently(arg1:string):Promise<void>;
export function DetectFileTypeByContent(arg1:string):Promise<Record<string, any>>;
export function DownloadUpdate(arg1:string):Promise<Record<string, any>>;
export function EmptyRecycleBin():Promise<void>;
export function ExportPDF(arg1:string,arg2:string,arg3:string,arg4:number,arg5:number,arg6:number):Promise<Record<string, any>>;
export function ExtractFileFromZip(arg1:string,arg2:string):Promise<string>;
export function ExtractFileFromZipToTemp(arg1:string,arg2:string):Promise<string>;
export function GetAppConfig():Promise<Record<string, any>>;
export function GetAuditLogs(arg1:number):Promise<Array<Record<string, any>>>;
export function GetCPUInfo():Promise<Record<string, any>>;
export function GetCommonPaths():Promise<Record<string, string>>;
export function GetCurrentVersion():Promise<Record<string, any>>;
export function GetDiskInfo():Promise<Array<Record<string, any>>>;
export function GetEnvVars():Promise<Record<string, string>>;
export function GetFileInfo(arg1:string):Promise<Record<string, any>>;
export function GetFileServerURL():Promise<string>;
export function GetMemoryInfo():Promise<Record<string, any>>;
export function GetRecycleBinEntries():Promise<Array<Record<string, any>>>;
export function GetSystemInfo():Promise<Record<string, any>>;
export function GetUpdateConfig():Promise<Record<string, any>>;
export function GetZipFileInfo(arg1:string,arg2:string):Promise<Record<string, any>>;
export function InstallUpdate(arg1:string,arg2:boolean):Promise<Record<string, any>>;
export function InstallUpdateWithHash(arg1:string,arg2:boolean,arg3:string,arg4:string):Promise<Record<string, any>>;
export function ListDir(arg1:string):Promise<Array<Record<string, any>>>;
export function ListZipContents(arg1:string):Promise<Array<Record<string, any>>>;
export function OpenPath(arg1:string):Promise<void>;
export function ReadFile(arg1:string):Promise<string>;
export function Reload():Promise<void>;
export function RenamePath(arg1:main.RenamePathRequest):Promise<filesystem.FileOperationResult>;
export function ResolveShortcut(arg1:string):Promise<Record<string, any>>;
export function RestoreFromRecycleBin(arg1:string):Promise<void>;
export function SaveAppConfig(arg1:main.SaveAppConfigRequest):Promise<Record<string, any>>;
export function SaveBase64File(arg1:main.SaveBase64FileRequest):Promise<void>;
export function SelectPDFSaveDirectory():Promise<string>;
export function SetUpdateConfig(arg1:boolean,arg2:number,arg3:string):Promise<Record<string, any>>;
export function VerifyUpdateFile(arg1:string,arg2:string,arg3:string):Promise<Record<string, any>>;
export function WindowClose():Promise<void>;
export function WindowIsMaximized():Promise<boolean>;
export function WindowMaximize():Promise<void>;
export function WindowMinimize():Promise<void>;
export function WindowToggleAlwaysOnTop():Promise<boolean>;
export function WriteFile(arg1:main.WriteFileRequest):Promise<void>;

View File

@@ -1,191 +0,0 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function CheckUpdate() {
return window['go']['main']['App']['CheckUpdate']();
}
export function ClearCache() {
return window['go']['main']['App']['ClearCache']();
}
export function CreateDir(arg1) {
return window['go']['main']['App']['CreateDir'](arg1);
}
export function CreateFile(arg1) {
return window['go']['main']['App']['CreateFile'](arg1);
}
export function DeletePath(arg1) {
return window['go']['main']['App']['DeletePath'](arg1);
}
export function DeletePermanently(arg1) {
return window['go']['main']['App']['DeletePermanently'](arg1);
}
export function DetectFileTypeByContent(arg1) {
return window['go']['main']['App']['DetectFileTypeByContent'](arg1);
}
export function DownloadUpdate(arg1) {
return window['go']['main']['App']['DownloadUpdate'](arg1);
}
export function EmptyRecycleBin() {
return window['go']['main']['App']['EmptyRecycleBin']();
}
export function ExportPDF(arg1, arg2, arg3, arg4, arg5, arg6) {
return window['go']['main']['App']['ExportPDF'](arg1, arg2, arg3, arg4, arg5, arg6);
}
export function ExtractFileFromZip(arg1, arg2) {
return window['go']['main']['App']['ExtractFileFromZip'](arg1, arg2);
}
export function ExtractFileFromZipToTemp(arg1, arg2) {
return window['go']['main']['App']['ExtractFileFromZipToTemp'](arg1, arg2);
}
export function GetAppConfig() {
return window['go']['main']['App']['GetAppConfig']();
}
export function GetAuditLogs(arg1) {
return window['go']['main']['App']['GetAuditLogs'](arg1);
}
export function GetCPUInfo() {
return window['go']['main']['App']['GetCPUInfo']();
}
export function GetCommonPaths() {
return window['go']['main']['App']['GetCommonPaths']();
}
export function GetCurrentVersion() {
return window['go']['main']['App']['GetCurrentVersion']();
}
export function GetDiskInfo() {
return window['go']['main']['App']['GetDiskInfo']();
}
export function GetEnvVars() {
return window['go']['main']['App']['GetEnvVars']();
}
export function GetFileInfo(arg1) {
return window['go']['main']['App']['GetFileInfo'](arg1);
}
export function GetFileServerURL() {
return window['go']['main']['App']['GetFileServerURL']();
}
export function GetMemoryInfo() {
return window['go']['main']['App']['GetMemoryInfo']();
}
export function GetRecycleBinEntries() {
return window['go']['main']['App']['GetRecycleBinEntries']();
}
export function GetSystemInfo() {
return window['go']['main']['App']['GetSystemInfo']();
}
export function GetUpdateConfig() {
return window['go']['main']['App']['GetUpdateConfig']();
}
export function GetZipFileInfo(arg1, arg2) {
return window['go']['main']['App']['GetZipFileInfo'](arg1, arg2);
}
export function InstallUpdate(arg1, arg2) {
return window['go']['main']['App']['InstallUpdate'](arg1, arg2);
}
export function InstallUpdateWithHash(arg1, arg2, arg3, arg4) {
return window['go']['main']['App']['InstallUpdateWithHash'](arg1, arg2, arg3, arg4);
}
export function ListDir(arg1) {
return window['go']['main']['App']['ListDir'](arg1);
}
export function ListZipContents(arg1) {
return window['go']['main']['App']['ListZipContents'](arg1);
}
export function OpenPath(arg1) {
return window['go']['main']['App']['OpenPath'](arg1);
}
export function ReadFile(arg1) {
return window['go']['main']['App']['ReadFile'](arg1);
}
export function Reload() {
return window['go']['main']['App']['Reload']();
}
export function RenamePath(arg1) {
return window['go']['main']['App']['RenamePath'](arg1);
}
export function ResolveShortcut(arg1) {
return window['go']['main']['App']['ResolveShortcut'](arg1);
}
export function RestoreFromRecycleBin(arg1) {
return window['go']['main']['App']['RestoreFromRecycleBin'](arg1);
}
export function SaveAppConfig(arg1) {
return window['go']['main']['App']['SaveAppConfig'](arg1);
}
export function SaveBase64File(arg1) {
return window['go']['main']['App']['SaveBase64File'](arg1);
}
export function SelectPDFSaveDirectory() {
return window['go']['main']['App']['SelectPDFSaveDirectory']();
}
export function SetUpdateConfig(arg1, arg2, arg3) {
return window['go']['main']['App']['SetUpdateConfig'](arg1, arg2, arg3);
}
export function VerifyUpdateFile(arg1, arg2, arg3) {
return window['go']['main']['App']['VerifyUpdateFile'](arg1, arg2, arg3);
}
export function WindowClose() {
return window['go']['main']['App']['WindowClose']();
}
export function WindowIsMaximized() {
return window['go']['main']['App']['WindowIsMaximized']();
}
export function WindowMaximize() {
return window['go']['main']['App']['WindowMaximize']();
}
export function WindowMinimize() {
return window['go']['main']['App']['WindowMinimize']();
}
export function WindowToggleAlwaysOnTop() {
return window['go']['main']['App']['WindowToggleAlwaysOnTop']();
}
export function WriteFile(arg1) {
return window['go']['main']['App']['WriteFile'](arg1);
}

View File

@@ -1,137 +0,0 @@
export namespace api {
export class AppTabDefinition {
key: string;
title: string;
visible: boolean;
enabled: boolean;
static createFrom(source: any = {}) {
return new AppTabDefinition(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.key = source["key"];
this.title = source["title"];
this.visible = source["visible"];
this.enabled = source["enabled"];
}
}
}
export namespace filesystem {
export class FileOperationResult {
path: string;
name: string;
size: number;
size_str?: string;
is_dir: boolean;
mod_time?: string;
mode?: string;
old_path?: string;
deleted?: boolean;
static createFrom(source: any = {}) {
return new FileOperationResult(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.path = source["path"];
this.name = source["name"];
this.size = source["size"];
this.size_str = source["size_str"];
this.is_dir = source["is_dir"];
this.mod_time = source["mod_time"];
this.mode = source["mode"];
this.old_path = source["old_path"];
this.deleted = source["deleted"];
}
}
}
export namespace main {
export class RenamePathRequest {
oldPath: string;
newPath: string;
static createFrom(source: any = {}) {
return new RenamePathRequest(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.oldPath = source["oldPath"];
this.newPath = source["newPath"];
}
}
export class SaveAppConfigRequest {
tabs: api.AppTabDefinition[];
visibleTabs: string[];
defaultTab: string;
static createFrom(source: any = {}) {
return new SaveAppConfigRequest(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.tabs = this.convertValues(source["tabs"], api.AppTabDefinition);
this.visibleTabs = source["visibleTabs"];
this.defaultTab = source["defaultTab"];
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class SaveBase64FileRequest {
path: string;
content: string;
static createFrom(source: any = {}) {
return new SaveBase64FileRequest(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.path = source["path"];
this.content = source["content"];
}
}
export class WriteFileRequest {
path: string;
content: string;
static createFrom(source: any = {}) {
return new WriteFileRequest(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.path = source["path"];
this.content = source["content"];
}
}
}

View File

@@ -1,24 +0,0 @@
{
"name": "@wailsapp/runtime",
"version": "2.0.0",
"description": "Wails Javascript runtime library",
"main": "runtime.js",
"types": "runtime.d.ts",
"scripts": {
},
"repository": {
"type": "git",
"url": "git+https://github.com/wailsapp/wails.git"
},
"keywords": [
"Wails",
"Javascript",
"Go"
],
"author": "Lea Anthony <lea.anthony@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/wailsapp/wails/issues"
},
"homepage": "https://github.com/wailsapp/wails#readme"
}

View File

@@ -1,330 +0,0 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The electron alternative for Go
(c) Lea Anthony 2019-present
*/
export interface Position {
x: number;
y: number;
}
export interface Size {
w: number;
h: number;
}
export interface Screen {
isCurrent: boolean;
isPrimary: boolean;
width : number
height : number
}
// Environment information such as platform, buildtype, ...
export interface EnvironmentInfo {
buildType: string;
platform: string;
arch: string;
}
// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
// emits the given event. Optional data may be passed with the event.
// This will trigger any event listeners.
export function EventsEmit(eventName: string, ...data: any): void;
// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
// sets up a listener for the given event name, but will only trigger a given number times.
export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
// sets up a listener for the given event name, but will only trigger once.
export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
// unregisters the listener for the given event name.
export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
// unregisters all listeners.
export function EventsOffAll(): void;
// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
// logs the given message as a raw message
export function LogPrint(message: string): void;
// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
// logs the given message at the `trace` log level.
export function LogTrace(message: string): void;
// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
// logs the given message at the `debug` log level.
export function LogDebug(message: string): void;
// [LogError](https://wails.io/docs/reference/runtime/log#logerror)
// logs the given message at the `error` log level.
export function LogError(message: string): void;
// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
// logs the given message at the `fatal` log level.
// The application will quit after calling this method.
export function LogFatal(message: string): void;
// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
// logs the given message at the `info` log level.
export function LogInfo(message: string): void;
// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
// logs the given message at the `warning` log level.
export function LogWarning(message: string): void;
// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
// Forces a reload by the main application as well as connected browsers.
export function WindowReload(): void;
// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
// Reloads the application frontend.
export function WindowReloadApp(): void;
// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
// Sets the window AlwaysOnTop or not on top.
export function WindowSetAlwaysOnTop(b: boolean): void;
// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
// *Windows only*
// Sets window theme to system default (dark/light).
export function WindowSetSystemDefaultTheme(): void;
// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
// *Windows only*
// Sets window to light theme.
export function WindowSetLightTheme(): void;
// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
// *Windows only*
// Sets window to dark theme.
export function WindowSetDarkTheme(): void;
// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
// Centers the window on the monitor the window is currently on.
export function WindowCenter(): void;
// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
// Sets the text in the window title bar.
export function WindowSetTitle(title: string): void;
// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
// Makes the window full screen.
export function WindowFullscreen(): void;
// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
// Restores the previous window dimensions and position prior to full screen.
export function WindowUnfullscreen(): void;
// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
// Returns the state of the window, i.e. whether the window is in full screen mode or not.
export function WindowIsFullscreen(): Promise<boolean>;
// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
// Sets the width and height of the window.
export function WindowSetSize(width: number, height: number): void;
// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
// Gets the width and height of the window.
export function WindowGetSize(): Promise<Size>;
// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
// Setting a size of 0,0 will disable this constraint.
export function WindowSetMaxSize(width: number, height: number): void;
// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
// Setting a size of 0,0 will disable this constraint.
export function WindowSetMinSize(width: number, height: number): void;
// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
// Sets the window position relative to the monitor the window is currently on.
export function WindowSetPosition(x: number, y: number): void;
// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
// Gets the window position relative to the monitor the window is currently on.
export function WindowGetPosition(): Promise<Position>;
// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
// Hides the window.
export function WindowHide(): void;
// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
// Shows the window, if it is currently hidden.
export function WindowShow(): void;
// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
// Maximises the window to fill the screen.
export function WindowMaximise(): void;
// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
// Toggles between Maximised and UnMaximised.
export function WindowToggleMaximise(): void;
// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
// Restores the window to the dimensions and position prior to maximising.
export function WindowUnmaximise(): void;
// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
// Returns the state of the window, i.e. whether the window is maximised or not.
export function WindowIsMaximised(): Promise<boolean>;
// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
// Minimises the window.
export function WindowMinimise(): void;
// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
// Restores the window to the dimensions and position prior to minimising.
export function WindowUnminimise(): void;
// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
// Returns the state of the window, i.e. whether the window is minimised or not.
export function WindowIsMinimised(): Promise<boolean>;
// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
// Returns the state of the window, i.e. whether the window is normal or not.
export function WindowIsNormal(): Promise<boolean>;
// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
export function ScreenGetAll(): Promise<Screen[]>;
// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
// Opens the given URL in the system browser.
export function BrowserOpenURL(url: string): void;
// [Environment](https://wails.io/docs/reference/runtime/intro#environment)
// Returns information about the environment
export function Environment(): Promise<EnvironmentInfo>;
// [Quit](https://wails.io/docs/reference/runtime/intro#quit)
// Quits the application.
export function Quit(): void;
// [Hide](https://wails.io/docs/reference/runtime/intro#hide)
// Hides the application.
export function Hide(): void;
// [Show](https://wails.io/docs/reference/runtime/intro#show)
// Shows the application.
export function Show(): void;
// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
// Returns the current text stored on clipboard
export function ClipboardGetText(): Promise<string>;
// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
// Sets a text on the clipboard
export function ClipboardSetText(text: string): Promise<boolean>;
// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop)
// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void
// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff)
// OnFileDropOff removes the drag and drop listeners and handlers.
export function OnFileDropOff() :void
// Check if the file path resolver is available
export function CanResolveFilePaths(): boolean;
// Resolves file paths for an array of files
export function ResolveFilePaths(files: File[]): void
// Notification types
export interface NotificationOptions {
id: string;
title: string;
subtitle?: string; // macOS and Linux only
body?: string;
categoryId?: string;
data?: { [key: string]: any };
}
export interface NotificationAction {
id?: string;
title?: string;
destructive?: boolean; // macOS-specific
}
export interface NotificationCategory {
id?: string;
actions?: NotificationAction[];
hasReplyField?: boolean;
replyPlaceholder?: string;
replyButtonTitle?: string;
}
// [InitializeNotifications](https://wails.io/docs/reference/runtime/notification#initializenotifications)
// Initializes the notification service for the application.
// This must be called before sending any notifications.
export function InitializeNotifications(): Promise<void>;
// [CleanupNotifications](https://wails.io/docs/reference/runtime/notification#cleanupnotifications)
// Cleans up notification resources and releases any held connections.
export function CleanupNotifications(): Promise<void>;
// [IsNotificationAvailable](https://wails.io/docs/reference/runtime/notification#isnotificationavailable)
// Checks if notifications are available on the current platform.
export function IsNotificationAvailable(): Promise<boolean>;
// [RequestNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#requestnotificationauthorization)
// Requests notification authorization from the user (macOS only).
export function RequestNotificationAuthorization(): Promise<boolean>;
// [CheckNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#checknotificationauthorization)
// Checks the current notification authorization status (macOS only).
export function CheckNotificationAuthorization(): Promise<boolean>;
// [SendNotification](https://wails.io/docs/reference/runtime/notification#sendnotification)
// Sends a basic notification with the given options.
export function SendNotification(options: NotificationOptions): Promise<void>;
// [SendNotificationWithActions](https://wails.io/docs/reference/runtime/notification#sendnotificationwithactions)
// Sends a notification with action buttons. Requires a registered category.
export function SendNotificationWithActions(options: NotificationOptions): Promise<void>;
// [RegisterNotificationCategory](https://wails.io/docs/reference/runtime/notification#registernotificationcategory)
// Registers a notification category that can be used with SendNotificationWithActions.
export function RegisterNotificationCategory(category: NotificationCategory): Promise<void>;
// [RemoveNotificationCategory](https://wails.io/docs/reference/runtime/notification#removenotificationcategory)
// Removes a previously registered notification category.
export function RemoveNotificationCategory(categoryId: string): Promise<void>;
// [RemoveAllPendingNotifications](https://wails.io/docs/reference/runtime/notification#removeallpendingnotifications)
// Removes all pending notifications from the notification center.
export function RemoveAllPendingNotifications(): Promise<void>;
// [RemovePendingNotification](https://wails.io/docs/reference/runtime/notification#removependingnotification)
// Removes a specific pending notification by its identifier.
export function RemovePendingNotification(identifier: string): Promise<void>;
// [RemoveAllDeliveredNotifications](https://wails.io/docs/reference/runtime/notification#removealldeliverednotifications)
// Removes all delivered notifications from the notification center.
export function RemoveAllDeliveredNotifications(): Promise<void>;
// [RemoveDeliveredNotification](https://wails.io/docs/reference/runtime/notification#removedeliverednotification)
// Removes a specific delivered notification by its identifier.
export function RemoveDeliveredNotification(identifier: string): Promise<void>;
// [RemoveNotification](https://wails.io/docs/reference/runtime/notification#removenotification)
// Removes a notification by its identifier (cross-platform convenience function).
export function RemoveNotification(identifier: string): Promise<void>;

View File

@@ -1,298 +0,0 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The electron alternative for Go
(c) Lea Anthony 2019-present
*/
export function LogPrint(message) {
window.runtime.LogPrint(message);
}
export function LogTrace(message) {
window.runtime.LogTrace(message);
}
export function LogDebug(message) {
window.runtime.LogDebug(message);
}
export function LogInfo(message) {
window.runtime.LogInfo(message);
}
export function LogWarning(message) {
window.runtime.LogWarning(message);
}
export function LogError(message) {
window.runtime.LogError(message);
}
export function LogFatal(message) {
window.runtime.LogFatal(message);
}
export function EventsOnMultiple(eventName, callback, maxCallbacks) {
return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
}
export function EventsOn(eventName, callback) {
return EventsOnMultiple(eventName, callback, -1);
}
export function EventsOff(eventName, ...additionalEventNames) {
return window.runtime.EventsOff(eventName, ...additionalEventNames);
}
export function EventsOffAll() {
return window.runtime.EventsOffAll();
}
export function EventsOnce(eventName, callback) {
return EventsOnMultiple(eventName, callback, 1);
}
export function EventsEmit(eventName) {
let args = [eventName].slice.call(arguments);
return window.runtime.EventsEmit.apply(null, args);
}
export function WindowReload() {
window.runtime.WindowReload();
}
export function WindowReloadApp() {
window.runtime.WindowReloadApp();
}
export function WindowSetAlwaysOnTop(b) {
window.runtime.WindowSetAlwaysOnTop(b);
}
export function WindowSetSystemDefaultTheme() {
window.runtime.WindowSetSystemDefaultTheme();
}
export function WindowSetLightTheme() {
window.runtime.WindowSetLightTheme();
}
export function WindowSetDarkTheme() {
window.runtime.WindowSetDarkTheme();
}
export function WindowCenter() {
window.runtime.WindowCenter();
}
export function WindowSetTitle(title) {
window.runtime.WindowSetTitle(title);
}
export function WindowFullscreen() {
window.runtime.WindowFullscreen();
}
export function WindowUnfullscreen() {
window.runtime.WindowUnfullscreen();
}
export function WindowIsFullscreen() {
return window.runtime.WindowIsFullscreen();
}
export function WindowGetSize() {
return window.runtime.WindowGetSize();
}
export function WindowSetSize(width, height) {
window.runtime.WindowSetSize(width, height);
}
export function WindowSetMaxSize(width, height) {
window.runtime.WindowSetMaxSize(width, height);
}
export function WindowSetMinSize(width, height) {
window.runtime.WindowSetMinSize(width, height);
}
export function WindowSetPosition(x, y) {
window.runtime.WindowSetPosition(x, y);
}
export function WindowGetPosition() {
return window.runtime.WindowGetPosition();
}
export function WindowHide() {
window.runtime.WindowHide();
}
export function WindowShow() {
window.runtime.WindowShow();
}
export function WindowMaximise() {
window.runtime.WindowMaximise();
}
export function WindowToggleMaximise() {
window.runtime.WindowToggleMaximise();
}
export function WindowUnmaximise() {
window.runtime.WindowUnmaximise();
}
export function WindowIsMaximised() {
return window.runtime.WindowIsMaximised();
}
export function WindowMinimise() {
window.runtime.WindowMinimise();
}
export function WindowUnminimise() {
window.runtime.WindowUnminimise();
}
export function WindowSetBackgroundColour(R, G, B, A) {
window.runtime.WindowSetBackgroundColour(R, G, B, A);
}
export function ScreenGetAll() {
return window.runtime.ScreenGetAll();
}
export function WindowIsMinimised() {
return window.runtime.WindowIsMinimised();
}
export function WindowIsNormal() {
return window.runtime.WindowIsNormal();
}
export function BrowserOpenURL(url) {
window.runtime.BrowserOpenURL(url);
}
export function Environment() {
return window.runtime.Environment();
}
export function Quit() {
window.runtime.Quit();
}
export function Hide() {
window.runtime.Hide();
}
export function Show() {
window.runtime.Show();
}
export function ClipboardGetText() {
return window.runtime.ClipboardGetText();
}
export function ClipboardSetText(text) {
return window.runtime.ClipboardSetText(text);
}
/**
* Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
*
* @export
* @callback OnFileDropCallback
* @param {number} x - x coordinate of the drop
* @param {number} y - y coordinate of the drop
* @param {string[]} paths - A list of file paths.
*/
/**
* OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
*
* @export
* @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
* @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target)
*/
export function OnFileDrop(callback, useDropTarget) {
return window.runtime.OnFileDrop(callback, useDropTarget);
}
/**
* OnFileDropOff removes the drag and drop listeners and handlers.
*/
export function OnFileDropOff() {
return window.runtime.OnFileDropOff();
}
export function CanResolveFilePaths() {
return window.runtime.CanResolveFilePaths();
}
export function ResolveFilePaths(files) {
return window.runtime.ResolveFilePaths(files);
}
export function InitializeNotifications() {
return window.runtime.InitializeNotifications();
}
export function CleanupNotifications() {
return window.runtime.CleanupNotifications();
}
export function IsNotificationAvailable() {
return window.runtime.IsNotificationAvailable();
}
export function RequestNotificationAuthorization() {
return window.runtime.RequestNotificationAuthorization();
}
export function CheckNotificationAuthorization() {
return window.runtime.CheckNotificationAuthorization();
}
export function SendNotification(options) {
return window.runtime.SendNotification(options);
}
export function SendNotificationWithActions(options) {
return window.runtime.SendNotificationWithActions(options);
}
export function RegisterNotificationCategory(category) {
return window.runtime.RegisterNotificationCategory(category);
}
export function RemoveNotificationCategory(categoryId) {
return window.runtime.RemoveNotificationCategory(categoryId);
}
export function RemoveAllPendingNotifications() {
return window.runtime.RemoveAllPendingNotifications();
}
export function RemovePendingNotification(identifier) {
return window.runtime.RemovePendingNotification(identifier);
}
export function RemoveAllDeliveredNotifications() {
return window.runtime.RemoveAllDeliveredNotifications();
}
export function RemoveDeliveredNotification(identifier) {
return window.runtime.RemoveDeliveredNotification(identifier);
}
export function RemoveNotification(identifier) {
return window.runtime.RemoveNotification(identifier);
}

1
go.mod
View File

@@ -5,6 +5,7 @@ go 1.26
require (
github.com/chromedp/cdproto v0.0.0-20260427013145-5737772c319b
github.com/chromedp/chromedp v0.15.1
github.com/fsnotify/fsnotify v1.10.1
github.com/glebarez/sqlite v1.11.0
github.com/labstack/echo/v4 v4.15.1
github.com/pkg/sftp v1.13.10

2
go.sum
View File

@@ -36,6 +36,8 @@ github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=

View File

@@ -268,6 +268,11 @@ func (s *FileSystemService) DeletePathWithContext(ctx context.Context, path stri
// ListDir 列出目录内容
func (s *FileSystemService) ListDir(path string) ([]map[string]interface{}, error) {
// 根路径:返回 Windows 盘符列表
if path == "/" || path == "\\" {
return s.listDrives()
}
// 路径验证
if err := s.validatePath(path); err != nil {
return nil, err
@@ -308,6 +313,25 @@ func (s *FileSystemService) ListDir(path string) ([]map[string]interface{}, erro
return result, nil
}
// listDrives 列出 Windows 可用盘符
func (s *FileSystemService) listDrives() ([]map[string]interface{}, error) {
result := make([]map[string]interface{}, 0)
for _, drive := range "ABCDEFGHIJKLMNOPQRSTUVWXYZ" {
path := string(drive) + ":/"
_, err := os.Stat(path)
if err != nil {
continue
}
result = append(result, map[string]interface{}{
"name": string(drive) + ":",
"path": path,
"is_dir": true,
"size": int64(0),
})
}
return result, nil
}
// CreateDir 创建目录,返回创建的目录信息
func (s *FileSystemService) CreateDir(path string) (*FileOperationResult, error) {
if err := s.validatePath(path); err != nil {

View File

@@ -0,0 +1,138 @@
package filewatch
import (
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/fsnotify/fsnotify"
)
type Watcher struct {
watcher *fsnotify.Watcher
emitEvent func(name string, data ...any)
mu sync.Mutex
watched string // 当前监听的文件绝对路径
}
func NewWatcher(emitEvent func(name string, data ...any)) *Watcher {
return &Watcher{emitEvent: emitEvent}
}
// WatchFile 开始监听指定文件的变化。切换文件时自动取消旧监听。
func (w *Watcher) WatchFile(path string) error {
w.mu.Lock()
defer w.mu.Unlock()
absPath, err := filepath.Abs(path)
if err != nil {
return fmt.Errorf("解析路径失败: %w", err)
}
// 同一文件不重复监听
if absPath == w.watched {
return nil
}
// 停止旧监听
w.stopLocked()
// 检查文件是否存在
if _, err := os.Stat(absPath); err != nil {
return fmt.Errorf("文件不存在: %s", absPath)
}
// 创建 fsnotify watcher
fw, err := fsnotify.NewWatcher()
if err != nil {
return fmt.Errorf("创建文件监听器失败: %w", err)
}
w.watcher = fw
// fsnotify 在某些系统上不支持直接监听文件,监听其所在目录
dir := filepath.Dir(absPath)
if err := fw.Add(dir); err != nil {
fw.Close()
return fmt.Errorf("监听目录失败: %w", err)
}
w.watched = absPath
// 后台消费事件
go w.consumeEvents()
return nil
}
// UnwatchFile 停止监听
func (w *Watcher) UnwatchFile() {
w.mu.Lock()
defer w.mu.Unlock()
w.stopLocked()
}
func (w *Watcher) stopLocked() {
if w.watcher != nil {
w.watcher.Close()
w.watcher = nil
}
w.watched = ""
}
func (w *Watcher) consumeEvents() {
debounceDelay := 300 * time.Millisecond
var debounceTimer *time.Timer
for {
select {
case event, ok := <-w.watcher.Events:
if !ok {
return
}
// 只处理目标文件的 Write/Create/Rename 事件
if event.Op&(fsnotify.Write|fsnotify.Create|fsnotify.Rename) == 0 {
continue
}
w.mu.Lock()
target := w.watched
w.mu.Unlock()
if target == "" {
return // 已停止监听
}
// 路径比较忽略大小写Windows
if !strings.EqualFold(event.Name, target) {
continue
}
// 防抖
if debounceTimer != nil {
debounceTimer.Stop()
}
debounceTimer = time.AfterFunc(debounceDelay, func() {
// 文件已不存在则跳过(如被删除)
if _, err := os.Stat(target); err != nil {
return
}
if w.emitEvent != nil {
w.emitEvent("file-changed", target)
}
})
case _, ok := <-w.watcher.Errors:
if !ok {
return
}
}
}
}
// Close 释放资源
func (w *Watcher) Close() {
w.UnwatchFile()
}

35
main.go
View File

@@ -2,8 +2,9 @@ package main
import (
"embed"
"fmt"
"net/http"
"time"
"os"
"github.com/wailsapp/wails/v3/pkg/application"
"u-desk/internal/hotkey"
@@ -14,8 +15,9 @@ var assets embed.FS
// 标题栏颜色0x00BBGGRR
var (
titleBarLight = uint32(0xF0F0F0) // #F0F0F0 近白
titleBarLight = uint32(0xF0F0F0) // #F0F0F0 近白
titleBarDark = uint32(0x2D2D2D) // #2D2D2D 深灰
noBorder = uint32(0xFFFFFFFE) // DWMWA_COLOR_NONE - Win11 抑制边框绘制
)
func main() {
@@ -24,6 +26,9 @@ func main() {
wailsApp := application.New(application.Options{
Name: "U-Desk",
Description: "桌面文件管理器",
SingleInstance: &application.SingleInstanceOptions{
UniqueID: "top.1216.udesk",
},
Services: []application.Service{
application.NewService(app),
},
@@ -32,7 +37,6 @@ func main() {
Middleware: func(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if req.URL.Path == "/wails/custom.js" {
// custom.js 未使用,返回空响应避免 404 控制台报错
rw.Header().Set("Content-Type", "application/javascript")
rw.WriteHeader(200)
return
@@ -67,20 +71,27 @@ func main() {
Height: 900,
MinWidth: 1000,
MinHeight: 600,
BackgroundColour: application.NewRGB(255, 255, 255),
BackgroundColour: application.NewRGB(45, 45, 45),
URL: "/",
Frameless: true,
// 保留 Windows 11 原生装饰(圆角 + Aero 阴影)
Windows: application.WindowsWindow{
Theme: application.SystemDefault,
CustomTheme: application.ThemeSettings{
LightModeActive: &application.WindowTheme{
TitleBarColour: &titleBarLight,
BorderColour: &titleBarLight,
BorderColour: &noBorder,
},
LightModeInactive: &application.WindowTheme{
TitleBarColour: &titleBarLight,
BorderColour: &noBorder,
},
DarkModeActive: &application.WindowTheme{
TitleBarColour: &titleBarDark,
BorderColour: &titleBarDark,
BorderColour: &noBorder,
},
DarkModeInactive: &application.WindowTheme{
TitleBarColour: &titleBarDark,
BorderColour: &noBorder,
},
},
},
@@ -88,15 +99,7 @@ func main() {
app.SetMainWindow(window)
// production+devtools 模式下 OpenInspectorOnStartup 不生效(需 debugMode=true
// 手动延迟调用 OpenDevTools 弹出 Inspector
// TODO: 替换为 OnDomReady 回调,当前 alpha.80 可能未稳定支持
go func() {
time.Sleep(2 * time.Second)
window.OpenDevTools()
}()
if err := wailsApp.Run(); err != nil {
println("Error:", err.Error())
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
}
}