From 4954295961e5a0cf41217c5e1111aefa3b124063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E7=90=A6?= Date: Sat, 15 Aug 2026 07:29:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=8B=A5=E5=B9=B2=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../checksum/build-frontend--DEV--RUNNER-npm- | 2 +- .../checksum/windows-common-generate-bindings | 2 +- app.go | 3 ++ build/sync.defaults.json | 7 +++ frontend/src/polish.css | 50 ++++++++++++++---- sync.go | 51 ++++++++++++------- sync_defaults.go | 23 +++++++++ synccrypto_test.go | 20 +++++--- tools/applyinit/main.go | 50 +++++++++++++++--- 9 files changed, 164 insertions(+), 44 deletions(-) create mode 100644 build/sync.defaults.json create mode 100644 sync_defaults.go diff --git a/.task/checksum/build-frontend--DEV--RUNNER-npm- b/.task/checksum/build-frontend--DEV--RUNNER-npm- index f3b6c1d..f21d393 100644 --- a/.task/checksum/build-frontend--DEV--RUNNER-npm- +++ b/.task/checksum/build-frontend--DEV--RUNNER-npm- @@ -1 +1 @@ -bea799c633664a7311e58ec89934e52 +e4f8c1296afa8a28d19333bcd28c94b1 diff --git a/.task/checksum/windows-common-generate-bindings b/.task/checksum/windows-common-generate-bindings index dbe463c..c1f373f 100644 --- a/.task/checksum/windows-common-generate-bindings +++ b/.task/checksum/windows-common-generate-bindings @@ -1 +1 @@ -b4270031ccf5e17f6edfe9de8d3baa19 +62c10364921311d089da2985d202f33e diff --git a/app.go b/app.go index 381d204..eb7c808 100644 --- a/app.go +++ b/app.go @@ -111,6 +111,7 @@ func (a *App) startup(ctx context.Context) { } a.store = s a.bootstrap = BootstrapStatus{State: BootstrapReady, DefaultPath: defaultPath, DatabasePath: c.DatabasePath} + a.applyPackagedSyncConfig() a.store.Log("info", "系统", "应用程序启动", "") a.store.Log("info", "数据库", "桌面运行时已连接", s.path) } @@ -146,6 +147,7 @@ func (a *App) InitializeDatabase(path string) (BootstrapStatus, error) { return a.bootstrap, e } a.bootstrap = BootstrapStatus{State: BootstrapReady, DefaultPath: a.bootstrap.DefaultPath, DatabasePath: s.path} + a.applyPackagedSyncConfig() a.store.Log("info", "数据库", "数据库初始化成功", s.path) return a.bootstrap, nil } @@ -604,6 +606,7 @@ func (a *App) MigrateDatabase(target string) error { return coded("BOOTSTRAP_WRITE_FAILED", e) } a.bootstrap = BootstrapStatus{State: BootstrapReady, DefaultPath: a.bootstrap.DefaultPath, DatabasePath: target} + a.applyPackagedSyncConfig() a.store.Log("info", "数据库", "数据库迁移成功", target) return nil } diff --git a/build/sync.defaults.json b/build/sync.defaults.json new file mode 100644 index 0000000..194bdf6 --- /dev/null +++ b/build/sync.defaults.json @@ -0,0 +1,7 @@ +{ + "host": "101.43.12.11", + "port": 3306, + "user": "code_count", + "password": "code_count", + "database": "code_count" +} diff --git a/frontend/src/polish.css b/frontend/src/polish.css index d52b12e..46bbba5 100644 --- a/frontend/src/polish.css +++ b/frontend/src/polish.css @@ -1,7 +1,8 @@ -main{position:relative;overflow:visible} +main{position:relative;overflow:visible;min-height:0} main::before{content:"";position:fixed;inset:0 0 0 232px;pointer-events:none;background:linear-gradient(90deg,rgba(255,255,255,.035) 1px,transparent 1px),linear-gradient(180deg,rgba(255,255,255,.03) 1px,transparent 1px);background-size:80px 80px;mask-image:linear-gradient(90deg,transparent,black 14%,black 86%,transparent);animation:gridDrift 18s linear infinite;opacity:.55} main::after{content:"";position:fixed;inset:0 0 0 232px;pointer-events:none;background:linear-gradient(115deg,transparent 0 35%,rgba(124,115,255,.1) 45%,transparent 56%);transform:translateX(-35%);animation:ambientSweep 12s ease-in-out infinite} .page{position:relative;z-index:1} +/* 主内容区已是滚动容器,sticky 相对 main 顶边,不再叠加标题栏高度 */ html{--topbar-h:0px} .sticky-head{position:sticky;top:calc(var(--topbar-h) + 8px);z-index:20;margin:-12px -10px 24px;padding:18px 20px;border:1px solid rgba(255,255,255,.08);border-radius:8px;background:color-mix(in srgb,var(--surface) calc(clamp(.72, var(--glass-user-opacity, .55) + .18, .94) * 100%),transparent);backdrop-filter:blur(24px) saturate(140%);box-shadow:0 16px 38px rgba(0,0,0,.22)} @supports not (backdrop-filter:blur(1px)){.sticky-head{background:color-mix(in srgb,var(--surface) 97%,transparent)}} @@ -498,7 +499,7 @@ html[data-theme=light] .heat-cell.l1{background:#b9efd4}html[data-theme=light] . .calendar-layout{display:grid;grid-template-columns:minmax(0,3fr) minmax(230px,1fr);gap:20px;align-items:start} .calendar-weekdays{display:grid;grid-template-columns:repeat(7,1fr);gap:6px;margin-bottom:8px;color:var(--muted);font-size:12px;text-align:center} /* 面板撑满可用视口高度,格子行高 1fr 平分剩余空间,窗口越大格子越高 */ -.calendar-grid-panel{display:flex;flex-direction:column;min-height:calc(100vh - var(--topbar-h) - 196px)} +.calendar-grid-panel{display:flex;flex-direction:column;min-height:calc(100vh - var(--titlebar-h, 40px) - 196px)} .calendar-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:6px;grid-auto-rows:minmax(86px,1fr);flex:1} .calendar-cell{position:relative;min-height:86px;border:1px solid var(--border);border-radius:7px;background:var(--surface-2);padding:6px;display:flex;flex-direction:column;gap:4px;cursor:pointer;text-align:left;color:var(--text);transition:border-color .15s,background .15s} .calendar-cell:hover{border-color:var(--primary)} @@ -627,7 +628,7 @@ html[data-theme=light] .heat-cell.l1{background:#b9efd4}html[data-theme=light] . .ai-key-notice>div{flex:1;display:grid;gap:3px} .ai-key-notice small{color:var(--muted)} /* AI 页固定视口高度:页面自身不滚动,左历史/右会话各自内部滚动 */ -.ai-page{height:calc(100vh - var(--topbar-h));display:flex;flex-direction:column;overflow:hidden;padding-bottom:22px} +.ai-page{height:calc(100vh - var(--titlebar-h, 40px));display:flex;flex-direction:column;overflow:hidden;padding-bottom:22px} .ai-page .sticky-head{position:static;flex:none;margin:-12px -10px 18px} .ai-page .ai-key-notice{flex:none} .ai-layout{flex:1;min-height:0;display:grid;grid-template-columns:290px minmax(0,1fr);gap:20px;align-items:stretch} @@ -1780,6 +1781,43 @@ html[data-theme=light] .ph-stats{background:rgba(255,255,255,.5)} /* ============ 无边框自定义标题栏(logo + 菜单 + 窗控同一行) ============ */ :root{--titlebar-h:40px} +html,body,#app{height:100%;overflow:hidden} +/* 只有主内容区滚动;侧栏不设 overflow:auto,避免滚动条夹在 rail 中间、裁切铃铛/飞出菜单 */ +.shell{ + height:100%; + min-height:0; + overflow:hidden; +} +.shell main{ + margin-top:var(--titlebar-h); + height:calc(100% - var(--titlebar-h)); + min-height:0; + overflow-x:hidden; + overflow-y:auto; +} +.shell .sidebar{ + top:var(--titlebar-h); + bottom:0; + height:auto; + overflow:visible; + z-index:30; +} +.shell .side-sub{ + min-height:0; + overflow-x:hidden; + overflow-y:auto; +} +.shell main::before,.shell main::after{top:var(--titlebar-h)} +.with-titlebar,.boot-loading.with-titlebar{padding-top:var(--titlebar-h);height:100%;min-height:0;box-sizing:border-box;overflow:auto} +.setup-screen.with-titlebar{height:100%;min-height:0;overflow:auto} + +/* 细滚动条,避免 WebView 默认粗条显得像网页 */ +.shell main,.shell .side-sub,.bell-list{scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--muted) 55%,transparent) transparent} +.shell main::-webkit-scrollbar,.shell .side-sub::-webkit-scrollbar,.bell-list::-webkit-scrollbar{width:8px;height:8px} +.shell main::-webkit-scrollbar-track,.shell .side-sub::-webkit-scrollbar-track,.bell-list::-webkit-scrollbar-track{background:transparent} +.shell main::-webkit-scrollbar-thumb,.shell .side-sub::-webkit-scrollbar-thumb,.bell-list::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--muted) 45%,transparent);border-radius:999px;border:2px solid transparent;background-clip:content-box} +.shell main::-webkit-scrollbar-thumb:hover,.shell .side-sub::-webkit-scrollbar-thumb:hover,.bell-list::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb,var(--muted) 70%,transparent);border:2px solid transparent;background-clip:content-box} + .titlebar{ position:fixed;inset:0 0 auto 0;height:var(--titlebar-h);z-index:40; display:flex;align-items:center;gap:4px;padding:0 0 0 10px; @@ -1828,12 +1866,6 @@ html[data-theme=light] .ph-stats{background:rgba(255,255,255,.5)} .tb-win:hover{background:var(--surface-3);color:var(--text)} .tb-win.close:hover{background:#e04852;color:#fff} -/* 侧栏/主区为标题栏让出顶部空间 */ -.shell .sidebar{top:var(--titlebar-h)} -.shell main{margin-top:var(--titlebar-h);min-height:calc(100vh - var(--titlebar-h))} -.shell main::before,.shell main::after{top:var(--titlebar-h)} -.with-titlebar,.boot-loading.with-titlebar{padding-top:var(--titlebar-h);min-height:100vh;box-sizing:border-box} -.setup-screen.with-titlebar,.boot-loading.with-titlebar{min-height:100vh} .rail-flyout{top:auto} .side-rail{padding-top:10px} .side-sub{padding-top:10px} diff --git a/sync.go b/sync.go index 21dab3c..2474fce 100644 --- a/sync.go +++ b/sync.go @@ -22,33 +22,48 @@ import ( const syncTimeout = 10 * time.Second -// 内置默认连接:本地 MySQL(root/root)的 code_count 库。 -// 用户零配置即可登录同步;meta 中存过自定义配置时仍以 meta 为准。 -const ( - defaultSyncHost = "127.0.0.1" - defaultSyncUser = "root" - defaultSyncPassword = "root" - defaultSyncDatabase = "code_count" -) - // ---------- 配置与状态 ---------- +// syncConfig 解析同步服务器连接。 +// 启动时会把打包配置写入本地 SQLite meta(见 applyPackagedSyncConfig); +// 运行期以 meta 为准,meta 为空时回退到打包嵌入配置。 func (a *App) syncConfig() SyncConfig { port, _ := strconv.Atoi(a.store.Meta("sync_port")) - if port <= 0 || port > 65535 { - port = 3306 - } c := SyncConfig{ - Host: a.store.Meta("sync_host"), + Host: strings.TrimSpace(a.store.Meta("sync_host")), Port: port, - User: a.store.Meta("sync_user"), + User: strings.TrimSpace(a.store.Meta("sync_user")), Password: a.store.Meta("sync_password"), - Database: a.store.Meta("sync_database"), + Database: strings.TrimSpace(a.store.Meta("sync_database")), } - if c.Host == "" && c.User == "" && c.Database == "" { - c.Host, c.User, c.Password, c.Database = defaultSyncHost, defaultSyncUser, defaultSyncPassword, defaultSyncDatabase + if syncConfigComplete(c) { + if c.Port <= 0 || c.Port > 65535 { + c.Port = 3306 + } + return c } - return c + pkg := packagedSyncDefaults() + if syncConfigComplete(pkg) { + return pkg + } + return SyncConfig{Host: "127.0.0.1", Port: 3306, User: "root", Password: "root", Database: "code_count"} +} + +// applyPackagedSyncConfig 启动时用打包配置覆盖本地 SQLite 中的同步连接, +// 避免旧安装残留的 127.0.0.1 等 meta 继续生效。 +func (a *App) applyPackagedSyncConfig() { + if a.store == nil { + return + } + pkg := packagedSyncDefaults() + if !syncConfigComplete(pkg) { + return + } + _ = a.store.SetMeta("sync_host", strings.TrimSpace(pkg.Host)) + _ = a.store.SetMeta("sync_port", strconv.Itoa(pkg.Port)) + _ = a.store.SetMeta("sync_user", strings.TrimSpace(pkg.User)) + _ = a.store.SetMeta("sync_password", pkg.Password) + _ = a.store.SetMeta("sync_database", strings.TrimSpace(pkg.Database)) } func syncConfigComplete(c SyncConfig) bool { diff --git a/sync_defaults.go b/sync_defaults.go new file mode 100644 index 0000000..ec84247 --- /dev/null +++ b/sync_defaults.go @@ -0,0 +1,23 @@ +package main + +import ( + _ "embed" + "encoding/json" +) + +//go:embed build/sync.defaults.json +var syncDefaultsJSON []byte + +// packagedSyncDefaults 读取打包时嵌入的同步服务器配置。 +// 修改连接地址请编辑 build/sync.defaults.json 后重新 wails3 task windows:build。 +func packagedSyncDefaults() SyncConfig { + var c SyncConfig + if e := json.Unmarshal(syncDefaultsJSON, &c); e != nil { + // 嵌入文件损坏时回退到安全的空配置,由调用方再兜底。 + return SyncConfig{Port: 3306} + } + if c.Port <= 0 || c.Port > 65535 { + c.Port = 3306 + } + return c +} diff --git a/synccrypto_test.go b/synccrypto_test.go index 8fef82f..3c9c08e 100644 --- a/synccrypto_test.go +++ b/synccrypto_test.go @@ -67,17 +67,21 @@ func TestSyncConfigDefaults(t *testing.T) { defer s.db.Close() a := NewApp() a.store = s + pkg := packagedSyncDefaults() + a.applyPackagedSyncConfig() c := a.syncConfig() - if c.Host != defaultSyncHost || c.User != defaultSyncUser || c.Password != defaultSyncPassword || c.Database != defaultSyncDatabase || c.Port != 3306 { - t.Fatalf("defaults not applied: %+v", c) - } - // meta 存过任意自定义配置后,以 meta 为准,不再混入默认值。 - if e := s.SetMeta("sync_host", "db.example.com"); e != nil { - t.Fatal(e) + if c.Host != pkg.Host || c.User != pkg.User || c.Password != pkg.Password || c.Database != pkg.Database || c.Port != pkg.Port { + t.Fatalf("want packaged defaults written to sqlite: %+v got %+v", pkg, c) } + // 启动覆盖后,本地残留的旧地址会被打包配置盖掉。 + _ = s.SetMeta("sync_host", "127.0.0.1") + _ = s.SetMeta("sync_user", "root") + _ = s.SetMeta("sync_password", "root") + _ = s.SetMeta("sync_database", "code_count") + a.applyPackagedSyncConfig() c = a.syncConfig() - if c.Host != "db.example.com" || c.User != "" || c.Password != "" || c.Database != "" { - t.Fatalf("explicit meta should win: %+v", c) + if c.Host != pkg.Host || c.User != pkg.User { + t.Fatalf("packaged config must overwrite sqlite meta: %+v", c) } } diff --git a/tools/applyinit/main.go b/tools/applyinit/main.go index fe20513..fc494a6 100644 --- a/tools/applyinit/main.go +++ b/tools/applyinit/main.go @@ -1,25 +1,61 @@ -// Applies init.sql to the local MySQL using the app's built-in default connection. +// Applies init.sql using the packaged sync defaults (build/sync.defaults.json). package main import ( "database/sql" + "encoding/json" "fmt" "os" + "path/filepath" "strings" _ "github.com/go-sql-driver/mysql" ) +type syncDefaults struct { + Host string `json:"host"` + Port int `json:"port"` + User string `json:"user"` + Password string `json:"password"` + Database string `json:"database"` +} + +func loadDefaults() syncDefaults { + path := filepath.Join("build", "sync.defaults.json") + raw, err := os.ReadFile(path) + if err != nil { + panic(err) + } + var c syncDefaults + if err := json.Unmarshal(raw, &c); err != nil { + panic(err) + } + if c.Port <= 0 { + c.Port = 3306 + } + return c +} + +func dsn(c syncDefaults, withDB bool) string { + db := "" + if withDB { + db = c.Database + } + return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?multiStatements=false&charset=utf8mb4", + c.User, c.Password, c.Host, c.Port, db) +} + func main() { + cfg := loadDefaults() if len(os.Args) > 1 && os.Args[1] == "inspect" { - inspect() + inspect(cfg) return } raw, err := os.ReadFile("init.sql") if err != nil { panic(err) } - db, err := sql.Open("mysql", "code_count:code_count@tcp(101.43.12.11:3306)/?multiStatements=false&charset=utf8mb4") + db, err := sql.Open("mysql", dsn(cfg, false)) if err != nil { panic(err) } @@ -45,11 +81,11 @@ func main() { } } var n int - if err := db.QueryRow("SELECT COUNT(*) FROM code_count.sync_settings").Scan(&n); err != nil { + if err := db.QueryRow("SELECT COUNT(*) FROM " + cfg.Database + ".sync_settings").Scan(&n); err != nil { fmt.Println("verify failed:", err) os.Exit(1) } - fmt.Println("ok, sync_settings rows:", n) + fmt.Println("ok, sync_settings rows:", n, "host:", cfg.Host) } func min(a, b int) int { @@ -59,8 +95,8 @@ func min(a, b int) int { return b } -func inspect() { - db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/code_count?charset=utf8mb4") +func inspect(cfg syncDefaults) { + db, err := sql.Open("mysql", dsn(cfg, true)) if err != nil { panic(err) }