feat: implement main window layout strategy
This commit is contained in:
@@ -206,7 +206,7 @@ image_task_results(
|
||||
- 第二窗口保持当前小窗定位,不参与最大化、尺寸恢复和主布局填充逻辑。
|
||||
- 后续增强再保存主窗口尺寸和最大化状态,下次启动恢复用户选择。
|
||||
|
||||
实现顺序:先落主窗口初始尺寸和布局填充,再落路由骨架;窗口尺寸持久化依赖设置 schema 后续实现。
|
||||
实现状态:主窗口初始尺寸、居中、可调整大小、根容器和主布局填充已落地;下一步是主应用路由骨架。窗口尺寸持久化依赖设置 schema 后续实现。
|
||||
|
||||
## 关键风险
|
||||
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| T-010 | 确定 Windows-first 窗口与布局策略 | T-002 | 主窗口默认不最大化,初始 `1280x800`,最小 `1024x720`,居中,可调整大小;页面根容器 `width: 100%`、`height: 100vh`;内部用 flex/grid 填满窗口;第二窗口保持小窗 | DONE |
|
||||
| T-011 | 实现主窗口启动参数与根布局策略 | T-010 | Go 侧设置主窗口初始宽高、居中、可调整大小;前端根容器和主布局填满窗口;阅读型内容有最大宽度,列表/仪表盘/设置页可自适应拉伸 | TODO |
|
||||
| T-011 | 实现主窗口启动参数与根布局策略 | T-010 | Go 侧设置主窗口初始宽高、居中、可调整大小;前端根容器和主布局填满窗口;阅读型内容有最大宽度,列表/仪表盘/设置页可自适应拉伸 | DONE |
|
||||
| T-012 | 搭建主应用路由骨架 | T-011 | `login`、`dashboard`、`image-optimizer`、`logs`、`statistics`、`settings`、`components` 路由可访问 | TODO |
|
||||
| T-013 | 实现登录 provider 边界 | T-012 | 支持本地固定账号 provider;预留 remote provider;账号密码不写死在前端;登录事件写审计日志 | TODO |
|
||||
| T-014 | 收敛设置模块 schema | T-012 | 主题、语言、窗口行为、快捷键、数据目录、日志策略都有统一配置定义和默认值 | TODO |
|
||||
|
||||
+14
-6
@@ -23,12 +23,12 @@
|
||||
- 前端 runtime:`@wailsio/runtime 3.0.0-alpha.66`
|
||||
- 前端框架:Vue 3、Vite、TypeScript、Tailwind CSS、Ant Design Vue
|
||||
- 本地数据库:SQLite,用户配置目录下 `cmbone/cmbone.db`
|
||||
- 当前主窗口路由:`/#/`
|
||||
- 当前主窗口路由:`/#/`,已保留为主应用入口,不再自动跳转到第二窗口。
|
||||
- 当前第二窗口路由:`/#/second`
|
||||
- 已确定主窗口策略:默认不最大化,初始 `1280x800`,最小 `1024x720`,居中,可调整大小。
|
||||
- 已确定页面布局策略:根容器 `width: 100%`、`height: 100vh`,内部 flex/grid 填满窗口。
|
||||
- 已确定内容区策略:阅读型内容保留最大宽度;列表、仪表盘、设置页自适应拉伸。
|
||||
- 已确定第二窗口策略:保持小窗,不参与主窗口最大化和尺寸恢复逻辑。
|
||||
- 已实现主窗口策略:默认不最大化,初始 `1280x800`,最小 `1024x720`,居中,可调整大小。
|
||||
- 已实现页面布局策略:根容器 `width: 100%`、`height: 100vh`,内部 flex/grid 填满窗口。
|
||||
- 已实现内容区策略:阅读型内容保留最大宽度;列表、仪表盘、设置页自适应拉伸。
|
||||
- 已保留第二窗口策略:保持小窗,不参与主窗口最大化和尺寸恢复逻辑。
|
||||
|
||||
## 当前命令
|
||||
|
||||
@@ -83,9 +83,17 @@ wails3 dev
|
||||
|
||||
同日完成可复用桌面应用模板定位校准后,已再次运行旧定位残留检查、`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 通过。
|
||||
|
||||
## 下一步
|
||||
|
||||
从 [`06-tasks.md`](06-tasks.md) 领取第一个 `TODO` 任务。目前建议先做 `T-011`:实现主窗口启动参数与根布局策略。
|
||||
从 [`06-tasks.md`](06-tasks.md) 领取第一个 `TODO` 任务。目前建议先做 `T-012`:搭建主应用路由骨架。
|
||||
|
||||
## 当前 blocker
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
| 产品定位 | B | 已校准为 Windows-first 可复用桌面应用模板,后续需要代码落地 |
|
||||
| 后端服务 | B | 现有服务边界清晰,登录、日志、统计和业务样例服务尚未实现 |
|
||||
| 本地存储 | B | SQLite 初始化和基础测试已存在,目标新增表需要迁移和测试 |
|
||||
| 前端结构 | B- | Vue 组件按功能拆分,窗口和布局策略已明确,但尚未代码落地为模板化主布局和目标路由 |
|
||||
| 前端结构 | B | Vue 组件按功能拆分,窗口和布局策略已落地,目标路由骨架尚未实现 |
|
||||
| 构建验证 | B | Go 测试、前端构建、后端编译可运行 |
|
||||
| 文档 | B+ | Harness Coding 文档已接入,并同步了模板愿景 |
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
## 改进方向
|
||||
|
||||
- 按已确定的 Windows-first 窗口和布局策略实现主窗口启动参数、根容器和主布局。
|
||||
- 搭建登录、Dashboard、图片优化、日志、统计、设置、组件示例等目标路由骨架。
|
||||
- 为登录、日志、图片任务和统计新增服务、模型、迁移和测试。
|
||||
- 为商品图片 AI 优化样例补充真实页面闭环。
|
||||
- 为 Wails bindings 生成流程补充完整操作说明。
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@
|
||||
- 顶部区域:当前用户、运行状态、快捷动作。
|
||||
- 内容区:路由页面,填满剩余空间。
|
||||
|
||||
Windows 10+ 是主要体验目标。主窗口策略已经确定:
|
||||
Windows 10+ 是主要体验目标。主窗口策略已经实现到当前代码:
|
||||
|
||||
- 默认不最大化。
|
||||
- 初始宽高:`1280x800`。
|
||||
|
||||
@@ -18,20 +18,21 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ConfigProvider :theme="themeConfig" >
|
||||
<ConfigProvider :theme="themeConfig">
|
||||
<div class="app-root">
|
||||
<router-view />
|
||||
</div>
|
||||
</ConfigProvider>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
html, body {
|
||||
margin: 0;
|
||||
overflow-x: hidden; /* 禁止横向滚动 */
|
||||
}
|
||||
/* 可选:全局背景适配 */
|
||||
body {
|
||||
@apply bg-white dark:bg-gray-900 text-black dark:text-white;
|
||||
.app-root {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.drag-region {
|
||||
-webkit-app-region: drag;
|
||||
--wails-draggable: drag;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class=" text-slate-800 font-sans dark:text-white">
|
||||
<div class="max-w-4xl mx-auto pl-6 pr-6">
|
||||
<div class="min-h-full w-full text-slate-800 font-sans dark:text-white">
|
||||
<div class="mx-auto w-full max-w-7xl px-6">
|
||||
<header class="flex items-center justify-between mb-6">
|
||||
<div class="relative w-2/3">
|
||||
<span class="absolute inset-y-0 left-0 pl-3 flex items-center text-slate-400">
|
||||
|
||||
@@ -9,7 +9,7 @@ const modelValue = ref(false)
|
||||
</script>
|
||||
|
||||
<template >
|
||||
<div >
|
||||
<div class="w-full">
|
||||
<section >
|
||||
<!-- 外观设置 -->
|
||||
<h2 class="text-lg font-bold text-gray-800 dark:text-white" >{{ $t('components.general.title.exterior') }}</h2>
|
||||
@@ -67,4 +67,4 @@ const modelValue = ref(false)
|
||||
font-size:13px;
|
||||
color:#999;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<div class="flex h-screen font-sans text-slate-900">
|
||||
<div class="flex h-screen w-full min-w-0 overflow-hidden font-sans text-slate-900 dark:text-white">
|
||||
<!-- 左侧请求栏 -->
|
||||
<aside
|
||||
class="w-44 border-r border-slate-200 bg-white flex flex-col dark:bg-gray-800 dark:text-white dark:border-slate-600"
|
||||
class="w-44 shrink-0 border-r border-slate-200 bg-white flex flex-col dark:bg-gray-800 dark:text-white dark:border-slate-600"
|
||||
:class="ismacos ? 'pt-10 ' : 'pt-8'">
|
||||
<nav class="flex-1 px-4 space-y-1 dark:bg-gray-800">
|
||||
<nav class="min-h-0 flex-1 overflow-y-auto px-4 space-y-1 dark:bg-gray-800">
|
||||
<div v-for="(item, index) in requests" :key="index" @click="handleMenu(item)"
|
||||
:class="[' flex items-center gap-3 px-3 py-2 text-indigo-700 cursor-pointer rounded-xl font-medium dark:text-white', selected === item.id ? 'bg-indigo-50 dark:bg-slate-500' : ' hover:bg-slate-50 text-slate-500 dark:hover:bg-slate-700']">
|
||||
<component :is="item.icon" />
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</nav>
|
||||
<div class="pl-4 pr-4 pt-2 pb-2 border-t border-slate-100 flex items-center gap-3 dark:border-slate-600 ">
|
||||
<div class="shrink-0 pl-4 pr-4 pt-2 pb-2 border-t border-slate-100 flex items-center gap-3 dark:border-slate-600 ">
|
||||
<img src="https://api.dicebear.com/7.x/avataaars/svg?seed=JinGong8"
|
||||
class="w-10 h-10 rounded-full bg-slate-200" />
|
||||
<div class="text-sm">
|
||||
@@ -21,8 +21,8 @@
|
||||
</div>
|
||||
</aside>
|
||||
<!-- 右侧整体 -->
|
||||
<div class="flex-1 flex flex-col bg-gray-50 dark:bg-gray-950">
|
||||
<div v-if="iswindows" class="titlebar drag-region pt-4 px-4 mb-2 text-white flex items-center justify-between">
|
||||
<div class="min-w-0 flex-1 flex flex-col bg-gray-50 dark:bg-gray-950">
|
||||
<div v-if="iswindows" class="titlebar drag-region shrink-0 pt-4 px-4 mb-2 text-white flex items-center justify-between">
|
||||
<div class="drag-area">
|
||||
</div>
|
||||
<div class="window-controls">
|
||||
@@ -39,8 +39,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- 主内容 -->
|
||||
<main class="flex-1 overflow-y-auto max-h-screen scroll-container scrollbar-thin" :class="iswindows ? 'px-4 pt-px pb-4' : 'p-4'">
|
||||
<component :is="getComponent" />
|
||||
<main class="min-h-0 w-full flex-1 overflow-y-auto scroll-container scrollbar-thin" :class="iswindows ? 'px-4 pt-px pb-4' : 'p-4'">
|
||||
<div class="min-h-full w-full">
|
||||
<component :is="getComponent" />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex h-full ">
|
||||
<div class="flex h-full w-full min-w-0 overflow-hidden">
|
||||
<!-- Left List -->
|
||||
<div class=" pl-1 pb-2 pr-1 flex flex-col" :class="currentTag === null ? 'w-3/5 ' : 'w-2/5'">
|
||||
<div class="min-w-0 pl-1 pb-2 pr-1 flex flex-col" :class="currentTag === null ? 'w-3/5 ' : 'w-2/5'">
|
||||
<div class="mr-2 ml-1">
|
||||
<h2 class="text-lg font-bold text-gray-800 dark:text-white float-left">
|
||||
{{ $t('components.tags.title.manage_tags') }}
|
||||
@@ -39,7 +39,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Right Editor -->
|
||||
<div class="flex w-3/5 pt-2 pb-6 bg-white rounded-lg dark:bg-gray-800">
|
||||
<div class="flex min-w-0 w-3/5 pt-2 pb-6 bg-white rounded-lg dark:bg-gray-800">
|
||||
<div v-if="currentTag" class="w-full">
|
||||
<div class="w-full mb-4 px-5 border-b gap-2 items-center dark:border-gray-700">
|
||||
<div class="float-right flex gap-2 pt-1 w-[20%] justify-end">
|
||||
@@ -381,4 +381,4 @@ function removeSubTag(index: number) {
|
||||
.tag.active {
|
||||
@apply bg-purple-100 text-purple-600;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -9,9 +9,6 @@ import router from './router/index'
|
||||
import { GetLanguage } from '../bindings/cmbone/internal/services/appconfigservice'
|
||||
|
||||
initTheme()
|
||||
if (window.location.hash === '' || window.location.hash === '#/') {
|
||||
router.replace('/second')
|
||||
}
|
||||
async function bootstrap(){
|
||||
await setupI18n('zh')//默认语言或从后端读取
|
||||
createApp(App).use(Antd).use(router).use(i18n).mount('#app')
|
||||
|
||||
@@ -69,7 +69,7 @@ async function handleOCR() {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex items-start justify-center pt-8 h-screen w-screen h-full w-full font-sans select-none bg-gray-100 drag-region dark:bg-gray-900">
|
||||
class="flex h-screen w-screen items-start justify-center overflow-hidden pt-8 font-sans select-none bg-gray-100 drag-region dark:bg-gray-900">
|
||||
<TransitionGroup name="card" tag="div">
|
||||
<div
|
||||
class="flex-1 w-[300px] rounded-2xl bg-white/80 dark:bg-gray-700 dark:text-white backdrop-blur-md px-4 py-2 shadow-[0_8px_24px_rgba(0,0,0,0.12)] transition">
|
||||
|
||||
+23
-1
@@ -1,3 +1,25 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@tailwind utilities;
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-white text-black dark:bg-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
.drag-region {
|
||||
-webkit-app-region: drag;
|
||||
--wails-draggable: drag;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,15 @@ type AppService struct {
|
||||
|
||||
const HotkeyShowSecondWindow = 1
|
||||
|
||||
const (
|
||||
mainWindowWidth = 1280
|
||||
mainWindowHeight = 800
|
||||
mainWindowMinWidth = 1024
|
||||
mainWindowMinHeight = 720
|
||||
secondWindowWidth = 340
|
||||
secondWindowHeight = 320
|
||||
)
|
||||
|
||||
func ConfigureAppService(a *AppService, app *application.App, i18nFS embed.FS, suidb *SuiStore) {
|
||||
a.App = app
|
||||
a.I18nFS = i18nFS
|
||||
@@ -37,8 +46,16 @@ func ConfigureAppService(a *AppService, app *application.App, i18nFS embed.FS, s
|
||||
|
||||
isWindows := runtime.GOOS == "windows"
|
||||
mainWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "main",
|
||||
Frameless: isWindows,
|
||||
Title: "main",
|
||||
Name: "main",
|
||||
Width: mainWindowWidth,
|
||||
Height: mainWindowHeight,
|
||||
MinWidth: mainWindowMinWidth,
|
||||
MinHeight: mainWindowMinHeight,
|
||||
InitialPosition: application.WindowCentered,
|
||||
StartState: application.WindowStateNormal,
|
||||
DisableResize: false,
|
||||
Frameless: isWindows,
|
||||
Mac: application.MacWindow{
|
||||
InvisibleTitleBarHeight: 50,
|
||||
Backdrop: application.MacBackdropTranslucent,
|
||||
@@ -53,8 +70,8 @@ func ConfigureAppService(a *AppService, app *application.App, i18nFS embed.FS, s
|
||||
secondWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "second",
|
||||
Name: "second",
|
||||
Width: 340,
|
||||
Height: 320,
|
||||
Width: secondWindowWidth,
|
||||
Height: secondWindowHeight,
|
||||
Mac: application.MacWindow{
|
||||
InvisibleTitleBarHeight: 50,
|
||||
TitleBar: application.MacTitleBarHidden,
|
||||
|
||||
+21
@@ -78,3 +78,24 @@
|
||||
- `npm install` 仍报告 3 个 moderate audit findings。
|
||||
- 前端构建仍提示 Browserslist 数据过期。
|
||||
- Vite 仍提示生产 chunk 大于 500 kB。
|
||||
|
||||
### 实现主窗口启动参数与根布局策略
|
||||
|
||||
- 任务:实现 `T-011`,把已确定的主窗口与布局策略落到代码。
|
||||
- 结果:
|
||||
- Go 侧主窗口设置为初始 `1280x800`、最小 `1024x720`、居中、可调整大小、正常窗口状态。
|
||||
- 第二窗口继续保持 `340x320` 小窗。
|
||||
- 前端 `html`、`body`、`#app` 和应用根容器撑满窗口并隐藏外层滚动。
|
||||
- 主窗口侧边栏、顶部栏和内容区使用 flex 填满窗口。
|
||||
- Dashboard、General、Tags 等现有页面补充了宽度和最小尺寸约束。
|
||||
- 根路由保留为主窗口入口,不再自动跳转到第二窗口。
|
||||
- 验证:
|
||||
- `.\init.ps1` 通过。
|
||||
- `go test ./...` 通过。
|
||||
- `cd frontend && npm run build` 通过。
|
||||
- `go build -o bin\cmbone.exe .` 通过。
|
||||
- `bin\cmbone.exe` 启动 6 秒 smoke 通过。
|
||||
- 非阻塞警告:
|
||||
- `npm install` 仍报告 3 个 moderate audit findings。
|
||||
- 前端构建仍提示 Browserslist 数据过期。
|
||||
- Vite 仍提示生产 chunk 大于 500 kB。
|
||||
|
||||
Reference in New Issue
Block a user