优化:工具栏高度对齐+面板统一+远程连接架构+自动恢复预览
- 工具栏:面包屑与右侧组件像素级等高(:deep 34px)、合并重复search handler、统一分隔符样式、删除死代码 - 面板对齐:三面板header统一padding/font-size、文件列表分页固定底部(自定义紧凑)、表头默认隐藏、滚动条统一样式 - 预览区:始终显示空白预览面板、重启自动恢复上次打开文件 - 收藏夹:简化计数显示(共N项) - 远程连接:ConnectionIndicator自适应UI(无远程显示mini云图标)、ConnectionDialog支持编辑配置、transport抽象层(本地Wails/远程HTTP双模式)、agent后端模块
This commit is contained in:
176
internal/agent/handler/file_handler.go
Normal file
176
internal/agent/handler/file_handler.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"u-desk/internal/agent/model"
|
||||
"u-desk/internal/filesystem"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type writeFileReq struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type createReq struct {
|
||||
Type string `json:"type"` // "file" or "dir"
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type renameReq struct {
|
||||
NewPath string `json:"new_path"`
|
||||
}
|
||||
|
||||
type uploadReq struct {
|
||||
Content string `json:"content"` // base64 编码内容
|
||||
}
|
||||
|
||||
// ListOrStat 列出目录或获取文件信息(?get=stat 时返回单文件信息)
|
||||
func (h *Handler) ListOrStat(c echo.Context) error {
|
||||
path := getPath(c)
|
||||
action := c.QueryParam("get")
|
||||
|
||||
if action == "stat" {
|
||||
info, err := h.fsSvc.GetFileInfo(path)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusOK, model.NotFound(err.Error()))
|
||||
}
|
||||
return c.JSON(http.StatusOK, model.OK(info))
|
||||
}
|
||||
|
||||
files, err := h.fsSvc.ListDir(path)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusOK, model.InternalError(err.Error()))
|
||||
}
|
||||
// 限制返回数量,避免大目录导致前端卡顿
|
||||
limit := c.QueryParam("limit")
|
||||
if limit != "" {
|
||||
n := 0
|
||||
for i, f := range files {
|
||||
if n >= 500 { // 硬限制 500 条
|
||||
break
|
||||
}
|
||||
files[i] = f
|
||||
n++
|
||||
}
|
||||
files = files[:n]
|
||||
}
|
||||
return c.JSON(http.StatusOK, model.OK(files))
|
||||
}
|
||||
|
||||
// ReadFile 读取文件文本内容
|
||||
func (h *Handler) ReadFile(c echo.Context) error {
|
||||
path := getPath(c)
|
||||
content, err := h.fsSvc.ReadFile(path)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusOK, model.InternalError(err.Error()))
|
||||
}
|
||||
return c.JSON(http.StatusOK, model.OK(map[string]string{
|
||||
"content": content,
|
||||
}))
|
||||
}
|
||||
|
||||
// WriteFile 写入文件文本内容
|
||||
func (h *Handler) WriteFile(c echo.Context) error {
|
||||
path := getPath(c)
|
||||
var req writeFileReq
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, model.BadRequest("无效请求体"))
|
||||
}
|
||||
if err := h.fsSvc.WriteFile(path, req.Content); err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, model.InternalError(err.Error()))
|
||||
}
|
||||
return c.JSON(http.StatusOK, model.NoContent())
|
||||
}
|
||||
|
||||
// Create 创建文件或目录
|
||||
func (h *Handler) Create(c echo.Context) error {
|
||||
parentPath := getPath(c)
|
||||
var req createReq
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, model.BadRequest("无效请求体"))
|
||||
}
|
||||
|
||||
req.Name = strings.TrimSpace(req.Name)
|
||||
if req.Name == "" {
|
||||
return c.JSON(http.StatusBadRequest, model.BadRequest("名称不能为空"))
|
||||
}
|
||||
|
||||
var result *filesystem.FileOperationResult
|
||||
var err error
|
||||
|
||||
fullPath := filepath.Join(parentPath, req.Name)
|
||||
|
||||
switch req.Type {
|
||||
case "dir":
|
||||
result, err = h.fsSvc.CreateDir(fullPath)
|
||||
default:
|
||||
result, err = h.fsSvc.CreateFile(fullPath)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, model.InternalError(err.Error()))
|
||||
}
|
||||
return c.JSON(http.StatusCreated, model.OK(result))
|
||||
}
|
||||
|
||||
// Delete 删除文件或目录
|
||||
func (h *Handler) Delete(c echo.Context) error {
|
||||
path := getPath(c)
|
||||
result, err := h.fsSvc.DeletePath(path)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, model.InternalError(err.Error()))
|
||||
}
|
||||
return c.JSON(http.StatusOK, model.OK(result))
|
||||
}
|
||||
|
||||
// Rename 重命名文件或目录
|
||||
func (h *Handler) Rename(c echo.Context) error {
|
||||
oldPath := getPath(c)
|
||||
var req renameReq
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, model.BadRequest("无效请求体"))
|
||||
}
|
||||
req.NewPath = strings.TrimSpace(req.NewPath)
|
||||
if req.NewPath == "" {
|
||||
return c.JSON(http.StatusBadRequest, model.BadRequest("新路径不能为空"))
|
||||
}
|
||||
cleanNew := filepath.Clean(req.NewPath)
|
||||
if strings.Contains(cleanNew, "..") {
|
||||
return c.JSON(http.StatusBadRequest, model.BadRequest("新路径不允许包含 .."))
|
||||
}
|
||||
result, err := h.fsSvc.RenamePath(oldPath, cleanNew)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, model.InternalError(err.Error()))
|
||||
}
|
||||
return c.JSON(http.StatusOK, model.OK(result))
|
||||
}
|
||||
|
||||
// Upload 上传 Base64 编码的二进制文件
|
||||
func (h *Handler) Upload(c echo.Context) error {
|
||||
path := getPath(c)
|
||||
var req uploadReq
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, model.BadRequest("无效请求体"))
|
||||
}
|
||||
if req.Content == "" {
|
||||
return c.JSON(http.StatusBadRequest, model.BadRequest("内容不能为空"))
|
||||
}
|
||||
if err := h.fsSvc.SaveBase64File(path, req.Content); err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, model.InternalError(err.Error()))
|
||||
}
|
||||
return c.JSON(http.StatusOK, model.NoContent())
|
||||
}
|
||||
|
||||
// DetectType 通过文件内容检测类型
|
||||
func (h *Handler) DetectType(c echo.Context) error {
|
||||
path := getPath(c)
|
||||
info, err := h.fsSvc.DetectFileTypeByContent(path)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusOK, model.InternalError(err.Error()))
|
||||
}
|
||||
return c.JSON(http.StatusOK, model.OK(info))
|
||||
}
|
||||
Reference in New Issue
Block a user