更新若干功能

This commit is contained in:
李琦
2026-08-15 07:29:45 +08:00
parent ce00c9d193
commit 4954295961
9 changed files with 164 additions and 44 deletions

View File

@@ -1 +1 @@
bea799c633664a7311e58ec89934e52
e4f8c1296afa8a28d19333bcd28c94b1

View File

@@ -1 +1 @@
b4270031ccf5e17f6edfe9de8d3baa19
62c10364921311d089da2985d202f33e

3
app.go
View File

@@ -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
}

7
build/sync.defaults.json Normal file
View File

@@ -0,0 +1,7 @@
{
"host": "101.43.12.11",
"port": 3306,
"user": "code_count",
"password": "code_count",
"database": "code_count"
}

View File

@@ -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}

51
sync.go
View File

@@ -22,33 +22,48 @@ import (
const syncTimeout = 10 * time.Second
// 内置默认连接:本地 MySQLroot/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 {

23
sync_defaults.go Normal file
View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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)
}