Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71c6f22902 | ||
|
|
c77c808606 | ||
|
|
3f01e9a083 | ||
|
|
21953fed2a | ||
|
|
0bc023f3f9 | ||
|
|
bda9c57843 | ||
|
|
b8323550da | ||
|
|
9137699935 | ||
|
|
07357c4766 | ||
|
|
7fbcd3a838 | ||
|
|
76cda6b49c | ||
|
|
357747dd17 | ||
|
|
2c31a41036 | ||
|
|
769c861eb0 | ||
|
|
3bd05b316e | ||
|
|
7942add8b6 | ||
|
|
91debb9ad6 | ||
|
|
5aaac0b166 | ||
|
|
811a9c889f | ||
|
|
4c1de99d65 | ||
|
|
462ff5ac4c | ||
|
|
7c31554f64 | ||
|
|
a429353c42 | ||
|
|
9472f005c8 | ||
|
|
47d3c653c6 | ||
|
|
d2eeb049d0 | ||
|
|
e5d10577c0 |
@@ -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*
|
||||||
|
|||||||
@@ -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
|
||||||
|
```
|
||||||
|
|
||||||
|
脚本会安装前端依赖、运行后端测试、构建前端、编译后端,并打印开发启动命令。
|
||||||
@@ -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`
|
||||||
|
|
||||||
|
不要绕过这些文件直接修改代码。
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
|
```
|
||||||
|
|||||||
@@ -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}}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
@cd /d "%~dp0" && for /f "delims=" %%G in ('go env GOPATH') do @set "PATH=%%G\bin;%PATH%" && wails3 build
|
||||||
@@ -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 .
|
||||||
|
After Width: | Height: | Size: 130 KiB |
@@ -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
|
||||||
@@ -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}}'
|
||||||
@@ -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}}'
|
||||||
@@ -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"
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#!/bin/bash
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#!/bin/bash
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#!/bin/bash
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#!/bin/bash
|
||||||
@@ -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'
|
||||||
|
After Width: | Height: | Size: 21 KiB |
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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>
|
||||||
@@ -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
|
||||||
@@ -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 .
|
||||||
|
```
|
||||||
@@ -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 边界。
|
||||||
|
- 提交历史能看出每次改动的意图和验证结果。
|
||||||
@@ -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`、本地数据库或用户配置。
|
||||||
|
- 依赖升级必须单独开任务。
|
||||||
@@ -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 或图表后端。
|
||||||
@@ -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,警告不等于失败,但需要避免无节制增加前端体积。
|
||||||
@@ -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 或删除用户未要求删除的文件。
|
||||||
|
- 不把依赖升级和功能改动混在同一个任务里。
|
||||||
@@ -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 的接口形态。
|
||||||
|
- 评估本地数据库备份、恢复和清理策略。
|
||||||
@@ -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) 记录历史执行流水和验证证据。
|
||||||
@@ -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` 只包含本任务文件。
|
||||||
@@ -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` 写清原因和验证结果。
|
||||||
@@ -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 结果。
|
||||||
@@ -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
|
||||||
|
|
||||||
|
无。
|
||||||
@@ -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. 操作日志接入商品草稿创建、上架、下架、图片优化关联等审计事件。
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# 评估标准
|
||||||
|
|
||||||
|
用于 code review、交接和 AI 输出自检。
|
||||||
|
|
||||||
|
## 必须满足
|
||||||
|
|
||||||
|
- 能说明本次改动对应哪个任务。
|
||||||
|
- 没有超出任务范围。
|
||||||
|
- 版本、命令、路径和当前代码事实一致。
|
||||||
|
- 相关构建或测试命令已运行并记录结果。
|
||||||
|
- 没有提交构建产物、依赖目录或本地配置。
|
||||||
|
|
||||||
|
## 代码质量
|
||||||
|
|
||||||
|
| 等级 | 标准 |
|
||||||
|
| --- | --- |
|
||||||
|
| A | 改动小、边界清楚、测试充分、文档同步、无明显维护风险 |
|
||||||
|
| B | 功能正确、构建通过、文档基本同步,仍有少量后续优化空间 |
|
||||||
|
| C | 可运行但边界或错误处理薄弱,需要安排后续任务 |
|
||||||
|
| D | 构建失败、范围失控、接口不一致或缺少关键验证 |
|
||||||
|
|
||||||
|
## UI 质量
|
||||||
|
|
||||||
|
| 等级 | 标准 |
|
||||||
|
| --- | --- |
|
||||||
|
| A | 布局稳定、信息密度合适、窗口尺寸变化时无溢出 |
|
||||||
|
| B | 主要路径可用,局部样式可继续优化 |
|
||||||
|
| C | 功能可用但有明显布局问题或交互不清 |
|
||||||
|
| D | 关键控件不可用、文本重叠或页面无法加载 |
|
||||||
|
|
||||||
|
## 文档质量
|
||||||
|
|
||||||
|
| 等级 | 标准 |
|
||||||
|
| --- | --- |
|
||||||
|
| A | 文档能直接指导新维护者执行任务和验证 |
|
||||||
|
| B | 主要事实正确,少量细节需要补充 |
|
||||||
|
| C | 有导航但缺少真实命令或验收标准 |
|
||||||
|
| D | 文档与代码冲突,可能误导维护者 |
|
||||||
@@ -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/` | 常用组件示例 |
|
||||||
@@ -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 生成流程补充完整操作说明。
|
||||||
@@ -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/`。
|
||||||
@@ -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。
|
||||||
@@ -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`:受阻,需要明确原因。
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
id: T-000
|
||||||
|
title: 任务标题
|
||||||
|
status: TODO
|
||||||
|
depends_on: []
|
||||||
|
owner: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
# 任务标题
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
说明为什么要做这个任务。
|
||||||
|
|
||||||
|
## 范围
|
||||||
|
|
||||||
|
- 做什么。
|
||||||
|
- 不做什么。
|
||||||
|
|
||||||
|
## 验收标准
|
||||||
|
|
||||||
|
- [ ] 可观察、可执行的验收点。
|
||||||
|
- [ ] 相关测试或构建命令通过。
|
||||||
|
|
||||||
|
## 执行记录
|
||||||
|
|
||||||
|
尚未开始。
|
||||||
@@ -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 作为验收路径 |
|
||||||
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 185 KiB |
|
After Width: | Height: | Size: 217 KiB |
|
After Width: | Height: | Size: 216 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 155 KiB |
|
After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 155 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 215 KiB |
|
After Width: | Height: | Size: 214 KiB |
@@ -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.
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 16 KiB |
@@ -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 |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 29 KiB |
@@ -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 |
|
After Width: | Height: | Size: 8.8 KiB |