Compare commits

27 Commits
Author SHA1 Message Date
chengmaandClaude Opus 4.8 71c6f22902 docs: add Phase SP tasks for Shopee CDP multi-shop module
拆分虾皮店铺模块为 P1 列表与进程编排、P2 预览宫格、P3 RPA 引擎共 11 个任务,
设计依据 Obsidian 多店铺 CDP 管理方案。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 14:41:33 +08:00
QiuSW c77c808606 fix: support titlebar double click maximize 2026-07-09 10:59:49 +08:00
QiuSW 3f01e9a083 docs: add titlebar double click task 2026-07-09 10:46:32 +08:00
QiuSW 21953fed2a fix: fill maximized main window 2026-07-09 09:59:13 +08:00
QiuSW 0bc023f3f9 docs: add maximize layout fix task 2026-07-09 09:52:42 +08:00
QiuSW bda9c57843 feat: start ecommerce product center 2026-07-09 09:10:22 +08:00
QiuSW b8323550da fix: speed up wails dev frontend checks 2026-07-09 08:51:22 +08:00
QiuSW 9137699935 docs: record template smoke checklist 2026-07-09 01:26:22 +08:00
QiuSW 07357c4766 docs: add windows packaging verification 2026-07-09 01:20:36 +08:00
QiuSW 7fbcd3a838 docs: document wails binding generation 2026-07-09 01:18:11 +08:00
QiuSW 76cda6b49c feat: add interactive component examples 2026-07-09 01:16:47 +08:00
QiuSW 357747dd17 feat: build image optimizer page 2026-07-09 01:11:48 +08:00
QiuSW 2c31a41036 feat: add dashboard metrics service 2026-07-09 01:04:50 +08:00
QiuSW 769c861eb0 feat: add audit and app log services 2026-07-09 00:55:46 +08:00
QiuSW 3bd05b316e feat: add image optimization task model 2026-07-09 00:47:35 +08:00
QiuSW 7942add8b6 feat: persist main window state 2026-07-09 00:34:47 +08:00
QiuSW 91debb9ad6 feat: add settings schema 2026-07-09 00:12:30 +08:00
QiuSW 5aaac0b166 feat: add auth provider boundary 2026-07-08 23:37:19 +08:00
QiuSW 811a9c889f feat: add main app route skeleton 2026-07-08 23:06:48 +08:00
QiuSW 4c1de99d65 fix: speed up wails dev gui startup 2026-07-08 22:17:42 +08:00
QiuSW 462ff5ac4c fix: make dev script choose free port 2026-07-08 16:33:18 +08:00
QiuSW 7c31554f64 chore: add wails dev and build scripts 2026-07-08 16:18:53 +08:00
QiuSW a429353c42 feat: implement main window layout strategy 2026-07-08 16:01:16 +08:00
QiuSW 9472f005c8 docs: define main window layout strategy 2026-07-08 15:49:44 +08:00
QiuSW 47d3c653c6 docs: align vision with reusable desktop template 2026-07-08 15:45:01 +08:00
QiuSW d2eeb049d0 docs: add harness coding documentation 2026-07-08 15:27:21 +08:00
QiuSW e5d10577c0 chore: migrate and structure cmbone project 2026-07-08 15:13:26 +08:00
174 changed files with 16790 additions and 25 deletions
+20 -24
View File
@@ -1,27 +1,23 @@
# ---> Go .task/
# If you prefer the allow list template instead of the deny list, see community template: bin/
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore dist/
# node_modules/
# Binaries for programs and plugins frontend/dist/
*.exe frontend/node_modules/
*.exe~ frontend/.vite/
*.dll frontend/coverage/
*.so build/linux/appimage/build/
*.dylib build/windows/nsis/MicrosoftEdgeWebview2Setup.exe
# Test binary, built with `go test -c` .DS_Store
*.test *.log
*.local
*.syso
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
go.work.sum
# env file
.env .env
.env.*
!.env.example
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
+72
View File
@@ -0,0 +1,72 @@
# AGENTS.md
> Codex / AI coding agent 的仓库级入口。进入本仓库后,先读本文,再进入 `docs/00-ai-start-here.md`。
## 项目定位
`cmbone` 是从 `SuiDemo` 迁移而来的 Wails v3 本地桌面项目。当前目标是打造一个 Windows-first、可扩展到其他平台的可复用桌面应用模板,而不是某个单一业务产品。
模板要内置常见桌面应用能力:应用壳、设置、登录、主业务模块、操作日志、数据统计和常用业务组件。第一批业务样例使用“电商商品图片 AI 优化”,用来验证模板能否承载真实业务闭环。
项目当前已具备:
- Wails v3 桌面壳和前端嵌入。
- Vue 3 设置页、多语言、主题切换。
- 本地 SQLite 配置和热键表。
- 托盘菜单、快捷键、OCR 示例窗口。
目标模块包括:
- 登录:本地固定账号 provider 和真实后端 provider 两种路径。
- 设置:主题、语言、窗口行为、快捷键、数据目录、日志策略。
- 主业务:商品图片 AI 优化样例,包括任务、结果、失败反馈。
- 操作日志:审计日志和运行日志分开设计。
- 数据统计:图片处理量、成功率、耗时、失败数、日志数和 7 天趋势。
- 常用组件:输入框、多选框、下拉框、多行文本、表格、筛选和分页示例。
## 必读顺序
每次开始工作前,按顺序读取:
1. `README.md`:了解项目运行、构建和维护约定。
2. `docs/README.md`:了解 Harness Coding 文档导航。
3. `docs/00-ai-start-here.md`:进入固定开工流程。
4. `docs/05-coding-rules.md`:确认编码纪律。
5. `docs/current-state.md`:确认当前真实状态。
6. `progress.md`:查看最近验证结果和决策。
7. 与当前任务相关的具体文档。
## 工作规则
- 只做当前任务要求的改动,不夹带重构、依赖升级或无验收标准的产品扩展。
- 保持 Go `1.23.0`、Wails `v3.0.0-alpha.9`、`@wailsio/runtime 3.0.0-alpha.66` 的兼容关系;不要直接升级到 latest。
- 不提交 `bin/`、`frontend/dist/`、`frontend/node_modules/` 等构建产物或依赖目录。
- 后端服务方法变更后,优先使用 `wails3 generate bindings` 重新生成 `frontend/bindings`;如果本机没有 CLI,只允许小范围手动修正并在 `progress.md` 记录原因。
- 涉及存储结构时,同步更新 `docs/04-architecture.md`、`docs/api.md`、`docs/current-state.md` 和相关任务验收。
- 登录、日志、统计和业务样例必须通过 provider / service 边界接入,不把演示逻辑写死在前端页面里。
- 自研组件库不是目标;优先复用 Ant Design Vue,并沉淀项目级组合组件和使用示例。
- 提交前至少运行 `go test ./...`、`cd frontend && npm run build`、`go build -o bin/cmbone.exe .`。
## 风格
- 文档默认中文,代码标识符默认英文。
- 优先复用现有服务、组件和工具函数。
- 错误必须处理,不吞错。
- 注释只解释不明显的原因,不复述代码。
- 新增抽象必须能减少真实复杂度,否则保持直接实现。
## 验证入口
Windows 原生 PowerShell 使用:
```powershell
.\init.ps1
```
Git Bash / WSL / macOS / Linux 使用:
```bash
./init.sh
```
脚本会安装前端依赖、运行后端测试、构建前端、编译后端,并打印开发启动命令。
+13
View File
@@ -0,0 +1,13 @@
# CLAUDE.md
本仓库的 AI coding agent 规则以 [`AGENTS.md`](AGENTS.md) 为准。
开始任何任务前,先读取:
1. `AGENTS.md`
2. `docs/00-ai-start-here.md`
3. `docs/05-coding-rules.md`
4. `docs/current-state.md`
5. `progress.md`
不要绕过这些文件直接修改代码。
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 JinGong
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+120 -1
View File
@@ -1,3 +1,122 @@
# cmbone # cmbone
桌面端骨架 基于 SuiDemo 迁移而来的 Wails v3 桌面项目,目标是打造一个 Windows-first、可扩展到其他平台的可复用桌面应用模板。
模板内置方向包括:应用壳、设置、登录、主业务模块、操作日志、数据统计和常用业务组件。第一批业务样例以“电商商品图片 AI 优化”为主,用它验证模板是否能支撑真实业务闭环。
`ec` 分支开始面向电商运营应用开发,当前新增商品中心,后续会把商品、SKU、渠道、库存和商品图片优化串成业务闭环。
## 技术栈
- 后端:Go 1.23.0、Wails v3.0.0-alpha.9、SQLite
- 前端:Vue 3、TypeScript、Vite、Tailwind CSS、Ant Design Vue
- 当前功能:多语言、主题切换、统一设置 schema、主窗口状态恢复、本地配置、快捷键、本地登录 provider、商品中心、商品图片 AI 优化页面、本地模拟图片优化任务、审计日志、运行日志、P0 数据统计服务、常用组件示例页、OCR 示例
- 目标模板模块:登录、设置、主业务、审计日志、运行日志、数据统计、常用组件示例
## 环境准备
当前项目按本机 Go 1.23.0 锁定依赖,不要直接安装 `wails3@latest`。
```powershell
go install github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-alpha.9
```
如果安装后 PowerShell 找不到 `wails3`,把 Go bin 目录加入当前终端 PATH:
```powershell
$env:Path += ";$(go env GOPATH)\bin"
wails3 doctor
```
## 目录结构
```text
.
├── main.go # Wails 应用入口
├── internal/
│ ├── models/ # 后端数据模型
│ └── services/ # 后端服务和数据库逻辑
├── platform/ # Windows/macOS 平台差异代码
├── frontend/
│ ├── src/ # Vue 前端源码
│ ├── bindings/ # Wails 前端绑定
│ └── dist/ # 前端构建产物,供 Go embed 使用
└── build/ # Wails 构建配置
```
## 常用命令
```bash
go test ./...
cd frontend
npm install
npm run build
```
安装 `wails3` 后可以使用:
```bash
wails3 dev
wails3 build
```
Windows 下也可以直接运行:
```powershell
.\dev.bat
.\build.bat
```
`dev.bat` 会从 `9245` 开始选择一个空闲 Vite 端口,避免已有 dev server 占用端口时无法打开 GUI。
开发启动使用快速前端 dev 构建以尽快打开桌面窗口;提交前仍以 `npm run build` 做完整 TypeScript 检查和生产构建。
Windows 打包验证见 [`docs/windows-packaging.md`](docs/windows-packaging.md)。当前默认打包路径是 NSIS,要求本机安装 `makensis`。
## Wails Bindings
修改 Go 服务的公开方法、参数模型、返回模型,或在 `main.go` 注册新服务后,需要从项目根目录重新生成前端 bindings:
```powershell
$env:Path += ";$(go env GOPATH)\bin"
wails3 generate bindings -clean=true
```
当前固定使用 `wails3 v3.0.0-alpha.9`,前端 runtime 固定为 `@wailsio/runtime 3.0.0-alpha.66`。不要用 `latest` 混装。
常见失败处理:
- `wails3` 找不到:先按环境准备安装固定版本,或把 `$(go env GOPATH)\bin` 加入当前终端 PATH。
- 生成失败:先运行 `go test ./...` 或 `go build -o bin\cmbone.exe .` 修复 Go 编译错误。
- 前端 import 失效:重新执行 `wails3 generate bindings -clean=true`,前端源码继续使用无扩展名 import。
生成后提交 `frontend/bindings` 的变更,并运行 `npm run build` 验证前端类型和打包。
本地演示登录账号为 `admin` / `admin123`;密码由后端初始化为 hash,不写在前端页面里。
## 维护约定
- Go 模块名统一为 `cmbone`。
- 当前依赖按系统 Go 1.23.0 锁定,升级 Go 前不要随意升级 Wails、SQLite 或热键库。
- 修改后端服务后,优先使用 `wails3 generate bindings` 重新生成 `frontend/bindings`;如果本机没有 CLI,只做小范围手动绑定。
- 提交前至少运行 `go test ./...` 和 `npm run build`。
- 模板能力按任务逐步落地,不一次性堆完整平台;优先保证 Windows 10+ 体验和可维护性。
## Harness Coding 文档
本项目已接入 Harness Coding 文档,用于让后续维护者和 AI coding agent 快速恢复上下文、领取任务并验证改动。
- [`AGENTS.md`](AGENTS.md):仓库级 agent 入口。
- [`docs/00-ai-start-here.md`](docs/00-ai-start-here.md):固定开工流程。
- [`docs/current-state.md`](docs/current-state.md):当前真实状态和命令。
- [`docs/06-tasks.md`](docs/06-tasks.md):任务看板。
- [`docs/windows-packaging.md`](docs/windows-packaging.md):Windows 打包验证流程。
- [`docs/smoke-checklist.md`](docs/smoke-checklist.md):模板 smoke 验证清单。
- [`docs/ecommerce-scenario.md`](docs/ecommerce-scenario.md):`ec` 分支电商场景开发说明。
- [`progress.md`](progress.md):执行流水和验证证据。
统一验证入口:
```powershell
.\init.ps1
```
+33
View File
@@ -0,0 +1,33 @@
version: '3'
includes:
common: ./build/Taskfile.yml
windows: ./build/windows/Taskfile.yml
darwin: ./build/darwin/Taskfile.yml
linux: ./build/linux/Taskfile.yml
vars:
APP_NAME: cmbone
BIN_DIR: bin
VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}'
tasks:
build:
summary: Builds the application
cmds:
- task: '{{OS}}:build'
package:
summary: Packages a production build of the application
cmds:
- task: '{{OS}}:package'
run:
summary: Runs the application
cmds:
- task: '{{OS}}:run'
dev:
summary: Runs the application in development mode
cmds:
- wails3 dev -config ./build/config.yml -port {{.VITE_PORT}}
+1
View File
@@ -0,0 +1 @@
@cd /d "%~dp0" && for /f "delims=" %%G in ('go env GOPATH') do @set "PATH=%%G\bin;%PATH%" && wails3 build
+95
View File
@@ -0,0 +1,95 @@
version: '3'
tasks:
go:mod:tidy:
summary: Runs `go mod tidy`
internal: true
cmds:
- go mod tidy
install:frontend:deps:
summary: Install frontend dependencies
dir: frontend
sources:
- package.json
- package-lock.json
generates:
- node_modules/*
preconditions:
- sh: npm version
msg: "npm is required. Install it with Node.js."
cmds:
- npm install
build:frontend:
label: build:frontend (PRODUCTION={{.PRODUCTION}})
summary: Build the frontend project
dir: frontend
sources:
- index.html
- package.json
- package-lock.json
- postcss.config.js
- tailwind.config.js
- tsconfig.json
- tsconfig.node.json
- vite.config.ts
- public/**/*
- src/**/*
- bindings/**/*
generates:
- dist/**/*
deps:
- task: install:frontend:deps
- task: generate:bindings
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
cmds:
- npm run {{.BUILD_COMMAND}} -q
env:
PRODUCTION: '{{.PRODUCTION | default "false"}}'
vars:
BUILD_COMMAND: '{{if eq .PRODUCTION "true"}}build{{else}}build:dev{{end}}'
generate:bindings:
label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}})
summary: Generates bindings for the frontend
deps:
- task: go:mod:tidy
sources:
- "**/*.[jt]s"
- exclude: frontend/**/*
- frontend/bindings/**/*
- "**/*.go"
- go.mod
- go.sum
generates:
- frontend/bindings/**/*
cmds:
- wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true {{if .Typescript}} -ts{{end}}
generate:icons:
summary: Generates Windows `.ico` and Mac `.icns` files from an image
dir: build
sources:
- appicon.png
generates:
- darwin/icons.icns
- windows/icon.ico
cmds:
- wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico
dev:frontend:
summary: Runs the frontend in development mode
dir: frontend
deps:
- task: install:frontend:deps
cmds:
- npm run dev -- --port {{.VITE_PORT}} --strictPort
update:build-assets:
summary: Updates the build assets
dir: build
cmds:
- wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir .
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

+63
View File
@@ -0,0 +1,63 @@
# This file contains the configuration for this project.
# When you update `info` or `fileAssociations`, run `wails3 task common:update:build-assets` to update the assets.
# Note that this will overwrite any changes you have made to the assets.
version: '3'
# This information is used to generate the build assets.
info:
companyName: "cmbone"
productName: "cmbone"
productIdentifier: "com.cmbone.app"
description: "A Wails desktop application"
copyright: "(c) 2026, cmbone"
comments: "cmbone desktop application"
version: "0.0.1" # The application version
# Dev mode configuration
dev_mode:
root_path: .
log_level: warn
debounce: 1000
ignore:
dir:
- .git
- node_modules
- frontend
- bin
file:
- .DS_Store
- .gitignore
- .gitkeep
watched_extension:
- "*.go"
git_ignore: true
executes:
- cmd: wails3 task common:install:frontend:deps
type: once
- cmd: wails3 task common:dev:frontend
type: background
- cmd: go mod tidy
type: blocking
- cmd: wails3 task build
type: blocking
- cmd: wails3 task run
type: primary
# File Associations
# More information at: https://v3.wails.io/noit/done/yet
fileAssociations:
# - ext: wails
# name: Wails
# description: Wails Application File
# iconName: wailsFileIcon
# role: Editor
# - ext: jpg
# name: JPEG
# description: Image File
# iconName: jpegFileIcon
# role: Editor
# mimeType: image/jpeg # (optional)
# Other data
other:
- name: My Other Data
+81
View File
@@ -0,0 +1,81 @@
version: '3'
includes:
common: ../Taskfile.yml
tasks:
build:
summary: Creates a production build of the application
deps:
- task: common:go:mod:tidy
- task: common:build:frontend
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
PRODUCTION:
ref: .PRODUCTION
- task: common:generate:icons
cmds:
- go build {{.BUILD_FLAGS}} -o {{.OUTPUT}}
vars:
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}'
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
env:
GOOS: darwin
CGO_ENABLED: 1
GOARCH: '{{.ARCH | default ARCH}}'
CGO_CFLAGS: "-mmacosx-version-min=10.15"
CGO_LDFLAGS: "-mmacosx-version-min=10.15"
MACOSX_DEPLOYMENT_TARGET: "10.15"
PRODUCTION: '{{.PRODUCTION | default "false"}}'
build:universal:
summary: Builds darwin universal binary (arm64 + amd64)
deps:
- task: build
vars:
ARCH: amd64
OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-amd64"
- task: build
vars:
ARCH: arm64
OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-arm64"
cmds:
- lipo -create -output "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64"
- rm "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64"
package:
summary: Packages a production build of the application into a `.app` bundle
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- task: create:app:bundle
package:universal:
summary: Packages darwin universal binary (arm64 + amd64)
deps:
- task: build:universal
cmds:
- task: create:app:bundle
create:app:bundle:
summary: Creates an `.app` bundle
cmds:
- mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/{MacOS,Resources}
- cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources
- cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS
- cp build/darwin/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents
- codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.app
run:
cmds:
- mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/{MacOS,Resources}
- cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources
- cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS
- cp build/darwin/Info.dev.plist {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Info.plist
- codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.dev.app
- '{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS/{{.APP_NAME}}'
Binary file not shown.
+119
View File
@@ -0,0 +1,119 @@
version: '3'
includes:
common: ../Taskfile.yml
tasks:
build:
summary: Builds the application for Linux
deps:
- task: common:go:mod:tidy
- task: common:build:frontend
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
PRODUCTION:
ref: .PRODUCTION
- task: common:generate:icons
cmds:
- go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}
vars:
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}'
env:
GOOS: linux
CGO_ENABLED: 1
GOARCH: '{{.ARCH | default ARCH}}'
PRODUCTION: '{{.PRODUCTION | default "false"}}'
package:
summary: Packages a production build of the application for Linux
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- task: create:appimage
- task: create:deb
- task: create:rpm
- task: create:aur
create:appimage:
summary: Creates an AppImage
dir: build/linux/appimage
deps:
- task: build
vars:
PRODUCTION: "true"
- task: generate:dotdesktop
cmds:
- cp {{.APP_BINARY}} {{.APP_NAME}}
- cp ../../appicon.png appicon.png
- wails3 generate appimage -binary {{.APP_NAME}} -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build
vars:
APP_NAME: '{{.APP_NAME}}'
APP_BINARY: '../../../bin/{{.APP_NAME}}'
ICON: '../../appicon.png'
DESKTOP_FILE: '../{{.APP_NAME}}.desktop'
OUTPUT_DIR: '../../../bin'
create:deb:
summary: Creates a deb package
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- task: generate:dotdesktop
- task: generate:deb
create:rpm:
summary: Creates a rpm package
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- task: generate:dotdesktop
- task: generate:rpm
create:aur:
summary: Creates a arch linux packager package
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- task: generate:dotdesktop
- task: generate:aur
generate:deb:
summary: Creates a deb package
cmds:
- wails3 tool package -name {{.APP_NAME}} -format deb -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin
generate:rpm:
summary: Creates a rpm package
cmds:
- wails3 tool package -name {{.APP_NAME}} -format rpm -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin
generate:aur:
summary: Creates a arch linux packager package
cmds:
- wails3 tool package -name {{.APP_NAME}} -format archlinux -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin
generate:dotdesktop:
summary: Generates a `.desktop` file
dir: build
cmds:
- mkdir -p {{.ROOT_DIR}}/build/linux/appimage
- wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile {{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop -categories "{{.CATEGORIES}}"
vars:
APP_NAME: '{{.APP_NAME}}'
EXEC: '{{.APP_NAME}}'
ICON: 'appicon'
CATEGORIES: 'Development;'
OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop'
run:
cmds:
- '{{.BIN_DIR}}/{{.APP_NAME}}'
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# Copyright (c) 2018-Present Lea Anthony
# SPDX-License-Identifier: MIT
# Fail script on any error
set -euxo pipefail
# Define variables
APP_DIR="${APP_NAME}.AppDir"
# Create AppDir structure
mkdir -p "${APP_DIR}/usr/bin"
cp -r "${APP_BINARY}" "${APP_DIR}/usr/bin/"
cp "${ICON_PATH}" "${APP_DIR}/"
cp "${DESKTOP_FILE}" "${APP_DIR}/"
if [[ $(uname -m) == *x86_64* ]]; then
# Download linuxdeploy and make it executable
wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
chmod +x linuxdeploy-x86_64.AppImage
# Run linuxdeploy to bundle the application
./linuxdeploy-x86_64.AppImage --appdir "${APP_DIR}" --output appimage
else
# Download linuxdeploy and make it executable (arm64)
wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage
chmod +x linuxdeploy-aarch64.AppImage
# Run linuxdeploy to bundle the application (arm64)
./linuxdeploy-aarch64.AppImage --appdir "${APP_DIR}" --output appimage
fi
# Rename the generated AppImage
mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage"
+1
View File
@@ -0,0 +1 @@
#!/bin/bash
+1
View File
@@ -0,0 +1 @@
#!/bin/bash
+1
View File
@@ -0,0 +1 @@
#!/bin/bash
+1
View File
@@ -0,0 +1 @@
#!/bin/bash
+98
View File
@@ -0,0 +1,98 @@
version: '3'
includes:
common: ../Taskfile.yml
tasks:
build:
summary: Builds the application for Windows
deps:
- task: common:go:mod:tidy
- task: common:build:frontend
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
PRODUCTION:
ref: .PRODUCTION
- task: common:generate:icons
cmds:
- task: generate:syso
- go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}.exe
- cmd: powershell Remove-item *.syso
platforms: [windows]
- cmd: rm -f *.syso
platforms: [linux, darwin]
vars:
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}'
env:
GOOS: windows
CGO_ENABLED: 0
GOARCH: '{{.ARCH | default ARCH}}'
PRODUCTION: '{{.PRODUCTION | default "false"}}'
package:
summary: Packages a production build of the application
cmds:
- |-
if [ "{{.FORMAT | default "nsis"}}" = "msix" ]; then
task: create:msix:package
else
task: create:nsis:installer
fi
vars:
FORMAT: '{{.FORMAT | default "nsis"}}'
generate:syso:
summary: Generates Windows `.syso` file
dir: build
cmds:
- wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso
vars:
ARCH: '{{.ARCH | default ARCH}}'
create:nsis:installer:
summary: Creates an NSIS installer
dir: build/windows/nsis
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
# Create the Microsoft WebView2 bootstrapper if it doesn't exist
- wails3 generate webview2bootstrapper -dir "{{.ROOT_DIR}}/build/windows/nsis"
- makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" project.nsi
vars:
ARCH: '{{.ARCH | default ARCH}}'
ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}'
create:msix:package:
summary: Creates an MSIX package
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- |-
wails3 tool msix \
--config "{{.ROOT_DIR}}/wails.json" \
--name "{{.APP_NAME}}" \
--executable "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" \
--arch "{{.ARCH}}" \
--out "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}-{{.ARCH}}.msix" \
{{if .CERT_PATH}}--cert "{{.CERT_PATH}}"{{end}} \
{{if .PUBLISHER}}--publisher "{{.PUBLISHER}}"{{end}} \
{{if .USE_MSIX_TOOL}}--use-msix-tool{{else}}--use-makeappx{{end}}
vars:
ARCH: '{{.ARCH | default ARCH}}'
CERT_PATH: '{{.CERT_PATH | default ""}}'
PUBLISHER: '{{.PUBLISHER | default ""}}'
USE_MSIX_TOOL: '{{.USE_MSIX_TOOL | default "false"}}'
install:msix:tools:
summary: Installs tools required for MSIX packaging
cmds:
- wails3 tool msix-install-tools
run:
cmds:
- '{{.BIN_DIR}}/{{.APP_NAME}}.exe'
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

+15
View File
@@ -0,0 +1,15 @@
{
"fixed": {
"file_version": "1.0.0.0"
},
"info": {
"0000": {
"ProductVersion": "1.0.0.0",
"CompanyName": "cmbone",
"FileDescription": "cmbone desktop application",
"LegalCopyright": "(c) 2026 cmbone",
"ProductName": "cmbone",
"Comments": "Reusable Wails desktop application template"
}
}
}
+112
View File
@@ -0,0 +1,112 @@
Unicode true
####
## Please note: Template replacements don't work in this file. They are provided with default defines like
## mentioned underneath.
## If the keyword is not defined, "wails_tools.nsh" will populate them.
## If they are defined here, "wails_tools.nsh" will not touch them. This allows you to use this project.nsi manually
## from outside of Wails for debugging and development of the installer.
##
## For development first make a wails nsis build to populate the "wails_tools.nsh":
## > wails build --target windows/amd64 --nsis
## Then you can call makensis on this file with specifying the path to your binary:
## For a AMD64 only installer:
## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe
## For a ARM64 only installer:
## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe
## For a installer with both architectures:
## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe
####
## The following information is taken from the wails_tools.nsh file, but they can be overwritten here.
####
## !define INFO_PROJECTNAME "my-project" # Default "{{.Name}}"
## !define INFO_COMPANYNAME "My Company" # Default "{{.ProductCompany}}"
## !define INFO_PRODUCTNAME "My Product Name" # Default "{{.ProductName}}"
## !define INFO_PRODUCTVERSION "1.0.0" # Default "{{.ProductVersion}}"
## !define INFO_COPYRIGHT "(c) Now, My Company" # Default "{{.ProductCopyright}}"
###
## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe"
## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}"
####
## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html
####
## Include the wails tools
####
!include "wails_tools.nsh"
# The version information for this two must consist of 4 parts
VIProductVersion "${INFO_PRODUCTVERSION}.0"
VIFileVersion "${INFO_PRODUCTVERSION}.0"
VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}"
VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer"
VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}"
VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}"
VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}"
VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}"
# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware
ManifestDPIAware true
!include "MUI.nsh"
!define MUI_ICON "..\icon.ico"
!define MUI_UNICON "..\icon.ico"
# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314
!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps
!define MUI_ABORTWARNING # This will warn the user if they exit from the installer.
!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page.
# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer
!insertmacro MUI_PAGE_DIRECTORY # In which folder install page.
!insertmacro MUI_PAGE_INSTFILES # Installing page.
!insertmacro MUI_PAGE_FINISH # Finished installation page.
!insertmacro MUI_UNPAGE_INSTFILES # Uninstalling page
!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer
## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1
#!uninstfinalize 'signtool --file "%1"'
#!finalize 'signtool --file "%1"'
Name "${INFO_PRODUCTNAME}"
OutFile "..\..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file.
InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder).
ShowInstDetails show # This will always show the installation details.
Function .onInit
!insertmacro wails.checkArchitecture
FunctionEnd
Section
!insertmacro wails.setShellContext
!insertmacro wails.webview2runtime
SetOutPath $INSTDIR
!insertmacro wails.files
CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
!insertmacro wails.associateFiles
!insertmacro wails.writeUninstaller
SectionEnd
Section "uninstall"
!insertmacro wails.setShellContext
RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath
RMDir /r $INSTDIR
Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk"
Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk"
!insertmacro wails.unassociateFiles
!insertmacro wails.deleteUninstaller
SectionEnd
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity type="win32" name="cmbone.desktop" version="1.0.0.0" processorArchitecture="*"/>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>
+1
View File
@@ -0,0 +1 @@
@cd /d "%~dp0" && for /f "delims=" %%G in ('go env GOPATH') do @set "PATH=%%G\bin;%PATH%" && for /f %%P in ('powershell -NoProfile -Command "$p=9245; while (Get-NetTCPConnection -LocalPort $p -State Listen -ErrorAction SilentlyContinue) { $p++ }; $p"') do @wails3 dev -config .\build\config.yml -port %%P
+104
View File
@@ -0,0 +1,104 @@
# AI 开发入口
> 给 AI coding agent 的项目入口。硬性编码规则见 [`05-coding-rules.md`](05-coding-rules.md)。
## 一句话定位
`cmbone` 是一个 Windows-first、可扩展到其他平台的 Wails v3 + Vue 3 + SQLite 可复用桌面应用模板。
模板目标不是做空泛框架,而是沉淀桌面端常用模块:应用壳、设置、登录、主业务模块、操作日志、数据统计和常用业务组件。第一批业务样例使用“电商商品图片 AI 优化”来验证模板是否能支撑真实业务闭环。
## 必读顺序
每次开始写代码前,按这个顺序建立上下文:
1. [`01-vision.md`](01-vision.md):为什么做、为谁做、什么不做。
2. [`02-requirements.md`](02-requirements.md):模板 MVP 要什么、怎么算达成。
3. [`03-tech-stack.md`](03-tech-stack.md):既定技术选型。
4. [`04-architecture.md`](04-architecture.md):系统结构、职责划分、数据模型和关键难点。
5. [`05-coding-rules.md`](05-coding-rules.md):写代码前必须遵守的规则。
6. [`06-tasks.md`](06-tasks.md):领取本轮唯一任务。
7. [`../progress.md`](../progress.md):历史执行记录、验证结果、阻塞点和关键决策。
8. [`current-state.md`](current-state.md):当前代码现实、可运行命令、下一步任务。
仓库根目录的 `AGENTS.md` 和 `CLAUDE.md` 是仓库级规则,优先级高于本目录中的建议。
## 固定开工流程
1. `pwd`:确认在 `D:\chengma\cmbone` 或对应克隆目录。
2. 读 [`../progress.md`](../progress.md) 和 [`current-state.md`](current-state.md):恢复已验证状态、下一步和当前 blocker。
3. `git log --oneline -5`:看清最近发生了什么。
4. 运行 `.\init.ps1`,Git Bash / WSL 用 `./init.sh`。
5. 如果基线失败,先修基线,不在坏的起点上叠新功能。
6. 基线绿了,再从 [`06-tasks.md`](06-tasks.md) 领取唯一任务。
## 当前阶段
当前项目处于:可复用桌面应用模板的 Phase 1 基础模块建设阶段。
优先路径:
1. Phase 0:保持迁移后的 Wails/Vue/SQLite 基线可运行、可测试、可构建。
2. Phase 1:建立 Windows-first 的应用壳、主布局、设置和登录边界。
3. Phase 2:实现商品图片 AI 优化样例、审计日志、运行日志和数据统计。
4. Phase 3:补齐组件示例、测试、打包和交接文档。
## 领取任务规则
- 只领取第一个状态为 `TODO` 且依赖均为 `DONE` 的任务。
- 开始前把该任务状态改为 `DOING`。
- 本轮只完成这一个任务。
- 验收通过后把状态改为 `DONE`。
- 完成后把执行记录追加到 [`../progress.md`](../progress.md),并覆盖更新 [`current-state.md`](current-state.md) 的当前快照。
- 结束会话前过一遍 [`clean-state-checklist.md`](clean-state-checklist.md)。
## 模板 MVP 边界
MVP 只做:
- 应用壳:主窗口、侧边导航、顶部用户区、内容区,Windows 10+ 优先。
- 窗口策略:主窗口默认不最大化,初始 `1280x800`,居中且可调整大小;第二窗口保持小窗。
- 登录:本地固定账号 provider 和真实后端 provider 的可替换边界。
- 设置:主题、语言、窗口行为、快捷键、数据目录和日志策略。
- 主业务样例:电商商品图片 AI 优化的任务列表、状态、结果和失败反馈。
- 操作日志:审计日志用于记录用户动作,运行日志用于排错。
- 数据统计:今日处理图片数、AI 优化成功率、平均耗时、失败任务数、今日操作日志数、最近 7 天处理趋势。
- 常用组件示例:输入框、多选框、下拉框、多行文本、表格、筛选和分页。
MVP 不做:
- 完整权限系统、角色系统或企业 SSO。
- 完整 macOS / Linux 体验承诺;当前是 Windows-first,其他平台保持结构可扩展。
- 自研 UI 组件库;优先复用 Ant Design Vue。
- 复杂 BI 报表、自动更新和插件市场。
- 未确认接口前的真实 AI 服务深度集成。
## 常见任务该看哪里
做页面 / UI:先看 [`02-requirements.md`](02-requirements.md)、[`routes.md`](routes.md),再看现有 Vue 组件。
做后端服务:先看 [`api.md`](api.md)、[`04-architecture.md`](04-architecture.md),再看 `internal/services/`。
做登录:先看 [`04-architecture.md`](04-architecture.md) 的 AuthProvider 边界,不把账号密码写死在前端。
做日志:区分审计日志和运行日志,先看 [`04-architecture.md`](04-architecture.md) 的数据模型。
做统计:只围绕 MVP 指标,不提前做复杂报表平台。
做运行 / 打包:先看 [`03-tech-stack.md`](03-tech-stack.md) 和 [`current-state.md`](current-state.md)。
## 验证命令
```powershell
.\init.ps1
```
等价拆分命令:
```powershell
go test ./...
cd frontend
npm run build
cd ..
go build -o bin\cmbone.exe .
```
+65
View File
@@ -0,0 +1,65 @@
# 项目愿景
## 项目目标
`cmbone` 的目标是成为一个可复用桌面应用模板:以 Wails v3 承载跨平台桌面能力,以 Windows 10+ 作为主要体验目标,以 Vue 3 构建稳定的业务界面,以 SQLite 承载本地配置、业务数据、日志和统计。
它不是单一业务产品,也不是大而空的框架。它要提供一套能被复制、裁剪和扩展的桌面端基础能力,让后续具体业务模块可以快速接入。
## 模板定位
核心定位:
- Windows-first:优先保证 Windows 10+ 上的启动、窗口、热键、托盘、打包和用户体验。
- 可扩展跨平台:代码结构保留 macOS / Linux 适配边界,但不在 MVP 承诺完整体验。
- 本地优先:配置、日志、任务和统计默认落 SQLite 或本地文件。
- 业务可替换:登录、AI 服务、主业务模块通过 provider / service 边界替换。
- 初级程序员可维护:模块职责清楚,任务小步推进,不追求抽象炫技。
## 目标用户
- 项目维护者:需要一套可复用的桌面应用起点。
- 初级前端 / 后端程序员:需要沿着清晰模块做设置、表单、表格、日志和统计类功能。
- AI coding agent:需要稳定的上下文入口、任务看板和质量规则。
- 后续业务团队:需要把具体业务模块插入模板,而不是从零搭桌面壳。
## 第一批业务样例
第一批主业务模块使用“电商商品图片 AI 优化”。
这个样例用于验证模板是否具备真实业务所需的基本能力:
- 登录后进入主应用。
- 上传或选择商品图片。
- 创建 AI 优化任务。
- 查看任务状态、结果和失败原因。
- 记录用户操作和系统错误。
- 在数据统计页看到处理量、成功率、耗时和失败趋势。
## 核心模块
- 应用壳:主窗口、侧边导航、顶部用户区、内容区。
- 设置:主题、语言、窗口行为、快捷键、数据目录、日志策略。
- 登录:本地固定账号 provider 和真实后端 provider。
- 主业务:商品图片 AI 优化样例。
- 操作日志:审计日志和运行日志分开。
- 数据统计:围绕业务任务和日志生成基础指标。
- 常用组件:输入框、多选框、下拉框、多行文本、表格、筛选、分页和状态反馈。
## 非目标
- 不自研完整 UI 组件库,优先使用 Ant Design Vue 和项目级组合封装。
- 不在 MVP 做复杂权限、角色、组织、企业 SSO。
- 不在 MVP 做复杂 BI 平台、报表设计器或插件市场。
- 不在 MVP 承诺完整 macOS / Linux 体验。
- 不在没有真实接口前重度绑定某个 AI 服务商。
- 不追逐 latest 依赖版本,优先保持当前 Go 1.23.0 可构建。
## 成功标准
- 新项目可以基于模板快速保留或删除登录、设置、日志、统计和业务样例。
- Windows 10+ 上可以稳定启动、构建和打包。
- `.\init.ps1` 可以完成依赖安装、测试、前端构建和后端编译。
- 文档、目录、命令、接口和当前代码事实一致。
- 新增模块都有清楚 service / model / route 边界。
- 提交历史能看出每次改动的意图和验证结果。
+93
View File
@@ -0,0 +1,93 @@
# 需求说明
## 模板 MVP 范围
### P0 · 基线必须保持
- 应用能启动 Wails 主窗口,并加载嵌入的 `frontend/dist` 资源。
- Windows 10+ 是主要运行和体验目标。
- 多语言资源能从 `frontend/src/locales/*.json` 加载,并能通过 UI / 服务切换。
- 主题配置能保存到本地 SQLite 并在前端生效。
- 快捷键配置能从数据库读取、更新,并注册到系统热键层。
- 托盘菜单能执行显示、隐藏、语言切换和退出等基础动作。
- `go test ./...`、`npm run build`、`go build -o bin/cmbone.exe .` 通过。
### P0 · 模板必须具备
- 应用壳:侧边导航、顶部用户区、主内容区,默认布局适合长期业务操作。
- 主窗口:默认不最大化,初始 `1280x800`,最小 `1024x720`,居中,可调整大小,并恢复用户上次窗口尺寸和最大化状态。
- 页面布局:根容器 `width: 100%`、`height: 100vh`,内部使用 flex/grid 填满窗口。
- 内容区:阅读型内容保留最大宽度;列表、仪表盘、设置页自适应拉伸。
- 第二窗口:保持小窗,不参与主窗口最大化和尺寸恢复逻辑。
- 设置模块:主题、语言、窗口行为、快捷键、数据目录、日志策略。
- 登录模块:支持本地固定账号 provider;预留真实后端 provider。
- 主业务模块:以电商商品图片 AI 优化为样例,包含任务创建、列表、状态、结果和失败反馈。
- 操作日志模块:审计日志记录用户动作,运行日志记录系统错误和排错信息。
- 数据统计模块:展示第一批 P0 指标。
- 常用组件示例:输入框、多选框、下拉框、多行文本、表格、筛选、分页和状态标签。
## 第一批统计指标
P0 数据统计只做 6 个指标:
- 今日处理图片数。
- AI 优化成功率。
- 平均处理耗时。
- 失败任务数。
- 今日操作日志数。
- 最近 7 天处理趋势。
P1 再考虑:
- 图片优化前后平均文件大小变化。
- 不同优化类型使用次数,例如增强、压缩、背景替换。
- 最常见错误类型 Top 5。
- 当前本地数据库大小和输出目录占用空间。
- 登录成功 / 失败次数。
- 任务队列状态:待处理、处理中、成功、失败。
## 登录要求
- 本地固定账号只用于模板演示和离线场景。
- 固定账号密码不得写死在前端。
- 真实后端登录通过 provider 接入,返回 token 或 session。
- 登录成功、失败、退出必须写入审计日志。
- MVP 不做复杂权限、角色或 SSO。
## 日志要求
审计日志用于回答“谁在什么时候做了什么”:
- 登录、退出、登录失败。
- 修改设置。
- 创建图片优化任务。
- 任务成功、失败、取消。
- 导出或清理数据。
运行日志用于排错:
- AI 调用失败。
- 数据库错误。
- 文件读写失败。
- 热键注册失败。
- 未捕获的服务错误。
审计日志和运行日志不要混在同一张业务表里。
## 验收标准
每个任务完成前至少满足:
- 相关代码已格式化。
- 相关单元测试或构建命令通过。
- 前端页面在目标窗口尺寸下没有明显溢出或不可用控件。
- 文档中的命令、版本、接口和当前代码一致。
- `progress.md` 记录了本次验证命令和结果。
## 约束
- 当前 Go 版本为 `1.23.0`。
- Wails 版本固定为 `v3.0.0-alpha.9`。
- 前端 runtime 固定为 `@wailsio/runtime 3.0.0-alpha.66`。
- 不提交 `node_modules`、`dist`、`bin`、本地数据库或用户配置。
- 依赖升级必须单独开任务。
+191
View File
@@ -0,0 +1,191 @@
# 技术栈
## 后端
- Go:`1.23.0`
- 桌面框架:`github.com/wailsapp/wails/v3 v3.0.0-alpha.9`
- 本地数据库:`modernc.org/sqlite v1.38.2`
- 系统热键:`golang.design/x/hotkey v0.4.1`
运行策略:
- Windows 10+ 是主要目标环境。
- macOS / Linux 保留代码边界,但不在 MVP 承诺完整体验。
- 平台差异统一放在 `platform/` 或明确 provider 层。
Wails CLI 安装命令:
```powershell
go install github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-alpha.9
```
不要直接安装 `wails3@latest`,避免 CLI、Go SDK 和前端 runtime 版本不匹配。
## 前端
- Vue:`^3.2.45`
- TypeScript:`^4.9.3`
- Vite:`^8.1.3`
- Wails runtime:`@wailsio/runtime 3.0.0-alpha.66`
- UI:Ant Design Vue `^4.2.6`
- 样式:Tailwind CSS `^3.4.1`
- 国际化:Vue I18n `^11.1.5`
UI 策略:
- 不自研完整组件库。
- 优先复用 Ant Design Vue。
- 只沉淀项目级组合组件和使用示例,例如筛选表格、设置项、状态标签、日志列表。
## 目录职责
```text
.
├── main.go # Wails 应用入口、服务注册、资源嵌入
├── internal/
│ ├── models/ # 数据模型
│ └── services/ # Wails 服务、配置、数据库、热键、OCR
├── platform/ # Windows / macOS 平台差异实现
├── frontend/
│ ├── src/ # Vue 源码
│ ├── bindings/ # Wails 生成的前端绑定
│ └── dist/ # 前端构建产物,git 忽略
├── build/ # Wails 打包配置
└── docs/ # Harness Coding 文档
```
## 常用命令
安装前端依赖:
```powershell
cd frontend
npm install
```
后端测试:
```powershell
go test ./...
```
前端构建:
```powershell
cd frontend
npm run build
```
前端类型检查:
```powershell
cd frontend
npm run typecheck
```
后端编译:
```powershell
go build -o bin\cmbone.exe .
```
统一验证入口:
```powershell
.\init.ps1
```
开发启动:
```powershell
wails3 dev
```
Wails 构建:
```powershell
wails3 build
```
Windows 打包:
```powershell
wails3 package
```
打包前置条件、输出目录和常见失败处理见 [`windows-packaging.md`](windows-packaging.md)。
Windows 快捷脚本:
```powershell
.\dev.bat
.\build.bat
```
`dev.bat` 会先切到项目根目录,把 Go bin 加入当前进程 PATH,再从 `9245` 开始选择空闲端口运行 `wails3 dev`。
开发启动链路使用 `npm run build:dev` 做快速前端 dev 构建,避免 `vue-tsc` 阻塞 GUI 打开;完整类型检查仍由 `npm run build` 或 `npm run typecheck` 执行。
Windows build 资源:
- `build/windows/wails.exe.manifest`
- `build/windows/info.json`
- `build/windows/icon.ico`
`wails3 build` 会重新生成 `frontend/bindings`,当前 CLI 生成的是 `.js` bindings。
## Wails Bindings 生成流程
版本要求:
- Go:`1.23.0`
- Wails CLI:`wails3 v3.0.0-alpha.9`
- 前端 runtime:`@wailsio/runtime 3.0.0-alpha.66`
需要重新生成 bindings 的情况:
- 新增、删除或改名 Wails 服务。
- 修改 Go 服务的公开方法签名。
- 修改前端需要使用的请求模型、返回模型或 JSON 字段。
- 在 `main.go` 的 `Services` 列表注册新服务。
从项目根目录执行:
```powershell
$env:Path += ";$(go env GOPATH)\bin"
wails3 generate bindings -clean=true
```
生成结果位于 `frontend/bindings/`。当前 Wails v3 alpha CLI 生成 `.js` 文件,前端源码使用无扩展名 import,例如:
```ts
import { ListTasks } from '../../../bindings/cmbone/internal/services/imageoptimizationservice'
```
失败处理:
- `wails3` 找不到:确认已安装固定版本,并把 `$(go env GOPATH)\bin` 加入当前 PowerShell PATH。
- 版本不匹配:不要用 `latest`,重新安装 `github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-alpha.9`。
- Go 编译错误:先运行 `go test ./...` 或 `go build -o bin\cmbone.exe .`,修复服务或模型错误后再生成。
- 前端类型或 import 错误:重新执行 `wails3 generate bindings -clean=true`,再运行 `cd frontend && npm run build`。
- 本机暂时无法生成:只允许按已有 binding 模式做最小手工修正,并在 `progress.md` 写清原因、范围和后续补救。
提交要求:
- 服务或模型变更必须和对应 `frontend/bindings` 变更同一个 task 提交。
- 提交前至少运行 `go test ./...`、`cd frontend && npm run build`。
- 涉及完整启动或打包链路时,再运行 `go build -o bin\cmbone.exe .` 或 `cmd /c build.bat`。
## 版本升级规则
- 升级 Go 前,先确认 Wails v3 alpha、SQLite 和热键库的兼容性。
- 升级 Wails 后,同时检查 `@wailsio/runtime` 和 `frontend/bindings`。
- 升级 Vite / TypeScript 后,必须跑 `npm run build`。
- 依赖升级必须单独提交,不和业务功能混在一起。
## 目标模块技术边界
- 设置:通过 `settings_schema.go` 集中定义 key、类型、默认值、说明、选项和只读策略。
- 登录:通过 `AuthProvider` 支持本地固定账号和真实后端,不在前端写死密码。
- 主业务:商品图片 AI 优化通过服务层封装,MVP 可先用本地模拟 provider。
- 操作日志:审计日志进 SQLite,运行日志可进 SQLite 或本地滚动日志文件。
- 数据统计:MVP 直接 SQL 聚合,不提前引入 BI 或图表后端。
+236
View File
@@ -0,0 +1,236 @@
# 架构设计
## 总览
```text
main.go
├─ Wails application
├─ embedded frontend/dist
├─ embedded frontend/src/locales/*.json
└─ services
├─ AppService
├─ AppConfigService
├─ HotkeyService
├─ SystemInfo
├─ OCRService # 当前存量示例
├─ AuthService # 已实现 provider 边界
├─ AuditLogService # 已实现审计日志边界
├─ AppLogService # 已实现运行日志边界
├─ ImageOptimizationService # 已实现本地模拟任务模型
└─ MetricsService # 已实现 P0 统计聚合
frontend/src
├─ router
├─ layouts
├─ pages
├─ components
├─ modules
├─ utils
└─ locales
```
前端通过 `frontend/bindings` 调用 Wails 暴露的 Go 服务。后端服务访问本地 SQLite、平台热键、文件系统、真实后端或 AI provider。
## 后端边界
- `main.go`:只负责应用组装、资源嵌入、服务注册和启动。
- `internal/models/`:定义数据库和服务返回的结构体。
- `internal/services/store.go`:负责 SQLite 连接、迁移和基础读写。
- `internal/services/appservice.go`:负责窗口、托盘、语言资源和应用生命周期。
- `internal/services/appconfig_service.go`:负责配置读写的 Wails 服务边界。
- `internal/services/settings_schema.go`:集中定义设置模块 schema、默认值、选项和初始化 SQL。
- `internal/services/hotkey_service.go`:负责热键读取、更新和重新注册。
- `internal/services/ocr_service.go`:当前 OCR 存量示例,后续可演进为图片 AI 能力的一部分。
- `internal/services/image_optimization_service.go`:负责商品图片 AI 优化任务、结果和本地模拟 provider。
- `internal/services/log_service.go`:负责审计日志和运行日志的记录、查询和共享写入 helper。
- `internal/services/metrics_service.go`:负责 P0 统计指标和趋势聚合。
- `platform/`:隔离 Windows / macOS 热键和系统能力差异。
已实现边界:
- `AuthService`:登录、退出、会话读取;内部通过 `AuthProvider` 接入本地固定账号或真实后端。当前 `LocalAuthProvider` 可用,`RemoteAuthProvider` 保留边界并返回未配置错误。
- `AppConfigService`:通过统一设置 schema 初始化和校验主题、语言、窗口行为、快捷键展示、数据目录、日志策略和登录配置。
- `ImageOptimizationService`:商品图片 AI 优化任务模型和本地模拟任务创建已可用,页面已接入任务创建、列表和结果预览。
- `AuditLogService`:审计日志记录和查询已可用,登录、设置变更和图片任务创建会写审计日志。
- `AppLogService`:运行日志记录和查询已可用,图片任务创建失败和服务错误会写运行日志。
- `MetricsService`:P0 统计指标和最近 N 天趋势已可查询,当前直接聚合 `image_tasks` 和 `audit_logs`。
服务之间不要互相绕过边界直接操作平台实现。新增平台能力时,优先放在 `platform/`,再由 `internal/services/` 暴露稳定方法。
## 前端边界
- `frontend/src/router/index.ts`:维护页面路由。
- `frontend/src/layouts/`:目标目录,放登录布局和主应用布局。
- `frontend/src/modules/auth/`:目标目录,放登录页面、会话状态和 provider 调用。
- `frontend/src/modules/settings/`:目标目录,放设置页和设置项。
- `frontend/src/modules/image-optimizer/`:放已接入的商品图片 AI 优化样例页面。
- `frontend/src/modules/logs/`:目标目录,放审计日志和运行日志页面。
- `frontend/src/modules/statistics/`:放统计卡片和趋势图。
- `frontend/src/modules/components-demo/`:放已接入的常用组件示例页。
- `frontend/src/components/template/`:放当前模块占位页共享壳。
- `frontend/src/components/`:通用展示组件和组合控件。
- `frontend/src/utils/`:国际化、主题、OS、热键等工具函数。
- `frontend/bindings/`:由 Wails 生成或按 Wails 合约维护,不写业务逻辑。
## 登录架构
登录已经通过 provider 边界实现:
```text
AuthService
└─ AuthProvider
├─ LocalAuthProvider
└─ RemoteAuthProvider
```
- `LocalAuthProvider`:用于模板演示和离线模式,固定账号密码由后端配置或安全哈希保存,不写死在前端。
- `RemoteAuthProvider`:用于真实后端,负责请求登录接口并保存 token / session;当前只保留接口边界并返回未配置错误。
- 前端只关心登录、退出和当前会话,不关心 provider 细节。
- 默认本地账号由 `appconfig` 初始化,用户名为 `admin`,默认密码为 `admin123`,密码以 salt + SHA-256 hash 形式保存。
- 登录成功、失败和退出会写入 `audit_logs`。
## 日志架构
审计日志和运行日志分开:
- 审计日志:记录用户可解释动作,支持查询、筛选和导出。
- 运行日志:记录系统错误和排错信息,支持按时间、级别、模块查看。
审计日志用于“责任链”,运行日志用于“故障定位”。不要用一张表混合两种语义。
## 数据模型
SQLite 数据库位于用户配置目录下的 `cmbone/cmbone.db`,不进入 git。
当前核心表:
```sql
hotkeys(
id INTEGER PRIMARY KEY,
keycode INTEGER,
modifiers INTEGER,
description TEXT,
target TEXT
)
```
```sql
appconfig(
id INTEGER PRIMARY KEY,
key TEXT,
type TEXT,
value TEXT,
description TEXT,
state INTEGER
)
```
当前 `appconfig` 默认配置由统一设置 schema 生成:
- `theme.mode`、`language`
- `window.start_state`、`window.width`、`window.height`、`window.remember_state`
- `shortcut.open_second_window`、`shortcut.open_main_window`
- `data.root_mode`、`data.custom_directory`
- `logs.level`、`logs.audit_retention_days`、`logs.app_retention_days`
- `auth.provider`、`auth.local.username`、`auth.local.password_salt`、`auth.local.password_hash`
```sql
auth_sessions(
id INTEGER PRIMARY KEY,
user_id TEXT,
username TEXT,
provider TEXT,
token TEXT,
created_at TEXT,
expires_at TEXT,
active INTEGER
)
```
```sql
audit_logs(
id INTEGER PRIMARY KEY,
actor TEXT,
action TEXT,
target_type TEXT,
target_id TEXT,
detail TEXT,
created_at TEXT
)
```
```sql
image_tasks(
id INTEGER PRIMARY KEY,
source_path TEXT,
status TEXT,
operation TEXT,
duration_ms INTEGER,
error_message TEXT,
created_at DATETIME,
updated_at DATETIME
)
```
```sql
image_task_results(
id INTEGER PRIMARY KEY,
task_id INTEGER,
output_path TEXT,
before_size INTEGER,
after_size INTEGER,
created_at DATETIME
)
```
当前 `image_tasks` 和 `image_task_results` 已由 `T-020` 落地,用于本地模拟图片优化任务和后续统计聚合。
```sql
app_logs(
id INTEGER PRIMARY KEY,
level TEXT,
module TEXT,
message TEXT,
detail TEXT,
created_at DATETIME
)
```
当前 `audit_logs` 和 `app_logs` 分别用于用户动作审计和运行排错。`MetricsService` 已直接用 SQL 聚合 P0 指标,不急着落 `daily_metrics` 缓存表。数据量变大后再增加缓存表。
迁移要求:
- 初始化必须幂等。
- 新字段必须有默认值或迁移逻辑。
- schema 变化必须同步 `store_test.go`、`api.md` 和 `current-state.md`。
## 窗口与页面
- 主窗口路由:`/#/` 重定向到 `/#/dashboard`
- 第二窗口路由:`/#/second`
- 已实现主应用骨架路由:`/#/login`、`/#/dashboard`、`/#/image-optimizer`、`/#/logs`、`/#/statistics`、`/#/settings`、`/#/components`。
- 兼容目标文档路由:`/#/business/image-optimizer`、`/#/logs/audit`、`/#/logs/app` 会重定向到当前主入口。
- 托盘菜单由 `AppService` 配置。
- 热键触发目标可以是打开主窗口、打开快捷功能或创建快速任务。
窗口策略:
- 主窗口承载长期业务操作,默认不最大化。
- 主窗口初始尺寸为 `1280x800`,最小尺寸为 `1024x720`。
- 主窗口启动时居中,并允许用户调整大小。
- 主窗口按 `window.width`、`window.height`、`window.start_state` 和 `window.remember_state` 恢复用户上次选择。
- 前端根容器使用 `width: 100%` 和 `height: 100vh`。
- 主应用布局内部使用 flex 或 grid 填满窗口。
- 阅读型内容保留最大宽度,列表、仪表盘、设置页等工作型页面自适应拉伸。
- 第二窗口保持当前小窗定位,不参与最大化、尺寸恢复和主布局填充逻辑。
实现状态:主窗口初始尺寸、居中、可调整大小、根容器和主布局填充已落地;主应用路由骨架已落地;登录 provider 边界已落地;设置模块统一 schema 已落地;主窗口尺寸和最大化状态持久化已落地。
## 关键风险
- 范围膨胀:模板容易变成“大而全平台”,必须按任务小步交付。
- Wails v3 仍是 alpha,CLI、Go 模块和 runtime 必须保持匹配。
- bindings 若与 Go 服务签名不一致,前端会在运行时失败。
- 登录 provider、AI provider 和日志模块必须有清晰边界,否则后续替换困难。
- OCR、热键、托盘依赖平台能力,跨平台修改必须分别验证。
- Vite 构建可能出现 chunk size warning,警告不等于失败,但需要避免无节制增加前端体积。
+67
View File
@@ -0,0 +1,67 @@
# 编码规则
> 每次写代码前先读完本文件。与技术细节冲突时,以 [`03-tech-stack.md`](03-tech-stack.md) 和 [`04-architecture.md`](04-architecture.md) 为准。
## 黄金法则
1. 不臆造:字段、接口、依赖和文件必须来自当前代码或文档。
2. 守范围:只做当前任务,不顺手加后续功能。
3. 照架构:使用既有 Wails、Vue、SQLite、平台模块边界。
4. 小步改:一次解决一个问题,不夹带无关重构。
5. 可验证:完成前必须测试或构建通过。
## 动手前
- 先读 `AGENTS.md`、`00-ai-start-here.md`、`current-state.md` 和当前任务。
- 查找现有函数、组件、工具和测试,复用优先。
- 需求不清或会影响架构时,先说明问题再决定。
- 不确定 Wails API 时,优先看本项目现有代码和已锁版本,不直接按最新文档改。
## 代码规范
- Go 代码用 `gofmt`。
- TypeScript / Vue 保持当前项目风格。
- 错误必须返回或展示,不静默吞掉。
- 后端服务方法签名变化必须同步前端 bindings。
- 前端 UI 文案走现有 i18n 体系,避免新增硬编码中文/英文。
- 新依赖必须说明必要性;能用标准库或现有依赖解决时不新增。
- 登录、AI、日志和统计能力必须经过 service / provider 边界,不写死在页面组件里。
- 常用输入框、多选框、下拉框、多行文本、表格等优先使用 Ant Design Vue,不自研基础控件。
## 模块规则
- `main.go` 不写业务逻辑。
- SQLite 读写集中在 store 或明确服务层,不散落到多个模块。
- 平台差异放在 `platform/`。
- 前端 bindings 不承载业务逻辑。
- 通用 UI 组件保持小而直接,不为未来需求提前抽象。
- 审计日志记录用户动作,运行日志记录系统错误,两类日志不要混用。
- 数据统计只做任务要求的指标,不提前做复杂报表平台。
## 验证要求
代码改动完成前至少运行相关命令:
```powershell
go test ./...
cd frontend
npm run build
cd ..
go build -o bin\cmbone.exe .
```
推荐直接运行:
```powershell
.\init.ps1
```
验证失败时,不要标记任务完成。无法运行时,必须在回复和 `progress.md` 里写清原因。
## 绝不
- 不提交密钥、token、密码、本地数据库或用户配置。
- 不提交 `node_modules`、`frontend/dist`、`bin`。
- 不用删除测试、降低断言的方式制造通过。
- 不擅自 reset、checkout 或删除用户未要求删除的文件。
- 不把依赖升级和功能改动混在同一个任务里。
+102
View File
@@ -0,0 +1,102 @@
# 任务看板
> 单 agent / 小项目使用本文件。多 agent 并发时,改用 [`tasks/README.md`](tasks/README.md) 的一任务一文件规则。
## 使用规则
1. 一次只做一个任务:领取第一个状态为 `TODO` 且依赖均为 `DONE` 的任务。
2. 开始后把状态改为 `DOING`。
3. 完成并验证后把状态改为 `DONE`。
4. 任务完成必须在 [`../progress.md`](../progress.md) 写入验证命令和结果。
5. 完成后更新 [`current-state.md`](current-state.md),并过一遍 [`clean-state-checklist.md`](clean-state-checklist.md)。
## 状态图例
`TODO` 待开始 · `DOING` 进行中 · `DONE` 已完成并验收 · `BLOCKED` 受阻
## Phase 0 · 基线与定位
| ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- |
| T-000 | 接入 Harness Coding 文档 | - | `AGENTS.md`、`docs/`、`progress.md`、`init.ps1`、`init.sh` 已创建;README 已登记入口;统一验证通过 | DONE |
| T-001 | 验证迁移后构建基线 | T-000 | `go test ./...`、`npm run build`、`go build -o bin/cmbone.exe .` 通过 | DONE |
| T-002 | 校准为可复用桌面应用模板 | T-001 | 愿景、需求、架构、API、路由、任务和当前状态文档都改为 Windows-first 可复用模板方向 | DONE |
## Phase 1 · 应用壳与基础模块
| ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- |
| T-010 | 确定 Windows-first 窗口与布局策略 | T-002 | 主窗口默认不最大化,初始 `1280x800`,最小 `1024x720`,居中,可调整大小;页面根容器 `width: 100%`、`height: 100vh`;内部用 flex/grid 填满窗口;第二窗口保持小窗 | DONE |
| T-011 | 实现主窗口启动参数与根布局策略 | T-010 | Go 侧设置主窗口初始宽高、居中、可调整大小;前端根容器和主布局填满窗口;阅读型内容有最大宽度,列表/仪表盘/设置页可自适应拉伸 | DONE |
| T-012 | 搭建主应用路由骨架 | T-011 | `login`、`dashboard`、`image-optimizer`、`logs`、`statistics`、`settings`、`components` 路由可访问 | DONE |
| T-013 | 实现登录 provider 边界 | T-012 | 支持本地固定账号 provider;预留 remote provider;账号密码不写死在前端;登录事件写审计日志 | DONE |
| T-014 | 收敛设置模块 schema | T-012 | 主题、语言、窗口行为、快捷键、数据目录、日志策略都有统一配置定义和默认值 | DONE |
| T-015 | 持久化窗口尺寸和最大化状态 | T-014 | 保存主窗口尺寸和最大化状态;下次启动恢复用户上次选择;第二窗口不参与该逻辑 | DONE |
## Phase 2 · 业务样例与日志统计
| ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- |
| T-020 | 建立商品图片 AI 优化任务模型 | T-013 | `image_tasks`、`image_task_results` 迁移和基础测试通过;可创建模拟任务 | DONE |
| T-021 | 实现审计日志与运行日志边界 | T-013 | 审计日志和运行日志分开;登录、设置变更、任务创建、任务失败可记录 | DONE |
| T-022 | 实现 P0 数据统计 | T-020 | 今日处理图片数、成功率、平均耗时、失败数、今日操作日志数、7 天趋势可查询 | DONE |
| T-023 | 实现商品图片 AI 优化页面 | T-020 | 上传/选择图片、任务列表、状态、结果预览、失败提示路径可用 | DONE |
## Phase 3 · 组件、质量与发布
| ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- |
| T-030 | 建立常用组件示例页 | T-012 | 输入框、多选框、下拉框、多行文本、表格、筛选、分页、状态标签有可运行示例 | DONE |
| T-031 | 文档化 Wails bindings 生成流程 | T-012 | README 或技术栈文档说明 `wails3 generate bindings` 的使用、失败处理和版本要求 | DONE |
| T-032 | 补充 Windows 打包验证文档 | T-031 | `wails3 package` 前置条件、输出目录和常见失败处理写入 README 或 docs | DONE |
| T-033 | 完成一次模板 smoke 清单 | T-023 | 登录、设置、业务样例、日志、统计、组件示例、托盘、热键都按清单验证并记录结果 | DONE |
| T-034 | 添加 Windows Wails 快捷脚本 | T-001 | `dev.bat` 一行切到项目根目录、补 Go bin PATH、自动选择空闲端口并运行 `wails3 dev`;`build.bat` 一行切到项目根目录、补 Go bin PATH 并运行 `wails3 build`;README、技术栈、当前状态和执行流水已同步 | DONE |
| T-035 | 修复主窗口最大化黑边与布局填充 | T-011 | 点击右上角最大化后窗口占满 Windows 可用区域;主窗口背景、根容器和主布局铺满;侧边导航贴齐左边;内容区填满剩余区域;不出现左右黑边;恢复窗口后布局正常 | DONE |
| T-036 | 支持 Windows 主窗口标题栏双击最大化 | T-035 | Windows 无边框主窗口顶部拖拽区域双击可切换最大化/还原;右上角按钮区、登录入口和打开第二窗口入口双击不误触发;第二窗口不受影响;窗口状态持久化继续生效 | DONE |
## EC 分支 · 电商场景
| ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- |
| EC-001 | 建立商品中心页面 | T-023 | `/#/products` 可访问;支持商品草稿、筛选、列表、状态标签和图片优化入口 | DONE |
| EC-002 | 建立商品后端模型 | EC-001 | 新增 `products` 表、ProductService、基础测试和 Wails bindings | TODO |
| EC-003 | 商品中心接入后端 | EC-002 | 商品中心不再使用前端本地示例数据,支持创建和查询商品 | TODO |
| EC-004 | 图片优化关联商品 | EC-002 | 图片优化任务可关联 SKU 或 product_id,商品中心能看到图片状态 | TODO |
| EC-005 | 电商统计指标 | EC-003 | 展示商品总数、待优化图片、在售商品、失败图片任务和处理趋势 | TODO |
## Phase SP · 虾皮店铺模块(CDP 多店管理)
> 设计依据:Obsidian `项目文档/Wails多店铺CDP管理方案.md`(含 UI 与数据模型第 10 节)。架构:Wails 当编排,N 个独立 Chrome(独立 user-data-dir / 端口 / 代理)并行 CDP。分三期:P1 列表与进程编排、P2 预览宫格、P3 RPA 引擎。
### P1 · 列表、凭据、进程编排
| ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- |
| SP-001 | 凭据静态加密原语 | - | `SuiStore` 增加加密/解密工具;Windows 用 DPAPI(`CryptProtectData`,绑当前用户)或主密码派生密钥;密码等敏感字段不落明文;含单元测试 | TODO |
| SP-002 | 店铺数据模型与迁移 | SP-001 | 新增 `shopee_shops` 表:名称、账号、加密密码(可选)、user-data-dir、调试端口、代理(类型/host/port/user/pass 结构化)、状态枚举(进程未启动/未登录/在线/掉线/需人工OTP/代理失效/RPA运行中)、出口IP校验状态;迁移和基础测试通过 | TODO |
| SP-003 | ShopService CRUD 与 bindings | SP-002 | 店铺增删改查 service;user-data-dir 与端口可自动分配;密码经加密原语存取;生成 Wails bindings;含测试 | TODO |
| SP-004 | 导航入口、路由与模块页骨架 | T-012 | `navItems` 增加“虾皮店铺”;`/shopee` 路由可访问;模块页为 master-detail 布局(左店铺列表区 + 右预览区占位) | TODO |
| SP-005 | 店铺列表 CRUD 界面 | SP-003, SP-004 | 列表展示名称/账号/代理/状态;可增删改;代理为结构化表单;密码为可选项且不明文回显;空态与校验提示可用 | TODO |
| SP-006 | Chrome 进程编排器 | SP-002 | Go 侧起停/守护外部 Chrome;每进程绑定 user-data-dir + 唯一调试端口 + 代理;代理带认证走 CDP `Fetch.authRequired` 或本地转发;启动后校验出口 IP 并回写状态;打包固定版本 portable Chromium | TODO |
| SP-007 | 单店打开/关闭与状态回显(路 A) | SP-005, SP-006 | 每店“打开/关闭”按钮拉起/关闭真实 headed Chrome 窗口;状态枚举实时回显到列表;掉线/代理失效可识别并提示人工 | TODO |
### P2 · 预览宫格
| ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- |
| SP-010 | screencast CDP 客户端 | SP-006 | 用 chromedp 连接各店调试端口;`Page.startScreencast` 拉帧;连接池管理与断连重连;帧率可配置 | TODO |
| SP-011 | 预览宫格界面 | SP-010, SP-007 | 右侧宫格按在线店铺渲染 N 格 `<img>` 实时画面;进入/离开 tab 懒启停 screencast;帧率节流;点击某格置前对应真实 Chrome 窗口(路 A) | TODO |
### P3 · RPA 引擎
| ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- |
| SP-020 | RPA 步骤引擎与首个登录后流程 | SP-007 | 预设步骤模型(打开/导航/填充/点击等)+ 执行器;单店运行态与步骤日志;错误处理与中断;首个内置流程为“登录后流程”(在已登录 profile 上执行,如抓订单/改库存) | TODO |
| SP-021 | 登录辅助 RPA | SP-020 | 可选“自动填账密”步骤;走到 OTP/验证码即停下并通知人工接管;补登完成后可继续;密码经加密原语读取,不出现在日志 | TODO |
## Backlog
- 评估是否需要 Playwright / Vitest 覆盖前端关键页面。
- 评估是否需要平台能力降级策略。
- 评估真实 AI 后端 provider 的接口形态。
- 评估本地数据库备份、恢复和清理策略。
+27
View File
@@ -0,0 +1,27 @@
# Harness Coding 文档导航
本目录用于让后续 AI coding agent 和初级维护者快速恢复上下文、领取任务、验证改动。
## 必读入口
- [`00-ai-start-here.md`](00-ai-start-here.md):AI 开发入口和固定开工流程。
- [`01-vision.md`](01-vision.md):产品目标、用户和非目标。
- [`02-requirements.md`](02-requirements.md):MVP 范围和验收标准。
- [`03-tech-stack.md`](03-tech-stack.md):真实技术栈、版本和命令。
- [`04-architecture.md`](04-architecture.md):模块边界、数据流和风险点。
- [`05-coding-rules.md`](05-coding-rules.md):编码规则和验证纪律。
- [`06-tasks.md`](06-tasks.md):当前任务看板。
## 辅助文档
- [`api.md`](api.md):Wails 服务方法和本地模块合约。
- [`routes.md`](routes.md):前端路由和页面职责。
- [`current-state.md`](current-state.md):当前可运行状态、命令和下一步。
- [`clean-state-checklist.md`](clean-state-checklist.md):交接前清理检查。
- [`method-map.md`](method-map.md):关键方法和文件索引。
- [`quality-document.md`](quality-document.md):质量分层和风险登记。
- [`evaluator-rubric.md`](evaluator-rubric.md):评估标准。
- [`adoption-checklist.md`](adoption-checklist.md):Harness Coding 接入检查。
- [`tasks/README.md`](tasks/README.md):多 agent 时的一任务一文件规则。
根目录 [`../progress.md`](../progress.md) 记录历史执行流水和验证证据。
+19
View File
@@ -0,0 +1,19 @@
# Harness Coding 接入检查
## 已完成
- [x] 添加 `AGENTS.md` 作为仓库级 AI 入口。
- [x] 添加 `CLAUDE.md` 指向仓库级规则。
- [x] 添加 `docs/00-ai-start-here.md` 作为固定开工流程。
- [x] 添加愿景、需求、技术栈、架构、编码规则和任务看板。
- [x] 添加 API、路由、当前状态、质量、评估和清理检查文档。
- [x] 添加 `progress.md` 执行流水。
- [x] 添加 `init.ps1` 和 `init.sh` 统一验证入口。
- [x] README 已登记 Harness Coding 文档入口。
## 后续维护
- [ ] 每次任务完成后更新 `progress.md`。
- [ ] 每次状态变化后更新 `docs/current-state.md`。
- [ ] 每次接口、路由、schema 改动后同步相关文档。
- [ ] 每次提交前确认 `git status --short` 只包含本任务文件。
+151
View File
@@ -0,0 +1,151 @@
# API 与模块合约
> 本项目没有固定远程 HTTP API。这里记录 Wails 暴露给前端的 Go 服务方法、本地模块合约,以及目标 provider 边界。
## 当前已存在服务
### AppService
文件:`internal/services/appservice.go`
- `OpenSecondWindow()`:打开或显示第二窗口。
- `SetLanguage(lang string) error`:切换应用语言,并刷新相关菜单/状态。
约束:
- 窗口创建、显示、隐藏和托盘菜单逻辑集中在 `AppService`。
- 语言资源来自 `frontend/src/locales/*.json` 的嵌入文件。
### AppConfigService
文件:`internal/services/appconfig_service.go`
- `GetAppConfig(key string) (string, error)`:读取配置值。
- `SetAppConfig(key string, value string) error`:保存配置值。
- `GetLanguage() string`:读取当前语言。
- `SetLanguage(lang string) error`:保存当前语言。
- `GetSettingsSchema() ([]models.AppSettingGroup, error)`:返回统一设置 schema、默认值和当前值。
约束:
- 配置持久化走 SQLite `appconfig` 表。
- 统一设置 schema 定义在 `internal/services/settings_schema.go`,覆盖主题、语言、窗口行为、快捷键、数据目录、日志策略和登录 provider 配置。
- 新增配置项时,必须补充 key、类型、默认值、说明和必要选项,并同步初始化 SQL。
- `SetAppConfig` 会校验 schema 已知配置项;只读项不能通过通用配置接口修改。
- 快捷键 schema 项只用于统一展示,实际热键更新仍走 `HotkeyService`。
### HotkeyService
文件:`internal/services/hotkey_service.go`
- `GetHotkeys() ([]models.Hotkey, error)`:读取热键配置。
- `UpHotkey(id int, key int, modifier int) error`:更新热键配置并重新注册。
约束:
- 热键数据来自 SQLite `hotkeys` 表。
- 平台注册通过 `platform/` 实现,服务层不直接写平台细节。
- 更新失败时必须返回错误,不只打印日志。
### SystemInfo
文件:`internal/services/systeminfo.go`
- `GetOS() string`:返回当前系统标识。
### OCRService
文件:`internal/services/ocr_service.go`
- `RecognizeImageBase64(base64str string) (string, error)`:识别 base64 图片内容。
约束:
- 当前是存量示例服务,后续可并入商品图片 AI 优化样例。
- 失败时返回错误给前端。
- 平台依赖或能力缺失时,不应导致主窗口不可启动。
### AuthService
文件:`internal/services/auth_service.go`
- `GetAuthProviders() []models.AuthProviderOption`:返回可用 provider 列表。
- `GetAuthMode() string`:读取当前 auth provider。
- `SetAuthMode(mode string) error`:切换 `local` 或 `remote` provider。
- `Login(provider string, username string, password string) (models.AuthSession, error)`:登录并返回会话。
- `Logout() error`:退出当前会话。
- `GetCurrentSession() (models.AuthSession, error)`:读取当前有效会话;无会话时返回空 session。
约束:
- 前端不得写死固定账号密码。
- `LocalAuthProvider` 只用于演示和离线模式,默认账号来自后端初始化配置。
- 默认本地演示账号:`admin` / `admin123`;密码在数据库中以 salt + SHA-256 hash 保存,不在前端页面或绑定中保存。
- `RemoteAuthProvider` 用于真实后端登录,目前保留边界并返回未配置错误。
- 登录成功、失败和退出必须写审计日志。
### ImageOptimizationService
文件:`internal/services/image_optimization_service.go`
- `CreateTask(input models.ImageTaskInput) (models.ImageTaskDetail, error)`:创建本地模拟图片优化任务,并写入任务和结果。
- `ListTasks(filter models.ImageTaskFilter) ([]models.ImageTask, error)`:按状态、操作类型分页查询任务。
- `GetTask(id int64) (models.ImageTaskDetail, error)`:查询任务和结果详情。
- `CancelTask(id int64) error`:取消待处理或处理中任务;当前模拟任务会立即成功,通常不可取消。
约束:
- 当前 provider 是本地模拟实现,不调用真实 AI 后端。
- 任务主表为 `image_tasks`,结果表为 `image_task_results`。
- 模拟任务会生成成功状态、耗时、输出路径和前后文件大小字段,供后续页面和统计模块使用。
### AuditLogService
文件:`internal/services/log_service.go`
- `Record(action string, targetType string, targetID string, detail string) error`:记录审计事件。
- `List(filter models.AuditLogFilter) ([]models.AuditLog, error)`:分页查询审计日志。
约束:
审计日志记录用户动作,不记录系统堆栈。
当前已接入登录、设置变更和图片任务创建。
### AppLogService
文件:`internal/services/log_service.go`
- `Record(level string, module string, message string, detail string) error`:记录运行日志。
- `List(filter models.AppLogFilter) ([]models.AppLog, error)`:分页查询运行日志。
约束:
运行日志用于排错,可以记录错误详情和模块名。
当前已接入图片任务创建失败和服务内部错误路径。
### MetricsService
文件:`internal/services/metrics_service.go`
- `GetDashboardMetrics() (DashboardMetrics, error)`:返回今日处理图片数、成功率、平均耗时、失败数、今日操作日志数。
- `GetProcessingTrend(days int) ([]DailyMetric, error)`:返回最近 N 天处理趋势。
约束:
- 统计按本机本地自然日计算,查询时转换为 UTC 字符串匹配现有 `created_at`。
- `days <= 0` 时默认 7 天,最大 30 天。
- 当前直接基于 `image_tasks` 和 `audit_logs` 聚合,不新增统计缓存表。
## 前端绑定
绑定目录:`frontend/bindings/cmbone/internal/services`
当前 Wails v3 CLI 会生成 `.js` bindings。前端源码使用无扩展名 import,允许 Vite 解析到这些 `.js` 文件。
服务签名变化后优先执行:
```powershell
wails3 generate bindings
```
如果本地无法生成 bindings,只允许根据现有绑定模式做最小修正,并在 `progress.md` 写清原因和验证结果。
+30
View File
@@ -0,0 +1,30 @@
# Clean State Checklist
每轮结束前检查,确保下一位维护者可以直接接手。
## 代码与文档
- [ ] 当前任务只改了必要文件。
- [ ] 没有夹带依赖升级或无关重构。
- [ ] README、docs、API、路由、架构和实际代码一致。
- [ ] 任务状态已更新。
- [ ] `progress.md` 已记录验证命令和结果。
- [ ] `current-state.md` 已更新当前状态和下一步。
## 构建与测试
- [ ] `go test ./...` 通过,或已记录失败原因。
- [ ] `cd frontend && npm run build` 通过,或已记录失败原因。
- [ ] `go build -o bin/cmbone.exe .` 通过,或已记录失败原因。
- [ ] 涉及 UI 时,已人工检查目标窗口尺寸。
## Git
- [ ] `git status --short` 中只包含本任务相关文件。
- [ ] 未提交 `bin/`、`frontend/dist/`、`frontend/node_modules/`。
- [ ] 提交信息能说明本次任务意图。
## 运行
- [ ] 如改动影响启动,已确认应用能打开主窗口。
- [ ] 如改动影响热键、托盘或 OCR,已记录人工 smoke 结果。
+273
View File
@@ -0,0 +1,273 @@
# 当前状态
更新时间:2026-07-09
## 当前阶段
项目处于 `ec` 分支的电商场景开发起步阶段。主线模板能力已经具备,当前分支开始把模板能力收敛成面向电商运营的桌面端应用。
当前代码已从迁移后的 Wails/Vue/SQLite 基线推进到主应用路由骨架、登录 provider 边界、统一设置 schema、主窗口状态恢复、商品中心、商品图片 AI 优化页面、日志服务边界、P0 数据统计和常用组件示例阶段,已具备设置、多语言、主题、本地配置、热键、托盘、OCR 示例、本地登录能力、商品运营入口、本地模拟图片优化任务能力、审计日志、运行日志和 P0 统计查询能力。统计页面仍是骨架,尚未展示真实统计数据。
## 已确认事实
- 项目类型:可复用桌面应用模板。
- 当前业务分支:`ec`,面向电商运营应用开发。
- 主要运行场景:Windows 10 以及以后的 Windows 操作系统。
- 跨平台策略:Windows-first,保留 macOS / Linux 适配边界,不在 MVP 承诺完整体验。
- 第一批业务样例:电商商品图片 AI 优化。
- 登录策略:本地固定账号 provider 已实现,真实后端 provider 已预留边界。
- 日志策略:审计日志用于用户动作追踪,运行日志用于系统排错。
- 第一批统计指标:今日处理图片数、AI 优化成功率、平均处理耗时、失败任务数、今日操作日志数、最近 7 天处理趋势。
- Go 模块名:`cmbone`
- Go 版本:`1.23.0`
- Wails 版本:`v3.0.0-alpha.9`
- 前端 runtime:`@wailsio/runtime 3.0.0-alpha.66`
- 前端框架:Vue 3、Vite、TypeScript、Tailwind CSS、Ant Design Vue
- 当前 Wails 前端 bindings:由 `wails3 build` / `wails3 generate bindings` 生成的 `.js` 文件。
- 已文档化 Wails bindings 生成流程:README 和技术栈文档包含固定版本、生成命令、失败处理和提交要求。
- 已文档化 Windows 打包验证流程:`docs/windows-packaging.md` 包含 `wails3 package` 前置条件、NSIS 输出目录、验证清单和常见失败处理。
- 已完成模板 smoke 清单:`docs/smoke-checklist.md` 记录登录、设置、业务样例、日志、统计、组件示例、托盘和热键验证结果。
- 本地数据库:SQLite,用户配置目录下 `cmbone/cmbone.db`
- 当前本地演示账号:`admin` / `admin123`,密码由后端初始化为 salt + SHA-256 hash,不写死在前端。
- 当前主窗口路由:`/#/` 重定向到 `/#/dashboard`。
- 当前主应用模块路由:`/#/login`、`/#/dashboard`、`/#/products`、`/#/image-optimizer`、`/#/logs`、`/#/statistics`、`/#/settings`、`/#/components`。
- 当前兼容重定向路由:`/#/business/image-optimizer`、`/#/logs/audit`、`/#/logs/app`。
- 当前第二窗口路由:`/#/second`
- 已实现主窗口策略:默认不最大化,初始 `1280x800`,最小 `1024x720`,居中,可调整大小;启动后可按 `window.*` 配置恢复尺寸和最大化状态;Windows 无边框主窗口顶部拖拽区域双击可切换最大化/还原。
- 已实现页面布局策略:根容器 `width: 100%`、`height: 100vh`,内部 flex/grid 填满窗口;主窗口最大化后背景、根容器和主布局铺满可用区域,不出现左右黑边。
- 已实现内容区策略:阅读型内容保留最大宽度;列表、仪表盘、设置页自适应拉伸。
- 已保留第二窗口策略:保持小窗,不参与主窗口最大化和尺寸恢复逻辑。
- 已实现 AuthService:`GetAuthProviders`、`GetAuthMode`、`SetAuthMode`、`Login`、`Logout`、`GetCurrentSession`。
- 已实现登录审计留痕:登录成功、登录失败和退出写入 `audit_logs`。
- 已实现统一设置 schema:主题、语言、窗口行为、快捷键展示、数据目录、日志策略和登录 provider 配置都有默认值、说明和基础校验。
- 已实现设置页 schema 展示:`/#/settings` 可读取后端 `GetSettingsSchema` 并展示分组、当前值、默认值、说明和只读标记。
- 已实现主窗口状态持久化:主窗口普通尺寸和最大化状态会写回 `appconfig`,下次启动恢复;第二窗口不参与该逻辑。
- 已实现商品中心页面:支持商品草稿、SKU、类目、渠道、售价、库存、上架状态、图片状态、筛选和分页展示。
- 已实现 ImageOptimizationService:`CreateTask`、`ListTasks`、`GetTask`、`CancelTask`,当前 provider 为本地模拟实现。
- 已实现 `image_tasks` 和 `image_task_results` 表,模拟任务可写入成功状态、耗时、输出路径和前后文件大小。
- 已实现商品图片 AI 优化页面:支持文件选择、手动图片路径、创建任务、筛选任务、任务列表、状态标签、结果预览和失败提示。
- 已实现 AuditLogService:`Record`、`List`,登录、设置变更和图片任务创建会写入 `audit_logs`。
- 已实现 AppLogService:`Record`、`List`,图片任务创建失败和服务内部错误会写入 `app_logs`。
- 已实现 MetricsService:`GetDashboardMetrics`、`GetProcessingTrend`,可查询今日处理量、成功率、平均耗时、失败数、今日审计日志数和最近 N 天趋势。
- 已实现常用组件示例页:输入框、多选框、下拉框、多行文本、表格、筛选、分页和状态标签都有可运行示例。
## 当前命令
统一验证:
```powershell
.\init.ps1
```
拆分验证:
```powershell
go test ./...
cd frontend
npm run build
npm run typecheck
cd ..
go build -o bin\cmbone.exe .
```
Wails CLI:
```powershell
go install github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-alpha.9
wails3 dev
wails3 build
.\dev.bat
.\build.bat
```
## 已知风险
- 模板范围容易膨胀,必须按任务小步实现。
- Wails v3 alpha 版本需要保持 CLI、Go 模块和前端 runtime 匹配。
- 真实后端登录 provider、真实 AI provider 尚未实现;统计页面展示仍待后续扩展。
- WebView 文件选择器不一定返回绝对路径,图片优化页面已保留手动路径输入。
- OCR 和热键依赖平台实现,跨平台修改需要分别验证。
- 当前前端以构建检查为主,暂未引入专门的前端单元测试。
- bindings 生成依赖本机安装的 `wails3`。
- Windows 默认打包路径依赖 NSIS `makensis`;当前本机 PATH 未检测到 `makensis`。
- 当前项目未配置 `wails.json`,MSIX 暂不作为可用打包路径。
- 托盘菜单点击行为仍建议在真实桌面环境人工复核,本轮已确认初始化代码路径和应用启动 smoke。
- `npm install` 当前报告 3 个 moderate audit findings,后续如做依赖治理应单独开任务。
- 前端构建当前有 Browserslist 数据过期和 chunk 大小警告,不阻塞生产构建。
- Windows 快捷脚本依赖 `go env GOPATH` 下的 `bin\wails3.exe`,如果未安装 Wails CLI,先按 README 安装固定版本。
- `dev.bat` 为了尽快打开 GUI,使用不含 `vue-tsc` 的 `build:dev`;提交前必须继续运行 `npm run build`。
## 最近验证
2026-07-08 已运行:
```powershell
.\init.ps1
```
结果:
- `npm install` 通过。
- `go test ./...` 通过。
- `npm run build` 通过。
- `go build -o bin\cmbone.exe .` 通过。
同日完成可复用桌面应用模板定位校准后,已再次运行旧定位残留检查、`git diff --check` 和 `.\init.ps1`,结果均通过。
同日完成主窗口启动参数与根布局策略实现后,已运行:
- `.\init.ps1` 通过。
- `go test ./...` 通过。
- `cd frontend && npm run build` 通过。
- `go build -o bin\cmbone.exe .` 通过。
- `bin\cmbone.exe` 启动 6 秒 smoke 通过。
同日新增 Windows Wails 快捷脚本:
- `dev.bat` 一行切到项目根目录,把 Go bin 加入当前进程 PATH,并从 `9245` 开始选择空闲端口运行 `wails3 dev`。
- `build.bat` 一行切到项目根目录,把 Go bin 加入当前进程 PATH 并运行 `wails3 build`。
- 已补齐 `build/windows/wails.exe.manifest` 和 `build/windows/info.json`,使 `wails3 build` 的 Windows syso 生成步骤可运行。
- `cmd /c build.bat` 通过。
- `dev.bat` 已验证能避开端口占用问题,进入 `windows:run` 并加载 WebView 资源。
- 使用同样 PATH 逻辑执行 `wails3 dev -help` 通过。
- `.\init.ps1` 通过。
同日修复 `dev.bat` 启动 GUI 等待过长问题:
- `frontend` 的 `build:dev` 改为快速 Vite dev 构建,避免 `vue-tsc` 在开发启动链路中长时间阻塞窗口打开。
- 新增 `npm run typecheck`,正式质量门禁仍由 `npm run build` 覆盖类型检查和生产构建。
- 已验证 `dev.bat` 能进入 `windows:run`,拉起 `bin/cmbone.exe`,并创建可见主窗口。
同日完成主应用路由骨架:
- 新增 `frontend/src/layouts/MainLayout.vue`,主窗口改为侧边导航、顶部窗口区和路由内容区。
- 新增登录、工作台、商品图片 AI 优化、操作日志、数据统计、设置、组件示例模块页。
- 新增 `frontend/src/components/template/ModulePageShell.vue`,用于模块标题、状态和摘要卡片。
- `frontend/src/router/index.ts` 已切换为主应用模块路由,第二窗口继续保留 `/#/second`。
- `npm run typecheck`、`npm run build`、`npm run build:dev`、`go test ./...`、`go build -o bin\cmbone.exe .`、`cmd /c build.bat` 和 `dev.bat` 烟测通过。
同日完成登录 provider 边界:
- 新增 `AuthService`、`LocalAuthProvider` 和 `RemoteAuthProvider`。
- 新增 `auth_sessions` 和 `audit_logs` 迁移。
- 登录页通过 Wails bindings 调用后端 AuthService,不在前端写固定账号密码。
- 默认本地演示账号为 `admin` / `admin123`,密码以后端配置 hash 形式保存。
- 登录成功、失败和退出会写审计日志。
- `go test ./...`、`npm run typecheck`、`npm run build`、`npm run build:dev`、`go build -o bin\cmbone.exe .`、`cmd /c build.bat` 和 `dev.bat` 烟测通过。
同日完成统一设置 schema:
- 新增 `internal/services/settings_schema.go` 和 `internal/models/settings.go`。
- `AppConfigService.GetSettingsSchema` 返回设置分组、配置项、默认值和当前值。
- `SetAppConfig` 对 schema 已知项执行 select、boolean、number 和只读校验。
- `theme.mode` 改为从后端配置读取,并支持 `light`、`dark`、`systemdefault`。
- `/#/settings` 展示统一设置 schema。
- `go test ./...`、`npm run typecheck`、`npm run build`、`go build -o bin\cmbone.exe .`、`cmd /c build.bat` 通过。
2026-07-09 完成主窗口状态持久化:
- 主窗口启动时读取 `window.width`、`window.height`、`window.start_state` 和 `window.remember_state`。
- 主窗口 resize、maximize、restore 和 closing 时写回普通尺寸和最大化状态。
- `window.remember_state` 默认值调整为 `true`。
- 已补充旧默认值迁移,把 T-014 旧描述下的 `window.remember_state=false` 升级为 `true`。
- 第二窗口仍保持固定 `340x320` 小窗,不注册尺寸恢复逻辑。
- `go test ./...`、`npm run build`、`go build -o bin\cmbone.exe .` 和 `cmd /c build.bat` 通过。
同日完成商品图片 AI 优化任务模型:
- 新增 `internal/models/image_task.go` 和 `internal/services/image_optimization_service.go`。
- 新增 `image_tasks`、`image_task_results` 迁移和索引。
- 新增 `ImageOptimizationService.CreateTask/ListTasks/GetTask/CancelTask`。
- 当前 `CreateTask` 使用本地模拟 provider,创建成功任务和结果记录。
- 已重新生成 Wails `.js` bindings。
- `go test ./...`、`npm run build`、`go build -o bin\cmbone.exe .` 和 `cmd /c build.bat` 通过。
同日完成审计日志与运行日志边界:
- 新增 `internal/models/log.go` 和 `internal/services/log_service.go`。
- 新增 `app_logs` 迁移和日志查询索引。
- 新增 `AuditLogService.Record/List` 和 `AppLogService.Record/List`。
- 登录审计改为复用统一审计写入 helper。
- `AppConfigService.SetAppConfig` 会记录用户配置变更审计,自动窗口状态保存不会写审计。
- `ImageOptimizationService.CreateTask` 会记录任务创建审计,创建失败和服务错误会写运行日志。
- 已重新生成 Wails `.js` bindings。
- `go test ./...`、`npm run build`、`go build -o bin\cmbone.exe .` 和 `cmd /c build.bat` 通过。
同日完成 P0 数据统计服务:
- 新增 `internal/models/metrics.go` 和 `internal/services/metrics_service.go`。
- 新增 `DashboardMetrics` 和 `DailyMetric` 绑定模型。
- `MetricsService.GetDashboardMetrics` 可查询今日处理图片数、成功率、平均耗时、失败任务数和今日审计日志数。
- `MetricsService.GetProcessingTrend` 可查询最近 N 天处理趋势,默认 7 天,最多 30 天。
- SQLite 迁移新增 `image_tasks(created_at)` 和 `audit_logs(created_at)` 查询索引。
- 已重新生成 Wails `.js` bindings,新增 `metricsservice.js`。
- `go test ./...`、`npm run build`、`go build -o bin\cmbone.exe .` 和 `cmd /c build.bat` 通过。
同日完成商品图片 AI 优化页面:
- `frontend/src/modules/image-optimizer/ImageOptimizerPage.vue` 接入 `ImageOptimizationService` bindings。
- 页面支持选择图片、手动输入图片路径、选择优化类型、创建模拟任务。
- 页面支持按状态和优化类型筛选任务,展示任务列表、状态、耗时和创建时间。
- 页面支持查看任务详情,展示原图/结果占位预览、输出路径、文件大小、失败原因。
- WebView 文件选择器不返回绝对路径时,页面会提示用户粘贴绝对路径。
- `go test ./...`、`npm run build`、`go build -o bin\cmbone.exe .` 和 `cmd /c build.bat` 通过。
同日完成常用组件示例页:
- `frontend/src/modules/components-demo/ComponentsDemoPage.vue` 从静态骨架改为可交互示例。
- 表单区支持输入框、多选框、下拉框和多行文本,并实时展示当前值。
- 表格区支持关键字、分类、状态筛选,展示分页和状态标签。
- `go test ./...`、`npm run build`、`go build -o bin\cmbone.exe .` 和 `cmd /c build.bat` 通过。
同日完成 Wails bindings 生成流程文档:
- README 新增 `Wails Bindings` 章节。
- `docs/03-tech-stack.md` 新增 `Wails Bindings 生成流程` 章节。
- 明确固定版本:Go `1.23.0`、Wails CLI `v3.0.0-alpha.9`、`@wailsio/runtime 3.0.0-alpha.66`。
- 明确生成命令、生成时机、常见失败处理、提交要求和验证命令。
同日完成 Windows 打包验证文档:
- 新增 `docs/windows-packaging.md`。
- README 和技术栈文档已链接该文档。
- 文档记录 `wails3 package` 前置条件、默认 NSIS 路径、输出目录、安装包验证清单和常见失败处理。
- 已确认本机 `wails3 package -help` 可用,但 PATH 中未检测到 `makensis`,因此未强跑 NSIS 打包。
同日完成模板 smoke 清单:
- 新增 `docs/smoke-checklist.md`。
- `go test ./...`、`npm run build`、`go build -o bin\cmbone.exe .` 和 `cmd /c build.bat` 通过。
- 启动 `bin\cmbone.exe` 后进程未退出,主窗口标题为 `main`。
- 自动发送默认 `Alt+P` 后可见窗口包含 `second, main`,热键打开第二窗口通过。
- 登录、设置、业务样例、日志、统计、组件示例、托盘、热键均已按清单记录验证方式和结果。
同日创建 `ec` 分支并启动电商场景:
- `ec` 分支已推送到 `origin/ec`。
- 新增 `/#/products` 商品中心路由。
- 新增商品中心页面,当前使用前端本地示例数据。
- 商品中心支持商品草稿、SKU、类目、渠道、售价、库存、上架状态、图片状态、筛选、分页和图片优化入口。
- 新增 `docs/ecommerce-scenario.md`。
- `go test ./...`、`cd frontend && npm run build`、`go build -o bin\cmbone.exe .` 和 `cmd /c build.bat` 通过。
同日修复主窗口最大化黑边与布局填充:
- Go 侧主窗口设置 `BackgroundColour`,避免 WebView 外围区域显示默认黑底。
- 前端 `html`、`body`、`#app`、应用根容器和主布局统一填满窗口,并设置明暗主题背景。
- `go test ./...`、`cd frontend && npm run build`、`go build -o bin\cmbone.exe .` 和 `cmd /c build.bat` 通过。
- 启动 `bin\cmbone.exe` 后程序化最大化窗口,左右边缘采样 `11264` 个像素,纯黑像素数为 `0`。
同日完成 Windows 主窗口标题栏双击最大化:
- 主窗口标题栏使用短时间、小距离的双击检测,不依赖 WebView 是否合成 `dblclick` 事件。
- 右上角控件区继续保持 `no-drag`,双击不误触发标题栏最大化逻辑。
- `go test ./...`、`cd frontend && npm run build`、`go build -o bin\cmbone.exe .` 和 `cmd /c build.bat` 通过。
- 启动 `bin\cmbone.exe` 后自动化双击顶部中间区域,窗口从普通状态进入最大化;双击右侧 `no-drag` 控件区后窗口未最大化。
## 下一步
继续领取 [`06-tasks.md`](06-tasks.md) 的 `EC-002`:建立商品后端模型。
## 当前 blocker
无。
+34
View File
@@ -0,0 +1,34 @@
# 电商场景开发说明
当前分支:`ec`
目标:在可复用桌面应用模板基础上,发展一个面向电商运营的 Windows 桌面端应用。
## 场景边界
第一阶段聚焦商品运营,不做完整 ERP:
- 商品中心:维护 SKU、商品名称、类目、渠道、售价、库存、上架状态和图片状态。
- 商品图片 AI 优化:复用现有 `ImageOptimizationService`,服务于商品主图和渠道图。
- 操作日志:记录登录、设置变更、任务创建和后续商品关键操作。
- 数据统计:围绕商品图片处理量、成功率、失败任务和运营审计做基础统计。
## 当前已落地
- 新增 `/#/products` 商品中心页面。
- 主导航新增“商品中心”。
- 商品中心当前使用前端本地示例数据,支持:
- 新增商品草稿。
- 商品、SKU、渠道关键字筛选。
- 类目、上架状态、图片状态筛选。
- 商品列表、价格、库存、上架状态、图片状态展示。
- 跳转到图片优化和统计模块。
## 下一步建议
1. 后端新增商品模型和 SQLite 表:`products`。
2. 新增 `ProductService`:`CreateProduct`、`ListProducts`、`UpdateProductStatus`、`UpdateImageStatus`。
3. 商品中心改为调用 Wails bindings,不再使用前端本地示例数据。
4. 商品图片 AI 优化任务增加 `product_id` 或 `sku` 关联。
5. 统计模块增加商品维度:商品总数、待优化图片、在售商品、失败图片任务。
6. 操作日志接入商品草稿创建、上架、下架、图片优化关联等审计事件。
+38
View File
@@ -0,0 +1,38 @@
# 评估标准
用于 code review、交接和 AI 输出自检。
## 必须满足
- 能说明本次改动对应哪个任务。
- 没有超出任务范围。
- 版本、命令、路径和当前代码事实一致。
- 相关构建或测试命令已运行并记录结果。
- 没有提交构建产物、依赖目录或本地配置。
## 代码质量
| 等级 | 标准 |
| --- | --- |
| A | 改动小、边界清楚、测试充分、文档同步、无明显维护风险 |
| B | 功能正确、构建通过、文档基本同步,仍有少量后续优化空间 |
| C | 可运行但边界或错误处理薄弱,需要安排后续任务 |
| D | 构建失败、范围失控、接口不一致或缺少关键验证 |
## UI 质量
| 等级 | 标准 |
| --- | --- |
| A | 布局稳定、信息密度合适、窗口尺寸变化时无溢出 |
| B | 主要路径可用,局部样式可继续优化 |
| C | 功能可用但有明显布局问题或交互不清 |
| D | 关键控件不可用、文本重叠或页面无法加载 |
## 文档质量
| 等级 | 标准 |
| --- | --- |
| A | 文档能直接指导新维护者执行任务和验证 |
| B | 主要事实正确,少量细节需要补充 |
| C | 有导航但缺少真实命令或验收标准 |
| D | 文档与代码冲突,可能误导维护者 |
+117
View File
@@ -0,0 +1,117 @@
# 方法地图
> 维护者查找入口时先看这里,再进入具体文件。
## 应用启动
| 文件 | 方法 / 内容 | 说明 |
| --- | --- | --- |
| `main.go` | `main()` | 创建 Wails app、注册服务、嵌入资源、启动应用 |
| `internal/services/appservice.go` | `ConfigureAppService` | 配置窗口、托盘、语言资源和生命周期 |
| `internal/services/window_state.go` | main window state helpers | 读取和保存主窗口尺寸、最大化状态;第二窗口不参与 |
## 存储
| 文件 | 方法 / 内容 | 说明 |
| --- | --- | --- |
| `internal/services/store.go` | `NewSuiStore` | 打开 SQLite 并执行初始化 |
| `internal/services/store.go` | migration / CRUD 方法 | 初始化 `hotkeys`、`appconfig` 并提供基础读写 |
| `internal/services/store_test.go` | store 测试 | 覆盖存储初始化和关键读写 |
## 配置
| 文件 | 方法 / 内容 | 说明 |
| --- | --- | --- |
| `internal/services/appconfig_service.go` | `GetAppConfig` / `SetAppConfig` | 前端配置读写入口 |
| `internal/services/appconfig_service.go` | `GetLanguage` / `SetLanguage` | 语言配置读写入口 |
| `internal/services/appconfig_service.go` | `GetSettingsSchema` | 返回设置分组、默认值和当前值 |
| `internal/services/settings_schema.go` | settings schema | 集中定义主题、语言、窗口、快捷键、数据目录、日志策略和登录配置 |
| `internal/models/settings.go` | `AppSettingGroup` / `AppSettingDefinition` | 设置 schema 前端绑定模型 |
## 登录
| 文件 | 方法 / 内容 | 说明 |
| --- | --- | --- |
| `internal/services/auth_service.go` | `AuthService` | 登录、退出、当前会话和 auth mode |
| `internal/services/auth_service.go` | `LocalAuthProvider` | 校验后端初始化的本地固定账号 |
| `internal/services/auth_service.go` | `RemoteAuthProvider` | 真实后端登录 provider 预留边界 |
| `internal/models/auth.go` | `AuthSession` / `AuthProviderOption` | 前端绑定模型 |
| `frontend/src/modules/auth/LoginPage.vue` | 登录页 | 调用后端 AuthService,不写死账号密码 |
## 图片优化任务
| 文件 | 方法 / 内容 | 说明 |
| --- | --- | --- |
| `internal/services/image_optimization_service.go` | `ImageOptimizationService` | 创建本地模拟图片优化任务、查询任务和结果 |
| `internal/models/image_task.go` | `ImageTask` / `ImageTaskResult` / `ImageTaskDetail` | 图片优化任务绑定模型 |
| `frontend/bindings/cmbone/internal/services/imageoptimizationservice.js` | Wails binding | 前端调用图片优化任务服务的生成绑定 |
## 日志
| 文件 | 方法 / 内容 | 说明 |
| --- | --- | --- |
| `internal/services/log_service.go` | `AuditLogService` | 记录和查询用户动作审计日志 |
| `internal/services/log_service.go` | `AppLogService` | 记录和查询运行排错日志 |
| `internal/models/log.go` | `AuditLogFilter` / `AppLog` / `AppLogFilter` | 日志查询绑定模型 |
| `frontend/bindings/cmbone/internal/services/auditlogservice.js` | Wails binding | 前端调用审计日志服务的生成绑定 |
| `frontend/bindings/cmbone/internal/services/applogservice.js` | Wails binding | 前端调用运行日志服务的生成绑定 |
## 数据统计
| 文件 | 方法 / 内容 | 说明 |
| --- | --- | --- |
| `internal/services/metrics_service.go` | `MetricsService` | 查询 P0 统计卡片和最近 N 天处理趋势 |
| `internal/models/metrics.go` | `DashboardMetrics` / `DailyMetric` | 统计服务绑定模型 |
| `frontend/bindings/cmbone/internal/services/metricsservice.js` | Wails binding | 前端调用统计服务的生成绑定 |
## 目标模板模块
| 模块 | 目标服务 | 说明 |
| --- | --- | --- |
| 登录 | `AuthService` | 已实现本地固定账号 provider,真实后端 provider 保留边界 |
| 审计日志 | `AuditLogService` | 已实现记录和查询;登录、设置变更、图片任务创建已接入 |
| 运行日志 | `AppLogService` | 已实现记录和查询;图片任务失败和服务错误已接入 |
| 商品图片 AI 优化 | `ImageOptimizationService` | 已实现本地模拟任务模型和页面闭环 |
| 数据统计 | `MetricsService` | 已实现后端聚合;统计页面展示待后续扩展 |
## 热键
| 文件 | 方法 / 内容 | 说明 |
| --- | --- | --- |
| `internal/services/hotkey_service.go` | `GetHotkeys` | 返回当前热键配置 |
| `internal/services/hotkey_service.go` | `UpHotkey` | 更新热键并重新注册 |
| `platform/hotkey_windows.go` | Windows 热键实现 | Windows 平台注册逻辑 |
| `platform/hotkey_darwin.go` | macOS 热键实现 | macOS 平台注册逻辑 |
## 前端
| 文件 | 方法 / 内容 | 说明 |
| --- | --- | --- |
| `frontend/src/main.ts` | Vue 入口 | 注册应用、路由和国际化 |
| `frontend/src/router/index.ts` | routes | 登录、主应用模块和第二窗口路由 |
| `frontend/src/layouts/MainLayout.vue` | 主窗口布局 | 侧边导航、顶部窗口区和 `RouterView` 内容区 |
| `frontend/src/components/template/ModulePageShell.vue` | 模块页共享壳 | 模块标题、状态和摘要卡片 |
| `frontend/src/modules/auth/LoginPage.vue` | 登录页 | 调用 AuthService 登录并处理错误 |
| `frontend/src/modules/dashboard/DashboardPage.vue` | 工作台骨架 | P0 指标和近期任务入口 |
| `frontend/src/modules/products/ProductCenterPage.vue` | 商品中心 | 电商商品草稿、筛选、列表和状态入口 |
| `frontend/src/modules/image-optimizer/ImageOptimizerPage.vue` | 主业务页面 | 商品图片 AI 优化任务创建、列表、状态和结果预览 |
| `frontend/src/modules/logs/LogsPage.vue` | 日志页骨架 | 审计日志和运行日志入口 |
| `frontend/src/modules/statistics/StatisticsPage.vue` | 统计页骨架 | P0 指标和 7 天趋势入口 |
| `frontend/src/modules/settings/SettingsPage.vue` | 设置页 | 复用存量设置组件,并展示后端统一设置 schema |
| `frontend/src/modules/components-demo/ComponentsDemoPage.vue` | 组件示例页 | 输入框、多选框、下拉框、多行文本、表格、筛选、分页和状态标签 |
| `frontend/src/pages/Home/Index.vue` | 第二窗口入口 | OCR 示例页面 |
| `frontend/src/utils/ThemeManager.ts` | 主题工具 | 主题切换和持久化协作 |
| `frontend/src/utils/i18n.ts` | 国际化工具 | 语言资源和切换 |
## 目标前端目录
| 目录 | 说明 |
| --- | --- |
| `frontend/src/layouts/` | 登录布局和主应用布局 |
| `frontend/src/modules/auth/` | 登录和会话 |
| `frontend/src/modules/settings/` | 设置中心 |
| `frontend/src/modules/products/` | 电商商品中心 |
| `frontend/src/modules/image-optimizer/` | 商品图片 AI 优化业务样例 |
| `frontend/src/modules/logs/` | 审计日志和运行日志 |
| `frontend/src/modules/statistics/` | 数据统计 |
| `frontend/src/modules/components-demo/` | 常用组件示例 |
+49
View File
@@ -0,0 +1,49 @@
# 质量文档
## 当前质量概览
| 领域 | 当前等级 | 说明 |
| --- | --- | --- |
| 项目结构 | B | 已从模板整理为普通工程,目录职责基本清楚 |
| 产品定位 | B | 已校准为 Windows-first 可复用桌面应用模板,后续需要代码落地 |
| 后端服务 | B+ | 现有服务边界清晰,登录 provider、设置 schema、日志、统计和业务样例服务已落地,真实 provider 和统计页面仍需推进 |
| 本地存储 | B | SQLite 初始化和基础测试已存在,目标新增表需要迁移和测试 |
| 前端结构 | B+ | Vue 组件按功能拆分,窗口、布局策略和目标路由骨架已落地,设置页已接入后端 schema |
| 构建验证 | B | Go 测试、前端构建、后端编译可运行 |
| 文档 | B+ | Harness Coding 文档已接入,并同步了模板愿景 |
## 主要风险
- 模板范围膨胀:登录、设置、业务、日志、统计和组件示例都要做,但必须小步交付。
- Wails v3 alpha 版本兼容性风险。
- bindings 与 Go 服务签名不一致的运行时风险。
- 真实后端登录 provider 和真实 AI provider 尚未接入,当前仍是本地演示能力。
- 热键、托盘、OCR 等平台能力在 Windows 以外行为不完全一致。
- 前端缺少专门单元测试,主要依赖构建和人工 smoke。
## 质量门槛
每个代码任务至少达到:
- Go 代码 `gofmt`。
- `go test ./...` 通过。
- 涉及前端时 `npm run build` 通过。
- 涉及后端启动时 `go build -o bin/cmbone.exe .` 通过。
- 涉及 UI 时完成对应窗口的人工检查。
- 涉及登录、日志、统计或业务样例时,同步更新 `api.md`、`routes.md`、`current-state.md` 和任务验收。
## 模板模块质量要求
- 登录:本地 provider 和 remote provider 边界清楚,密码不写死在前端。
- 设置:配置项有 key、类型、默认值、说明和迁移逻辑。
- 主业务:商品图片 AI 优化可以先模拟,但任务状态、失败原因和结果结构要真实。
- 审计日志:记录用户动作,字段可查询、可导出。
- 运行日志:记录系统错误,能支持排错。
- 数据统计:只做 P0 指标,不提前做复杂 BI。
- 组件示例:复用 Ant Design Vue,不自研组件库。
## 改进方向
- 搭建登录、Dashboard、图片优化、日志、统计、设置、组件示例等目标路由骨架。
- 为统计模块补充真实页面闭环。
- 为 Wails bindings 生成流程补充完整操作说明。
+66
View File
@@ -0,0 +1,66 @@
# 前端路由
路由定义文件:`frontend/src/router/index.ts`
当前使用 hash history,适配 Wails 桌面窗口。
## 当前路由
| 路由 | 组件 | 窗口 | 职责 |
| --- | --- | --- | --- |
| `#/` | `frontend/src/layouts/MainLayout.vue` | 主窗口 | 重定向到 `#/dashboard` |
| `#/login` | `frontend/src/modules/auth/LoginPage.vue` | 主窗口 | 登录入口,已接入本地固定账号 provider,真实后端 provider 保留边界 |
| `#/dashboard` | `frontend/src/modules/dashboard/DashboardPage.vue` | 主窗口 | 工作台,展示 P0 指标和近期任务入口 |
| `#/products` | `frontend/src/modules/products/ProductCenterPage.vue` | 主窗口 | 电商商品中心,支持商品草稿、筛选、列表和图片优化入口 |
| `#/image-optimizer` | `frontend/src/modules/image-optimizer/ImageOptimizerPage.vue` | 主窗口 | 商品图片 AI 优化业务样例,已接入任务创建、列表和结果预览 |
| `#/logs` | `frontend/src/modules/logs/LogsPage.vue` | 主窗口 | 审计日志和运行日志统一入口骨架 |
| `#/statistics` | `frontend/src/modules/statistics/StatisticsPage.vue` | 主窗口 | 第一批统计指标和趋势入口;后端 `MetricsService` 已可查询 |
| `#/settings` | `frontend/src/modules/settings/SettingsPage.vue` | 主窗口 | 设置中心,复用存量设置组件并展示统一设置 schema |
| `#/components` | `frontend/src/modules/components-demo/ComponentsDemoPage.vue` | 主窗口 | 常用输入、选择、文本域、表格、筛选和状态标签示例 |
| `#/second` | `frontend/src/pages/Home/Index.vue` | 第二窗口 | 当前 OCR 示例和轻量快捷功能 |
| `#/business/image-optimizer` | redirect | 主窗口 | 兼容目标路由,重定向到 `#/image-optimizer` |
| `#/logs/audit` | redirect | 主窗口 | 兼容目标路由,重定向到 `#/logs` |
| `#/logs/app` | redirect | 主窗口 | 兼容目标路由,重定向到 `#/logs` |
## 目标路由演进
| 路由 | 页面 | 职责 |
| --- | --- | --- |
| `#/login` | 登录页 | 已接入本地固定账号登录;真实后端登录待后续接入 |
| `#/products` | 商品中心 | `ec` 分支起点,当前为前端本地示例数据;后续接入 ProductService |
| `#/image-optimizer` | 商品图片 AI 优化 | 已接入页面任务列表、状态、结果和失败原因 |
| `#/logs` | 操作日志 | 后端审计日志和运行日志服务已接入;页面列表待后续扩展 |
| `#/statistics` | 数据统计 | 后端 `MetricsService` 已接入趋势、成功率、耗时和失败任务;页面展示待后续扩展 |
| `#/settings` | 设置中心 | 已接入统一设置 schema,窗口配置已用于主窗口尺寸恢复 |
| `#/components` | 组件示例 | 已接入输入框、多选框、下拉框、多行文本、表格、筛选、分页和状态标签 |
## 主窗口布局
目标主窗口使用三段式布局:
- 侧边导航:模块入口。
- 顶部区域:当前用户、运行状态、快捷动作。
- 内容区:路由页面,填满剩余空间。
Windows 10+ 是主要体验目标。主窗口策略已经实现到当前代码:
- 默认不最大化。
- 初始宽高:`1280x800`。
- 最小宽高:`1024x720`。
- 启动时居中。
- 允许用户调整大小。
- 保存主窗口尺寸和最大化状态,下次启动恢复用户上次选择。
- 页面根容器使用 `width: 100%` 和 `height: 100vh`。
- 主布局内部使用 flex 或 grid,让侧边导航、顶部区域和内容区稳定填满窗口。
- 阅读型内容使用最大宽度,建议 `max-width: 960px` 并居中。
- 列表、仪表盘、设置页等工作型页面自适应拉伸,占满可用内容区。
- 第二窗口保持小窗,不参与主窗口最大化、尺寸恢复和布局填充逻辑。
## 页面规则
- 新增页面必须先确认属于模板能力、业务样例还是开发者示例。
- 主业务页面放在主窗口,不放在第二窗口。
- 第二窗口保持轻量快捷功能,不承载长期复杂业务流程。
- 页面内容不得只占左上角小块;除阅读型内容外,默认应填满主内容区域。
- 路由变化必须同步本文和相关任务验收。
- 主窗口模块页默认放在 `frontend/src/modules/`,只把跨模块复用 UI 放到 `frontend/src/components/`。
+37
View File
@@ -0,0 +1,37 @@
# 模板 Smoke 清单
验证日期:2026-07-09
验证环境:Windows,本机项目根目录 `D:\chengma\cmbone`
目标:确认可复用桌面应用模板的基础模块在当前构建下可用,记录自动验证边界。
## 命令验证
| 项目 | 命令 / 方式 | 结果 | 备注 |
| --- | --- | --- | --- |
| Go 测试 | `go test ./...` | PASS | 覆盖登录、设置、窗口状态、图片任务、日志、统计等后端服务 |
| 前端构建 | `cd frontend && npm run build` | PASS | 仍有 Browserslist 过期和 chunk 大小警告 |
| 后端编译 | `go build -o bin\cmbone.exe .` | PASS | 生成 `bin/cmbone.exe` |
| Wails 构建 | `cmd /c build.bat` | PASS | 仍提示 `wails3 build` 是 `wails3 task` 的 alias |
| 启动 smoke | 启动 `bin\cmbone.exe`,等待 6 秒,读取窗口句柄 | PASS | 进程未退出,主窗口标题为 `main` |
| 热键 smoke | 启动应用后发送默认 `Alt+P`,枚举可见窗口标题 | PASS | 发送前:`main`;发送后:`second, main` |
## 模块清单
| 模块 | 验证方式 | 结果 | 说明 |
| --- | --- | --- | --- |
| 登录 | 后端测试 + 前端构建 | PASS | `AuthService` 本地账号、会话、失败审计和 remote provider 边界有测试覆盖;登录页构建通过 |
| 设置 | 后端测试 + 前端构建 | PASS | 统一设置 schema、只读/类型校验、设置页 schema 展示构建通过 |
| 主业务样例 | 后端测试 + 前端构建 | PASS | 图片任务创建、列表、详情、失败路径有测试或页面构建覆盖 |
| 操作日志 | 后端测试 + 前端构建 | PASS | 审计日志、运行日志服务和写入路径有测试覆盖;日志页入口构建通过 |
| 数据统计 | 后端测试 + 前端构建 | PASS | Dashboard 指标和趋势聚合有测试覆盖;统计页入口构建通过 |
| 组件示例 | 前端构建 | PASS | 输入框、多选框、下拉框、多行文本、表格、筛选、分页、状态标签示例构建通过 |
| 托盘 | 代码路径检查 + 启动 smoke | PASS | `AppService` 启动时初始化系统托盘和菜单;托盘菜单点击仍建议人工桌面复核 |
| 热键 | 自动化桌面 smoke | PASS | 默认 `Alt+P` 可打开第二窗口 |
## 非阻塞项
- 前端构建仍提示 Browserslist 数据过期。
- 前端构建仍提示部分 chunk 大于 500 kB。
- `build.bat` 仍提示 `wails3 build` 是 `wails3 task` 的 alias。
- 本机缺少 NSIS `makensis`,未执行 `wails3 package` 生成安装包;打包前置条件已记录在 [`windows-packaging.md`](windows-packaging.md)。
- 托盘菜单的点击行为需要桌面人工复核;本轮已确认初始化代码路径和应用启动 smoke。
+18
View File
@@ -0,0 +1,18 @@
# 一任务一文件规则
默认情况下,本项目使用 [`../06-tasks.md`](../06-tasks.md) 作为单文件任务看板。
当多人或多个 agent 并发工作时,为避免抢改同一个看板,改用本目录的一任务一文件方式:
1. 新任务复制 [`_template.md`](_template.md)。
2. 文件命名为 `T-<编号>-<短名称>.md`。
3. 状态写在文件 frontmatter。
4. 执行记录写入该任务文件的 `## 执行记录`。
5. 不再逐任务追加根目录 `progress.md`,只在里程碑或交接时同步摘要。
## 状态
- `TODO`:待开始。
- `DOING`:进行中。
- `DONE`:完成并通过验收。
- `BLOCKED`:受阻,需要明确原因。
+27
View File
@@ -0,0 +1,27 @@
---
id: T-000
title: 任务标题
status: TODO
depends_on: []
owner: ""
---
# 任务标题
## 背景
说明为什么要做这个任务。
## 范围
- 做什么。
- 不做什么。
## 验收标准
- [ ] 可观察、可执行的验收点。
- [ ] 相关测试或构建命令通过。
## 执行记录
尚未开始。
+125
View File
@@ -0,0 +1,125 @@
# Windows 打包验证
本文记录当前项目在 Windows 10+ 上执行 `wails3 package` 前需要确认的条件、默认输出位置和常见失败处理。
## 当前范围
- 默认打包格式:NSIS 安装包。
- 当前 Wails CLI:`wails3 v3.0.0-alpha.9`。
- 当前项目没有 `wails.json`,MSIX 任务暂不作为可用路径。
- Windows 打包配置位于 `build/windows/`。
## 前置条件
1. Go、Node.js、npm 可用。
2. Wails CLI 已安装固定版本:
```powershell
go install github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-alpha.9
$env:Path += ";$(go env GOPATH)\bin"
wails3 package -help
```
3. 前端依赖已安装:
```powershell
cd frontend
npm install
cd ..
```
4. 默认 NSIS 打包需要 `makensis` 在 PATH 中:
```powershell
Get-Command makensis
```
如果找不到 `makensis`,先安装 NSIS,并把 NSIS 安装目录加入 PATH。
5. Windows 打包资源存在:
```text
build/windows/icon.ico
build/windows/info.json
build/windows/wails.exe.manifest
build/windows/nsis/project.nsi.tmpl
```
## 打包前验证
打包前先确认普通构建通过:
```powershell
go test ./...
cd frontend
npm run build
cd ..
go build -o bin\cmbone.exe .
cmd /c build.bat
```
`build.bat` 使用 `wails3 build`,会生成 `bin\cmbone.exe`。当前 Wails v3 alpha CLI 会提示 `wails3 build` 是 `wails3 task` 的 alias,这是非阻塞提示。
## 执行打包
从项目根目录执行:
```powershell
$env:Path += ";$(go env GOPATH)\bin"
wails3 package
```
当前 Taskfile 的 Windows package 会走 NSIS:
1. 先执行 production build。
2. 生成 WebView2 bootstrapper。
3. 调用 `makensis` 生成安装包。
Production build 使用的关键参数来自 `build/windows/Taskfile.yml`:
```text
-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"
```
## 输出目录
普通构建输出:
```text
bin/cmbone.exe
```
默认 NSIS 安装包输出:
```text
bin/cmbone-amd64-installer.exe
```
该路径来自 `build/windows/nsis/project.nsi.tmpl` 的 `OutFile` 配置。
MSIX 不是当前默认路径。`build/windows/Taskfile.yml` 中的 MSIX task 依赖 `wails.json`,但当前项目根目录没有该文件;需要后续单独配置后再启用。
## 安装包验证
生成安装包后至少检查:
1. `bin/cmbone-amd64-installer.exe` 存在且文件大小不为 0。
2. 在 Windows 10+ 测试机或虚拟机运行安装包。
3. 安装完成后桌面和开始菜单快捷方式可启动应用。
4. 主窗口可见,默认进入 `/#/dashboard`。
5. 本地演示账号 `admin` / `admin123` 可登录。
6. 能打开 `/#/image-optimizer`、`/#/settings`、`/#/components`。
7. 卸载入口可用,卸载后安装目录被移除。
## 常见失败
| 现象 | 处理 |
| --- | --- |
| `wails3` 找不到 | 安装固定版本,并把 `$(go env GOPATH)\bin` 加入当前 PowerShell PATH |
| `makensis` 找不到 | 安装 NSIS,把 NSIS 目录加入 PATH 后重新打开终端 |
| WebView2 bootstrapper 生成失败 | 检查网络、代理和杀毒软件拦截;必要时单独执行 `wails3 generate webview2bootstrapper -dir build/windows/nsis` |
| 前端构建失败 | 先运行 `cd frontend && npm install && npm run build`,修复 TypeScript 或依赖问题 |
| bindings 失效 | 运行 `wails3 generate bindings -clean=true`,再运行 `npm run build` |
| Go 编译失败 | 运行 `go test ./...` 和 `go build -o bin\cmbone.exe .` 定位服务或模型错误 |
| 图标、manifest 或版本信息错误 | 检查 `build/windows/icon.ico`、`build/windows/wails.exe.manifest`、`build/windows/info.json` |
| MSIX 打包失败 | 当前项目未配置 `wails.json`,不要把 MSIX 作为验收路径 |
Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

+93
View File
@@ -0,0 +1,93 @@
Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+18
View File
@@ -0,0 +1,18 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Type Support For `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
@@ -0,0 +1,5 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export * from "./models.js";
@@ -0,0 +1,986 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Create as $Create} from "@wailsio/runtime";
export class AppLog {
/**
* Creates a new AppLog instance.
* @param {Partial<AppLog>} [$$source = {}] - The source object to create the AppLog.
*/
constructor($$source = {}) {
if (!("id" in $$source)) {
/**
* @member
* @type {number}
*/
this["id"] = 0;
}
if (!("level" in $$source)) {
/**
* @member
* @type {string}
*/
this["level"] = "";
}
if (!("module" in $$source)) {
/**
* @member
* @type {string}
*/
this["module"] = "";
}
if (!("message" in $$source)) {
/**
* @member
* @type {string}
*/
this["message"] = "";
}
if (!("detail" in $$source)) {
/**
* @member
* @type {string}
*/
this["detail"] = "";
}
if (!("createdAt" in $$source)) {
/**
* @member
* @type {string}
*/
this["createdAt"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new AppLog instance from a string or object.
* @param {any} [$$source = {}]
* @returns {AppLog}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new AppLog(/** @type {Partial<AppLog>} */($$parsedSource));
}
}
export class AppLogFilter {
/**
* Creates a new AppLogFilter instance.
* @param {Partial<AppLogFilter>} [$$source = {}] - The source object to create the AppLogFilter.
*/
constructor($$source = {}) {
if (!("level" in $$source)) {
/**
* @member
* @type {string}
*/
this["level"] = "";
}
if (!("module" in $$source)) {
/**
* @member
* @type {string}
*/
this["module"] = "";
}
if (!("limit" in $$source)) {
/**
* @member
* @type {number}
*/
this["limit"] = 0;
}
if (!("offset" in $$source)) {
/**
* @member
* @type {number}
*/
this["offset"] = 0;
}
Object.assign(this, $$source);
}
/**
* Creates a new AppLogFilter instance from a string or object.
* @param {any} [$$source = {}]
* @returns {AppLogFilter}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new AppLogFilter(/** @type {Partial<AppLogFilter>} */($$parsedSource));
}
}
export class AppSettingDefinition {
/**
* Creates a new AppSettingDefinition instance.
* @param {Partial<AppSettingDefinition>} [$$source = {}] - The source object to create the AppSettingDefinition.
*/
constructor($$source = {}) {
if (!("key" in $$source)) {
/**
* @member
* @type {string}
*/
this["key"] = "";
}
if (!("category" in $$source)) {
/**
* @member
* @type {string}
*/
this["category"] = "";
}
if (!("label" in $$source)) {
/**
* @member
* @type {string}
*/
this["label"] = "";
}
if (!("type" in $$source)) {
/**
* @member
* @type {string}
*/
this["type"] = "";
}
if (!("defaultValue" in $$source)) {
/**
* @member
* @type {string}
*/
this["defaultValue"] = "";
}
if (!("value" in $$source)) {
/**
* @member
* @type {string}
*/
this["value"] = "";
}
if (!("description" in $$source)) {
/**
* @member
* @type {string}
*/
this["description"] = "";
}
if (!("options" in $$source)) {
/**
* @member
* @type {AppSettingOption[]}
*/
this["options"] = [];
}
if (!("readOnly" in $$source)) {
/**
* @member
* @type {boolean}
*/
this["readOnly"] = false;
}
Object.assign(this, $$source);
}
/**
* Creates a new AppSettingDefinition instance from a string or object.
* @param {any} [$$source = {}]
* @returns {AppSettingDefinition}
*/
static createFrom($$source = {}) {
const $$createField7_0 = $$createType1;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("options" in $$parsedSource) {
$$parsedSource["options"] = $$createField7_0($$parsedSource["options"]);
}
return new AppSettingDefinition(/** @type {Partial<AppSettingDefinition>} */($$parsedSource));
}
}
export class AppSettingGroup {
/**
* Creates a new AppSettingGroup instance.
* @param {Partial<AppSettingGroup>} [$$source = {}] - The source object to create the AppSettingGroup.
*/
constructor($$source = {}) {
if (!("category" in $$source)) {
/**
* @member
* @type {string}
*/
this["category"] = "";
}
if (!("label" in $$source)) {
/**
* @member
* @type {string}
*/
this["label"] = "";
}
if (!("description" in $$source)) {
/**
* @member
* @type {string}
*/
this["description"] = "";
}
if (!("items" in $$source)) {
/**
* @member
* @type {AppSettingDefinition[]}
*/
this["items"] = [];
}
Object.assign(this, $$source);
}
/**
* Creates a new AppSettingGroup instance from a string or object.
* @param {any} [$$source = {}]
* @returns {AppSettingGroup}
*/
static createFrom($$source = {}) {
const $$createField3_0 = $$createType3;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("items" in $$parsedSource) {
$$parsedSource["items"] = $$createField3_0($$parsedSource["items"]);
}
return new AppSettingGroup(/** @type {Partial<AppSettingGroup>} */($$parsedSource));
}
}
export class AppSettingOption {
/**
* Creates a new AppSettingOption instance.
* @param {Partial<AppSettingOption>} [$$source = {}] - The source object to create the AppSettingOption.
*/
constructor($$source = {}) {
if (!("value" in $$source)) {
/**
* @member
* @type {string}
*/
this["value"] = "";
}
if (!("label" in $$source)) {
/**
* @member
* @type {string}
*/
this["label"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new AppSettingOption instance from a string or object.
* @param {any} [$$source = {}]
* @returns {AppSettingOption}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new AppSettingOption(/** @type {Partial<AppSettingOption>} */($$parsedSource));
}
}
export class AuditLog {
/**
* Creates a new AuditLog instance.
* @param {Partial<AuditLog>} [$$source = {}] - The source object to create the AuditLog.
*/
constructor($$source = {}) {
if (!("id" in $$source)) {
/**
* @member
* @type {number}
*/
this["id"] = 0;
}
if (!("actor" in $$source)) {
/**
* @member
* @type {string}
*/
this["actor"] = "";
}
if (!("action" in $$source)) {
/**
* @member
* @type {string}
*/
this["action"] = "";
}
if (!("targetType" in $$source)) {
/**
* @member
* @type {string}
*/
this["targetType"] = "";
}
if (!("targetId" in $$source)) {
/**
* @member
* @type {string}
*/
this["targetId"] = "";
}
if (!("detail" in $$source)) {
/**
* @member
* @type {string}
*/
this["detail"] = "";
}
if (!("createdAt" in $$source)) {
/**
* @member
* @type {string}
*/
this["createdAt"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new AuditLog instance from a string or object.
* @param {any} [$$source = {}]
* @returns {AuditLog}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new AuditLog(/** @type {Partial<AuditLog>} */($$parsedSource));
}
}
export class AuditLogFilter {
/**
* Creates a new AuditLogFilter instance.
* @param {Partial<AuditLogFilter>} [$$source = {}] - The source object to create the AuditLogFilter.
*/
constructor($$source = {}) {
if (!("actor" in $$source)) {
/**
* @member
* @type {string}
*/
this["actor"] = "";
}
if (!("action" in $$source)) {
/**
* @member
* @type {string}
*/
this["action"] = "";
}
if (!("targetType" in $$source)) {
/**
* @member
* @type {string}
*/
this["targetType"] = "";
}
if (!("limit" in $$source)) {
/**
* @member
* @type {number}
*/
this["limit"] = 0;
}
if (!("offset" in $$source)) {
/**
* @member
* @type {number}
*/
this["offset"] = 0;
}
Object.assign(this, $$source);
}
/**
* Creates a new AuditLogFilter instance from a string or object.
* @param {any} [$$source = {}]
* @returns {AuditLogFilter}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new AuditLogFilter(/** @type {Partial<AuditLogFilter>} */($$parsedSource));
}
}
export class AuthProviderOption {
/**
* Creates a new AuthProviderOption instance.
* @param {Partial<AuthProviderOption>} [$$source = {}] - The source object to create the AuthProviderOption.
*/
constructor($$source = {}) {
if (!("value" in $$source)) {
/**
* @member
* @type {string}
*/
this["value"] = "";
}
if (!("label" in $$source)) {
/**
* @member
* @type {string}
*/
this["label"] = "";
}
if (!("description" in $$source)) {
/**
* @member
* @type {string}
*/
this["description"] = "";
}
if (!("enabled" in $$source)) {
/**
* @member
* @type {boolean}
*/
this["enabled"] = false;
}
Object.assign(this, $$source);
}
/**
* Creates a new AuthProviderOption instance from a string or object.
* @param {any} [$$source = {}]
* @returns {AuthProviderOption}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new AuthProviderOption(/** @type {Partial<AuthProviderOption>} */($$parsedSource));
}
}
export class AuthSession {
/**
* Creates a new AuthSession instance.
* @param {Partial<AuthSession>} [$$source = {}] - The source object to create the AuthSession.
*/
constructor($$source = {}) {
if (!("id" in $$source)) {
/**
* @member
* @type {number}
*/
this["id"] = 0;
}
if (!("userId" in $$source)) {
/**
* @member
* @type {string}
*/
this["userId"] = "";
}
if (!("username" in $$source)) {
/**
* @member
* @type {string}
*/
this["username"] = "";
}
if (!("provider" in $$source)) {
/**
* @member
* @type {string}
*/
this["provider"] = "";
}
if (!("token" in $$source)) {
/**
* @member
* @type {string}
*/
this["token"] = "";
}
if (!("createdAt" in $$source)) {
/**
* @member
* @type {string}
*/
this["createdAt"] = "";
}
if (!("expiresAt" in $$source)) {
/**
* @member
* @type {string}
*/
this["expiresAt"] = "";
}
if (!("active" in $$source)) {
/**
* @member
* @type {boolean}
*/
this["active"] = false;
}
Object.assign(this, $$source);
}
/**
* Creates a new AuthSession instance from a string or object.
* @param {any} [$$source = {}]
* @returns {AuthSession}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new AuthSession(/** @type {Partial<AuthSession>} */($$parsedSource));
}
}
export class DailyMetric {
/**
* Creates a new DailyMetric instance.
* @param {Partial<DailyMetric>} [$$source = {}] - The source object to create the DailyMetric.
*/
constructor($$source = {}) {
if (!("date" in $$source)) {
/**
* @member
* @type {string}
*/
this["date"] = "";
}
if (!("processedImages" in $$source)) {
/**
* @member
* @type {number}
*/
this["processedImages"] = 0;
}
if (!("succeededTasks" in $$source)) {
/**
* @member
* @type {number}
*/
this["succeededTasks"] = 0;
}
if (!("failedTasks" in $$source)) {
/**
* @member
* @type {number}
*/
this["failedTasks"] = 0;
}
if (!("averageDurationMs" in $$source)) {
/**
* @member
* @type {number}
*/
this["averageDurationMs"] = 0;
}
if (!("auditLogs" in $$source)) {
/**
* @member
* @type {number}
*/
this["auditLogs"] = 0;
}
Object.assign(this, $$source);
}
/**
* Creates a new DailyMetric instance from a string or object.
* @param {any} [$$source = {}]
* @returns {DailyMetric}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new DailyMetric(/** @type {Partial<DailyMetric>} */($$parsedSource));
}
}
export class DashboardMetrics {
/**
* Creates a new DashboardMetrics instance.
* @param {Partial<DashboardMetrics>} [$$source = {}] - The source object to create the DashboardMetrics.
*/
constructor($$source = {}) {
if (!("todayProcessedImages" in $$source)) {
/**
* @member
* @type {number}
*/
this["todayProcessedImages"] = 0;
}
if (!("successRate" in $$source)) {
/**
* @member
* @type {number}
*/
this["successRate"] = 0;
}
if (!("averageDurationMs" in $$source)) {
/**
* @member
* @type {number}
*/
this["averageDurationMs"] = 0;
}
if (!("failedTasks" in $$source)) {
/**
* @member
* @type {number}
*/
this["failedTasks"] = 0;
}
if (!("todayAuditLogs" in $$source)) {
/**
* @member
* @type {number}
*/
this["todayAuditLogs"] = 0;
}
Object.assign(this, $$source);
}
/**
* Creates a new DashboardMetrics instance from a string or object.
* @param {any} [$$source = {}]
* @returns {DashboardMetrics}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new DashboardMetrics(/** @type {Partial<DashboardMetrics>} */($$parsedSource));
}
}
export class Hotkey {
/**
* Creates a new Hotkey instance.
* @param {Partial<Hotkey>} [$$source = {}] - The source object to create the Hotkey.
*/
constructor($$source = {}) {
if (!("id" in $$source)) {
/**
* 热键ID
* @member
* @type {number}
*/
this["id"] = 0;
}
if (!("keycode" in $$source)) {
/**
* 键码
* @member
* @type {number}
*/
this["keycode"] = 0;
}
if (!("modifiers" in $$source)) {
/**
* 修饰键
* @member
* @type {number}
*/
this["modifiers"] = 0;
}
Object.assign(this, $$source);
}
/**
* Creates a new Hotkey instance from a string or object.
* @param {any} [$$source = {}]
* @returns {Hotkey}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new Hotkey(/** @type {Partial<Hotkey>} */($$parsedSource));
}
}
export class ImageTask {
/**
* Creates a new ImageTask instance.
* @param {Partial<ImageTask>} [$$source = {}] - The source object to create the ImageTask.
*/
constructor($$source = {}) {
if (!("id" in $$source)) {
/**
* @member
* @type {number}
*/
this["id"] = 0;
}
if (!("sourcePath" in $$source)) {
/**
* @member
* @type {string}
*/
this["sourcePath"] = "";
}
if (!("status" in $$source)) {
/**
* @member
* @type {string}
*/
this["status"] = "";
}
if (!("operation" in $$source)) {
/**
* @member
* @type {string}
*/
this["operation"] = "";
}
if (!("durationMs" in $$source)) {
/**
* @member
* @type {number}
*/
this["durationMs"] = 0;
}
if (!("errorMessage" in $$source)) {
/**
* @member
* @type {string}
*/
this["errorMessage"] = "";
}
if (!("createdAt" in $$source)) {
/**
* @member
* @type {string}
*/
this["createdAt"] = "";
}
if (!("updatedAt" in $$source)) {
/**
* @member
* @type {string}
*/
this["updatedAt"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new ImageTask instance from a string or object.
* @param {any} [$$source = {}]
* @returns {ImageTask}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new ImageTask(/** @type {Partial<ImageTask>} */($$parsedSource));
}
}
export class ImageTaskDetail {
/**
* Creates a new ImageTaskDetail instance.
* @param {Partial<ImageTaskDetail>} [$$source = {}] - The source object to create the ImageTaskDetail.
*/
constructor($$source = {}) {
if (!("task" in $$source)) {
/**
* @member
* @type {ImageTask}
*/
this["task"] = (new ImageTask());
}
if (!("result" in $$source)) {
/**
* @member
* @type {ImageTaskResult}
*/
this["result"] = (new ImageTaskResult());
}
Object.assign(this, $$source);
}
/**
* Creates a new ImageTaskDetail instance from a string or object.
* @param {any} [$$source = {}]
* @returns {ImageTaskDetail}
*/
static createFrom($$source = {}) {
const $$createField0_0 = $$createType4;
const $$createField1_0 = $$createType5;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("task" in $$parsedSource) {
$$parsedSource["task"] = $$createField0_0($$parsedSource["task"]);
}
if ("result" in $$parsedSource) {
$$parsedSource["result"] = $$createField1_0($$parsedSource["result"]);
}
return new ImageTaskDetail(/** @type {Partial<ImageTaskDetail>} */($$parsedSource));
}
}
export class ImageTaskFilter {
/**
* Creates a new ImageTaskFilter instance.
* @param {Partial<ImageTaskFilter>} [$$source = {}] - The source object to create the ImageTaskFilter.
*/
constructor($$source = {}) {
if (!("status" in $$source)) {
/**
* @member
* @type {string}
*/
this["status"] = "";
}
if (!("operation" in $$source)) {
/**
* @member
* @type {string}
*/
this["operation"] = "";
}
if (!("limit" in $$source)) {
/**
* @member
* @type {number}
*/
this["limit"] = 0;
}
if (!("offset" in $$source)) {
/**
* @member
* @type {number}
*/
this["offset"] = 0;
}
Object.assign(this, $$source);
}
/**
* Creates a new ImageTaskFilter instance from a string or object.
* @param {any} [$$source = {}]
* @returns {ImageTaskFilter}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new ImageTaskFilter(/** @type {Partial<ImageTaskFilter>} */($$parsedSource));
}
}
export class ImageTaskInput {
/**
* Creates a new ImageTaskInput instance.
* @param {Partial<ImageTaskInput>} [$$source = {}] - The source object to create the ImageTaskInput.
*/
constructor($$source = {}) {
if (!("sourcePath" in $$source)) {
/**
* @member
* @type {string}
*/
this["sourcePath"] = "";
}
if (!("operation" in $$source)) {
/**
* @member
* @type {string}
*/
this["operation"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new ImageTaskInput instance from a string or object.
* @param {any} [$$source = {}]
* @returns {ImageTaskInput}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new ImageTaskInput(/** @type {Partial<ImageTaskInput>} */($$parsedSource));
}
}
export class ImageTaskResult {
/**
* Creates a new ImageTaskResult instance.
* @param {Partial<ImageTaskResult>} [$$source = {}] - The source object to create the ImageTaskResult.
*/
constructor($$source = {}) {
if (!("id" in $$source)) {
/**
* @member
* @type {number}
*/
this["id"] = 0;
}
if (!("taskId" in $$source)) {
/**
* @member
* @type {number}
*/
this["taskId"] = 0;
}
if (!("outputPath" in $$source)) {
/**
* @member
* @type {string}
*/
this["outputPath"] = "";
}
if (!("beforeSize" in $$source)) {
/**
* @member
* @type {number}
*/
this["beforeSize"] = 0;
}
if (!("afterSize" in $$source)) {
/**
* @member
* @type {number}
*/
this["afterSize"] = 0;
}
if (!("createdAt" in $$source)) {
/**
* @member
* @type {string}
*/
this["createdAt"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new ImageTaskResult instance from a string or object.
* @param {any} [$$source = {}]
* @returns {ImageTaskResult}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new ImageTaskResult(/** @type {Partial<ImageTaskResult>} */($$parsedSource));
}
}
// Private type creation functions
const $$createType0 = AppSettingOption.createFrom;
const $$createType1 = $Create.Array($$createType0);
const $$createType2 = AppSettingDefinition.createFrom;
const $$createType3 = $Create.Array($$createType2);
const $$createType4 = ImageTask.createFrom;
const $$createType5 = ImageTaskResult.createFrom;
@@ -0,0 +1,67 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as models$0 from "../models/models.js";
/**
* 根据key获取配置项
* @param {string} key
* @returns {Promise<string> & { cancel(): void }}
*/
export function GetAppConfig(key) {
let $resultPromise = /** @type {any} */($Call.ByID(4279572935, key));
return $resultPromise;
}
/**
* 获取应用语言
* @returns {Promise<string> & { cancel(): void }}
*/
export function GetLanguage() {
let $resultPromise = /** @type {any} */($Call.ByID(2277729644));
return $resultPromise;
}
/**
* @returns {Promise<models$0.AppSettingGroup[]> & { cancel(): void }}
*/
export function GetSettingsSchema() {
let $resultPromise = /** @type {any} */($Call.ByID(2109450092));
let $typingPromise = /** @type {any} */($resultPromise.then(($result) => {
return $$createType1($result);
}));
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* 设置或更新配置项
* @param {string} key
* @param {string} value
* @returns {Promise<void> & { cancel(): void }}
*/
export function SetAppConfig(key, value) {
let $resultPromise = /** @type {any} */($Call.ByID(2391832275, key, value));
return $resultPromise;
}
/**
* 设置应用语言
* @param {string} lang
* @returns {Promise<void> & { cancel(): void }}
*/
export function SetLanguage(lang) {
let $resultPromise = /** @type {any} */($Call.ByID(538150432, lang));
return $resultPromise;
}
// Private type creation functions
const $$createType0 = models$0.AppSettingGroup.createFrom;
const $$createType1 = $Create.Array($$createType0);
@@ -0,0 +1,40 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as models$0 from "../models/models.js";
/**
* @param {models$0.AppLogFilter} filter
* @returns {Promise<models$0.AppLog[]> & { cancel(): void }}
*/
export function List(filter) {
let $resultPromise = /** @type {any} */($Call.ByID(3950321256, filter));
let $typingPromise = /** @type {any} */($resultPromise.then(($result) => {
return $$createType1($result);
}));
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* @param {string} level
* @param {string} $module
* @param {string} message
* @param {string} detail
* @returns {Promise<void> & { cancel(): void }}
*/
export function Record(level, $module, message, detail) {
let $resultPromise = /** @type {any} */($Call.ByID(1131123237, level, $module, message, detail));
return $resultPromise;
}
// Private type creation functions
const $$createType0 = models$0.AppLog.createFrom;
const $$createType1 = $Create.Array($$createType0);
@@ -0,0 +1,24 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
/**
* @returns {Promise<void> & { cancel(): void }}
*/
export function OpenSecondWindow() {
let $resultPromise = /** @type {any} */($Call.ByID(3768296744));
return $resultPromise;
}
/**
* @param {string} lang
* @returns {Promise<void> & { cancel(): void }}
*/
export function SetLanguage(lang) {
let $resultPromise = /** @type {any} */($Call.ByID(3062168746, lang));
return $resultPromise;
}
@@ -0,0 +1,40 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as models$0 from "../models/models.js";
/**
* @param {models$0.AuditLogFilter} filter
* @returns {Promise<models$0.AuditLog[]> & { cancel(): void }}
*/
export function List(filter) {
let $resultPromise = /** @type {any} */($Call.ByID(1985320716, filter));
let $typingPromise = /** @type {any} */($resultPromise.then(($result) => {
return $$createType1($result);
}));
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* @param {string} action
* @param {string} targetType
* @param {string} targetID
* @param {string} detail
* @returns {Promise<void> & { cancel(): void }}
*/
export function Record(action, targetType, targetID, detail) {
let $resultPromise = /** @type {any} */($Call.ByID(1661109649, action, targetType, targetID, detail));
return $resultPromise;
}
// Private type creation functions
const $$createType0 = models$0.AuditLog.createFrom;
const $$createType1 = $Create.Array($$createType0);
@@ -0,0 +1,80 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as models$0 from "../models/models.js";
/**
* @returns {Promise<string> & { cancel(): void }}
*/
export function GetAuthMode() {
let $resultPromise = /** @type {any} */($Call.ByID(1527507258));
return $resultPromise;
}
/**
* @returns {Promise<models$0.AuthProviderOption[]> & { cancel(): void }}
*/
export function GetAuthProviders() {
let $resultPromise = /** @type {any} */($Call.ByID(2710846997));
let $typingPromise = /** @type {any} */($resultPromise.then(($result) => {
return $$createType1($result);
}));
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* @returns {Promise<models$0.AuthSession> & { cancel(): void }}
*/
export function GetCurrentSession() {
let $resultPromise = /** @type {any} */($Call.ByID(2579662840));
let $typingPromise = /** @type {any} */($resultPromise.then(($result) => {
return $$createType2($result);
}));
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* @param {string} provider
* @param {string} username
* @param {string} password
* @returns {Promise<models$0.AuthSession> & { cancel(): void }}
*/
export function Login(provider, username, password) {
let $resultPromise = /** @type {any} */($Call.ByID(4113748438, provider, username, password));
let $typingPromise = /** @type {any} */($resultPromise.then(($result) => {
return $$createType2($result);
}));
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* @returns {Promise<void> & { cancel(): void }}
*/
export function Logout() {
let $resultPromise = /** @type {any} */($Call.ByID(3585850903));
return $resultPromise;
}
/**
* @param {string} mode
* @returns {Promise<void> & { cancel(): void }}
*/
export function SetAuthMode(mode) {
let $resultPromise = /** @type {any} */($Call.ByID(3911115982, mode));
return $resultPromise;
}
// Private type creation functions
const $$createType0 = models$0.AuthProviderOption.createFrom;
const $$createType1 = $Create.Array($$createType0);
const $$createType2 = models$0.AuthSession.createFrom;
@@ -0,0 +1,38 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as models$0 from "../models/models.js";
/**
* @returns {Promise<models$0.Hotkey[]> & { cancel(): void }}
*/
export function GetHotkeys() {
let $resultPromise = /** @type {any} */($Call.ByID(925491794));
let $typingPromise = /** @type {any} */($resultPromise.then(($result) => {
return $$createType1($result);
}));
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* @param {number} id
* @param {number} key
* @param {number} modifier
* @returns {Promise<void> & { cancel(): void }}
*/
export function UpHotkey(id, key, modifier) {
let $resultPromise = /** @type {any} */($Call.ByID(881898498, id, key, modifier));
return $resultPromise;
}
// Private type creation functions
const $$createType0 = models$0.Hotkey.createFrom;
const $$createType1 = $Create.Array($$createType0);
@@ -0,0 +1,64 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as models$0 from "../models/models.js";
/**
* @param {number} id
* @returns {Promise<void> & { cancel(): void }}
*/
export function CancelTask(id) {
let $resultPromise = /** @type {any} */($Call.ByID(3767102380, id));
return $resultPromise;
}
/**
* @param {models$0.ImageTaskInput} input
* @returns {Promise<models$0.ImageTaskDetail> & { cancel(): void }}
*/
export function CreateTask(input) {
let $resultPromise = /** @type {any} */($Call.ByID(1860238962, input));
let $typingPromise = /** @type {any} */($resultPromise.then(($result) => {
return $$createType0($result);
}));
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* @param {number} id
* @returns {Promise<models$0.ImageTaskDetail> & { cancel(): void }}
*/
export function GetTask(id) {
let $resultPromise = /** @type {any} */($Call.ByID(701224676, id));
let $typingPromise = /** @type {any} */($resultPromise.then(($result) => {
return $$createType0($result);
}));
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* @param {models$0.ImageTaskFilter} filter
* @returns {Promise<models$0.ImageTask[]> & { cancel(): void }}
*/
export function ListTasks(filter) {
let $resultPromise = /** @type {any} */($Call.ByID(1714055147, filter));
let $typingPromise = /** @type {any} */($resultPromise.then(($result) => {
return $$createType2($result);
}));
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
// Private type creation functions
const $$createType0 = models$0.ImageTaskDetail.createFrom;
const $$createType1 = models$0.ImageTask.createFrom;
const $$createType2 = $Create.Array($$createType1);
@@ -0,0 +1,26 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import * as AppConfigService from "./appconfigservice.js";
import * as AppLogService from "./applogservice.js";
import * as AppService from "./appservice.js";
import * as AuditLogService from "./auditlogservice.js";
import * as AuthService from "./authservice.js";
import * as HotkeyService from "./hotkeyservice.js";
import * as ImageOptimizationService from "./imageoptimizationservice.js";
import * as MetricsService from "./metricsservice.js";
import * as OCRService from "./ocrservice.js";
import * as SystemInfo from "./systeminfo.js";
export {
AppConfigService,
AppLogService,
AppService,
AuditLogService,
AuthService,
HotkeyService,
ImageOptimizationService,
MetricsService,
OCRService,
SystemInfo
};
@@ -0,0 +1,41 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as models$0 from "../models/models.js";
/**
* @returns {Promise<models$0.DashboardMetrics> & { cancel(): void }}
*/
export function GetDashboardMetrics() {
let $resultPromise = /** @type {any} */($Call.ByID(2761444903));
let $typingPromise = /** @type {any} */($resultPromise.then(($result) => {
return $$createType0($result);
}));
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* @param {number} days
* @returns {Promise<models$0.DailyMetric[]> & { cancel(): void }}
*/
export function GetProcessingTrend(days) {
let $resultPromise = /** @type {any} */($Call.ByID(2069059156, days));
let $typingPromise = /** @type {any} */($resultPromise.then(($result) => {
return $$createType2($result);
}));
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
// Private type creation functions
const $$createType0 = models$0.DashboardMetrics.createFrom;
const $$createType1 = models$0.DailyMetric.createFrom;
const $$createType2 = $Create.Array($$createType1);
@@ -0,0 +1,17 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
/**
* 识别Base64图片(data:image/png;base64,...)
* @param {string} base64str
* @returns {Promise<string> & { cancel(): void }}
*/
export function RecognizeImageBase64(base64str) {
let $resultPromise = /** @type {any} */($Call.ByID(1021755169, base64str));
return $resultPromise;
}
@@ -0,0 +1,15 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
/**
* @returns {Promise<string> & { cancel(): void }}
*/
export function GetOS() {
let $resultPromise = /** @type {any} */($Call.ByID(1831744513));
return $resultPromise;
}
+81
View File
@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/wails.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/style.css" />
<title>Wails + Vue + TS</title>
<style>
html, body {
margin: 0;
overflow: hidden; /* 禁止横向滚动 */
}
html, body, #app {
background: transparent !important;
}
/* 仅当 html 有 dark 类时生效 */
/* Select 输入框 */
html.dark .ant-select-selector {
color: #ffffff !important;
/* background-color: #1f1f1f !important; */
background-color: rgba(31, 41, 55, 1) !important;
/* background-color: rgb(30 41 59 / var(--tw-bg-opacity, 1)); */
border-color: #434343 !important;
}
/* Select 下拉面板 */
html.dark .ant-select-dropdown {
/* background-color: #141414 !important; */
background-color: rgba(31, 41, 55, 1) !important;
}
/* 下拉项文字 */
html.dark .ant-select-item {
color: #ffffff !important;
}
/* Hover / Active 状态 */
html.dark .ant-select-item-option-active {
/* background-color: #2a2a2a !important; */
background-color: rgb(46, 56, 71) !important;
/* background-color: rgb(30 41 59 / var(--tw-bg-opacity, 1)); */
}
/* 选中项 */
html.dark .ant-select-item-option-selected {
/* background-color: #3a3a3a !important; */
background-color: #283150 !important;
/* background-color: rgb(30 41 59 / var(--tw-bg-opacity, 1)) !important; */
color: #69c0ff !important;
}
/* Input */
html.dark .ant-input {
background-color: #374151 !important;
color: white !important;
}
html.dark .ant-input::placeholder {
color: #cccccc !important;
}
/* Input 带前后缀 */
html.dark .ant-input-affix-wrapper {
background-color: #374151 !important;
border-color: #626161 !important;
}
#app {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+3288
View File
File diff suppressed because it is too large Load Diff
+30
View File
@@ -0,0 +1,30 @@
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"typecheck": "vue-tsc",
"build:dev": "vite build --minify false --mode development",
"build": "vue-tsc && vite build --mode production",
"preview": "vite preview"
},
"dependencies": {
"@headlessui/vue": "^1.7.23",
"@wailsio/runtime": "3.0.0-alpha.66",
"ant-design-vue": "^4.2.6",
"vue": "^3.2.45",
"vue-i18n": "^11.1.5",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.7",
"autoprefixer": "^10.4.21",
"postcss": "^8.5.3",
"tailwindcss": "^3.4.1",
"typescript": "^4.9.3",
"vite": "^8.1.3",
"vue-tsc": "^1.0.11"
}
}
+6
View File
@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
Binary file not shown.
+145
View File
@@ -0,0 +1,145 @@
:root {
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: rgba(27, 38, 54, 1);
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
* {
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 400;
src: local(""),
url("./Inter-Medium.ttf") format("truetype");
}
h3 {
font-size: 3em;
line-height: 1.1;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
button {
width: 60px;
height: 30px;
line-height: 30px;
border-radius: 3px;
border: none;
margin: 0 0 0 20px;
padding: 0 8px;
cursor: pointer;
}
.result {
height: 20px;
line-height: 20px;
}
body {
margin: 0;
display: flex;
place-items: center;
place-content: center;
min-width: 320px;
min-height: 100vh;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
#app {
max-width: 1280px;
margin: 0 auto;
text-align: center;
}
.result {
height: 20px;
line-height: 20px;
margin: 1.5rem auto;
text-align: center;
}
.footer {
margin-top: 1rem;
align-content: center;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
.input-box .btn:hover {
background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%);
color: #333333;
}
.input-box .input {
border: none;
border-radius: 3px;
outline: none;
height: 30px;
line-height: 30px;
padding: 0 10px;
color: black;
background-color: rgba(240, 240, 240, 1);
-webkit-font-smoothing: antialiased;
}
.input-box .input:hover {
border: none;
background-color: rgba(255, 255, 255, 1);
}
.input-box .input:focus {
border: none;
background-color: rgba(255, 255, 255, 1);
}
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="797.5" height="834.5" viewBox="0 0 797.5 834.5" xmlns:xlink="http://www.w3.org/1999/xlink" role="img" artist="Katerina Limpitsouni" source="https://undraw.co/"><title>void</title><ellipse cx="308.5" cy="780" rx="308.5" ry="54.5" fill="#3f3d56"/><circle cx="496" cy="301.5" r="301.5" fill="#3f3d56"/><circle cx="496" cy="301.5" r="248.89787" opacity="0.05"/><circle cx="496" cy="301.5" r="203.99362" opacity="0.05"/><circle cx="496" cy="301.5" r="146.25957" opacity="0.05"/><path d="M398.42029,361.23224s-23.70394,66.72221-13.16886,90.42615,27.21564,46.52995,27.21564,46.52995S406.3216,365.62186,398.42029,361.23224Z" transform="translate(-201.25 -32.75)" fill="#d0cde1"/><path d="M398.42029,361.23224s-23.70394,66.72221-13.16886,90.42615,27.21564,46.52995,27.21564,46.52995S406.3216,365.62186,398.42029,361.23224Z" transform="translate(-201.25 -32.75)" opacity="0.1"/><path d="M415.10084,515.74682s-1.75585,16.68055-2.63377,17.55847.87792,2.63377,0,5.26754-1.75585,6.14547,0,7.02339-9.65716,78.13521-9.65716,78.13521-28.09356,36.8728-16.68055,94.81576l3.51169,58.82089s27.21564,1.75585,27.21564-7.90132c0,0-1.75585-11.413-1.75585-16.68055s4.38962-5.26754,1.75585-7.90131-2.63377-4.38962-2.63377-4.38962,4.38961-3.51169,3.51169-4.38962,7.90131-63.2105,7.90131-63.2105,9.65716-9.65716,9.65716-14.92471v-5.26754s4.38962-11.413,4.38962-12.29093,23.70394-54.43127,23.70394-54.43127l9.65716,38.62864,10.53509,55.3092s5.26754,50.04165,15.80262,69.356c0,0,18.4364,63.21051,18.4364,61.45466s30.72733-6.14547,29.84941-14.04678-18.4364-118.5197-18.4364-118.5197L533.62054,513.991Z" transform="translate(-201.25 -32.75)" fill="#2f2e41"/><path d="M391.3969,772.97846s-23.70394,46.53-7.90131,48.2858,21.94809,1.75585,28.97148-5.26754c3.83968-3.83968,11.61528-8.99134,17.87566-12.87285a23.117,23.117,0,0,0,10.96893-21.98175c-.463-4.29531-2.06792-7.83444-6.01858-8.16366-10.53508-.87792-22.826-10.53508-22.826-10.53508Z" transform="translate(-201.25 -32.75)" fill="#2f2e41"/><path d="M522.20753,807.21748s-23.70394,46.53-7.90131,48.28581,21.94809,1.75584,28.97148-5.26754c3.83968-3.83969,11.61528-8.99134,17.87566-12.87285a23.117,23.117,0,0,0,10.96893-21.98175c-.463-4.29531-2.06792-7.83444-6.01857-8.16367-10.53509-.87792-22.826-10.53508-22.826-10.53508Z" transform="translate(-201.25 -32.75)" fill="#2f2e41"/><circle cx="295.90488" cy="215.43252" r="36.90462" fill="#ffb8b8"/><path d="M473.43048,260.30832S447.07,308.81154,444.9612,308.81154,492.41,324.62781,492.41,324.62781s13.70743-46.39439,15.81626-50.61206Z" transform="translate(-201.25 -32.75)" fill="#ffb8b8"/><path d="M513.86726,313.3854s-52.67543-28.97148-57.943-28.09356-61.45466,50.04166-60.57673,70.2339,7.90131,53.55335,7.90131,53.55335,2.63377,93.05991,7.90131,93.93783-.87792,16.68055.87793,16.68055,122.90931,0,123.78724-2.63377S513.86726,313.3854,513.86726,313.3854Z" transform="translate(-201.25 -32.75)" fill="#d0cde1"/><path d="M543.2777,521.89228s16.68055,50.91958,2.63377,49.16373-20.19224-43.89619-20.19224-43.89619Z" transform="translate(-201.25 -32.75)" fill="#ffb8b8"/><path d="M498.50359,310.31267s-32.48318,7.02339-27.21563,50.91957,14.9247,87.79237,14.9247,87.79237l32.48318,71.11182,3.51169,13.16886,23.70394-6.14547L528.353,425.32067s-6.14547-108.86253-14.04678-112.37423A33.99966,33.99966,0,0,0,498.50359,310.31267Z" transform="translate(-201.25 -32.75)" fill="#d0cde1"/><polygon points="277.5 414.958 317.885 486.947 283.86 411.09 277.5 414.958" opacity="0.1"/><path d="M533.896,237.31585l.122-2.82012,5.6101,1.39632a6.26971,6.26971,0,0,0-2.5138-4.61513l5.97581-.33413a64.47667,64.47667,0,0,0-43.1245-26.65136c-12.92583-1.87346-27.31837.83756-36.182,10.43045-4.29926,4.653-7.00067,10.57018-8.92232,16.60685-3.53926,11.11821-4.26038,24.3719,3.11964,33.40938,7.5006,9.18513,20.602,10.98439,32.40592,12.12114,4.15328.4,8.50581.77216,12.35457-.83928a29.721,29.721,0,0,0-1.6539-13.03688,8.68665,8.68665,0,0,1-.87879-4.15246c.5247-3.51164,5.20884-4.39635,8.72762-3.9219s7.74984,1.20031,10.062-1.49432c1.59261-1.85609,1.49867-4.559,1.70967-6.99575C521.28248,239.785,533.83587,238.70653,533.896,237.31585Z" transform="translate(-201.25 -32.75)" fill="#2f2e41"/><circle cx="559" cy="744.5" r="43" fill="#6c63ff"/><circle cx="54" cy="729.5" r="43" fill="#6c63ff"/><circle cx="54" cy="672.5" r="31" fill="#6c63ff"/><circle cx="54" cy="624.5" r="22" fill="#6c63ff"/></svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Some files were not shown because too many files have changed in this diff Show More