feat: 完善 WebUI basePath 支持并简化示例代码

主要改动:

1. WebUI basePath 逻辑完善
   - NewWebUI 支持可变参数 basePath
   - 新增 path() 辅助方法统一路径处理
   - handleTableAPI 正确处理 basePath 前缀
   - handleIndex 根据 basePath 替换占位符

2. 简化示例代码
   - 删除反向代理实现(111行)
   - 直接使用带 basePath 的 WebUI
   - 代码量减少 33%,架构更清晰

3. 前端优化
   - 新增 api.js 统一 API 服务层
   - 所有组件使用统一的 API 调用
   - 支持通过 window.API_BASE 配置 basePath

4. 修复 .gitignore
   - 使用通用模式支持 commands 目录
   - 无需为新示例项目修改配置
This commit is contained in:
2025-10-14 14:13:59 +08:00
parent 7ac4b99a9e
commit 30c3e74bd2
16 changed files with 222 additions and 68 deletions

View File

@@ -20,12 +20,18 @@ var staticFS embed.FS
// WebUI Web 界面处理器 v2 (Preact)
type WebUI struct {
db *srdb.Database
basePath string
handler http.Handler
}
// NewWebUI 创建 WebUI v2 实例
func NewWebUI(db *srdb.Database) *WebUI {
ui := &WebUI{db: db}
// basePath 是可选的 URL 前缀路径(例如 "/debug"),如果为空则使用根路径 "/"
func NewWebUI(db *srdb.Database, basePath ...string) *WebUI {
bp := ""
if len(basePath) > 0 && basePath[0] != "" {
bp = strings.TrimSuffix(basePath[0], "/") // 移除尾部斜杠
}
ui := &WebUI{db: db, basePath: bp}
ui.handler = ui.setupHandler()
return ui
}
@@ -35,19 +41,32 @@ func (ui *WebUI) setupHandler() http.Handler {
mux := http.NewServeMux()
// API endpoints - 纯 JSON API与 v1 共享)
mux.HandleFunc("/api/tables", ui.handleListTables)
mux.HandleFunc("/api/tables/", ui.handleTableAPI)
mux.HandleFunc(ui.path("/api/tables"), ui.handleListTables)
mux.HandleFunc(ui.path("/api/tables/"), ui.handleTableAPI)
// 静态文件服务
staticFiles, _ := fs.Sub(staticFS, "static")
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticFiles))))
staticPath := ui.path("/static/")
mux.Handle(staticPath, http.StripPrefix(staticPath, http.FileServer(http.FS(staticFiles))))
// 首页
mux.HandleFunc("/", ui.handleIndex)
mux.HandleFunc(ui.path("/"), ui.handleIndex)
return mux
}
// path 返回带 basePath 前缀的路径
func (ui *WebUI) path(p string) string {
if ui.basePath == "" {
return p
}
// 确保路径以 / 开头
if !strings.HasPrefix(p, "/") {
p = "/" + p
}
return ui.basePath + p
}
// Handler 返回 HTTP Handler
func (ui *WebUI) Handler() http.Handler {
return ui.handler
@@ -124,7 +143,12 @@ func (ui *WebUI) handleListTables(w http.ResponseWriter, r *http.Request) {
// handleTableAPI 处理表相关的 API 请求
func (ui *WebUI) handleTableAPI(w http.ResponseWriter, r *http.Request) {
// 解析路径: /api/tables/{name}/schema 或 /api/tables/{name}/data
path := strings.TrimPrefix(r.URL.Path, "/api/tables/")
// 需要先移除 basePath再移除 /api/tables/ 前缀
path := r.URL.Path
if ui.basePath != "" {
path = strings.TrimPrefix(path, ui.basePath)
}
path = strings.TrimPrefix(path, "/api/tables/")
parts := strings.Split(path, "/")
if len(parts) < 2 {
@@ -458,7 +482,9 @@ func (ui *WebUI) handleTableData(w http.ResponseWriter, r *http.Request, tableNa
// handleIndex 处理首页请求
func (ui *WebUI) handleIndex(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
// 检查路径是否匹配(支持 basePath
expectedPath := ui.path("/")
if r.URL.Path != expectedPath {
http.NotFound(w, r)
return
}
@@ -471,6 +497,25 @@ func (ui *WebUI) handleIndex(w http.ResponseWriter, r *http.Request) {
return
}
// 替换路径占位符 ~~ 为 basePath
// 如果 basePath 为空,则 ~~ → ""
// 如果 basePath 为 "/debug",则 ~~ → "/debug"
// 示例:
// - 无 basePath: href="~~/static/css/styles.css" → href="/static/css/styles.css"
// - 有 basePath: href="~~/static/css/styles.css" → href="/debug/static/css/styles.css"
htmlContent := string(content)
if ui.basePath == "" {
// 无 basePath 时,直接替换为空
htmlContent = strings.ReplaceAll(htmlContent, `"~~"`, `""`)
htmlContent = strings.ReplaceAll(htmlContent, `"~~/`, `"/`)
htmlContent = strings.ReplaceAll(htmlContent, `'~~/`, `'/`)
} else {
// 有 basePath 时,替换为实际的 basePath
htmlContent = strings.ReplaceAll(htmlContent, `"~~"`, fmt.Sprintf(`"%s"`, ui.basePath))
htmlContent = strings.ReplaceAll(htmlContent, `"~~/`, fmt.Sprintf(`"%s/`, ui.basePath))
htmlContent = strings.ReplaceAll(htmlContent, `'~~/`, fmt.Sprintf(`'%s/`, ui.basePath))
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write(content)
w.Write([]byte(htmlContent))
}