Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions docs/review/layout-20261104/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# 界面对齐修复

基于 `main` 的 `fd3fd0d4cf6f082d25fbab44bfe674dee2621609`,保留现有经典/玻璃外观及功能。

| 问题 | 修复 |
| --- | --- |
| 总览外层 page 和内部 home 重复添加边距,顶栏标题另用固定边距 | 由 page 统一提供内容宽度和边距,顶栏使用同一边界 |
| 首页各行列宽分别为 4 等分、1.6:1、1.3:1:1 | 统一为四列网格对应的整列、半宽与四分之一宽;根据实际内容宽度切换双列/单列 |
| 卡片 height:100%、自身底部 margin、行间距叠加 | 由网格负责等高和行间距;内容保持自然高度 |
| 手机侧栏被后置 position:relative 覆盖,正文出现在导航下方 | 恢复手机 fixed 抽屉定位;桌面相对定位放回基础规则 |
| 手机表单标签固定 150px,输入框只剩约 70px | 窄屏/窄内容区标签与字段上下排列;320px 下普通设置输入框为 256px |
| AI 补丁使用不存在的 .ai-panel/.ai-drawer,1100px 下正文被压到412px | 使用实际 .ai-host.panel 与共享宽度变量;1400px 起并排,其余覆盖显示 |
| 操作单元格右对齐被通用 table td 规则覆盖 | 提高操作列规则优先级,表头采用统一 ops 标记;CDN 桌面保持 table-cell |
| 320px 下统计页分段切换和待处理操作条溢出 | 分段控件内部滚动,操作条换行 |
| 紧凑模式的间距与列宽计算不一致 | 通过共享 layout-gap 同步计算;紧凑表格 padding 只应用于桌面 |
| Baize 双列内容面板在手机上被基础规则覆盖 | 窄容器内明确改为单列 |
| 按钮与筛选输入框/下拉框高度不同 | 公共控件使用 32px 高度;登录等专用大输入框保留原规则 |

## 验证

运行:安装 Playwright 后执行 `node scripts/layout_smoke.mjs /path/to/output`。
可用 `PLAYWRIGHT_MODULE` 指向已安装的 Playwright `index.mjs`。

- Chromium:1920、1440、1280、1100、1024、768、720、390、320px。
- 经典浅色,以及玻璃+紧凑+深色,共 306 个页面状态。
- 16 个主页面:总览、服务器、网站、访问统计、监控、待处理、证书、云服务器、CDN、解析、存储、终端、文件、记录、设置、AI 助手;另检查总览打开 AI 侧栏。
- 断言页面和正文不横向溢出、桌面标题与正文边距一致、同排首页卡片等高、首页共享列线、服务器表头与数据列一致、设置输入框宽度、手机菜单打开/关闭、AI 侧栏关闭。
- JavaScript 语法检查、`git diff --check`、布局检测器通过。
- 全部为本地模拟数据:首页包含长名称、长待办和 5 条变更;其余页面主要覆盖未配置/空数据状态;文件页覆盖明确的离线错误状态。没有连接生产云账号或服务器,没有验证真实服务端操作和所有弹窗。Go 后端未修改,未在本机运行完整 Go CI。

## 截图

桌面修复前:

![修复前](before-desktop.png)

桌面修复后:

![修复后](after-desktop.png)

手机修复后:

![手机](after-mobile.png)
Binary file added docs/review/layout-20261104/after-desktop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/review/layout-20261104/after-mobile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/review/layout-20261104/before-desktop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 8 additions & 8 deletions internal/webui/static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2324,7 +2324,7 @@ const DnsPage = {
</div>
<div class="table-wrap">
<table class="table dns-table">
<thead><tr><th>主机记录</th><th>类型</th><th>线路</th><th>记录值</th><th>TTL</th><th>状态</th><th><span class="sr-only">操作</span></th></tr></thead>
<thead><tr><th>主机记录</th><th>类型</th><th>线路</th><th>记录值</th><th>TTL</th><th>状态</th><th class="ops"><span class="sr-only">操作</span></th></tr></thead>
<tbody>
<tr v-for="r in shown" :key="r.rid || r.id" :class="{ off: !r.enabled }">
<td class="c-name"><div class="dns-name">{{ r.name }}</div><div class="small tertiary">{{ r.full }}</div></td>
Expand Down Expand Up @@ -2679,7 +2679,7 @@ const HomePage = {
<header class="card-head"><h3>服务器</h3></header>
<div class="srv-grid srv-head small tertiary"><span>名称</span><span>CPU</span><span>内存</span><span>磁盘</span></div>
<button class="srv-grid srv-row" v-for="s in ov.servers" :key="s.id" @click="$emit('server', s.id)">
<span class="srv-name"><span class="sdot" :class="levelDot(s.level)"></span><span><b>{{ s.name }}</b><span class="small tertiary block">{{ s.level === 'unknown' ? s.note : ({ '1panel': '1Panel', bt: '宝塔', linux: 'Linux' }[s.adapter] || '') }}</span></span></span>
<span class="srv-name"><span class="sdot" :class="levelDot(s.level)"></span><span><b :title="s.name">{{ s.name }}</b><span class="small tertiary block">{{ s.level === 'unknown' ? s.note : ({ '1panel': '1Panel', bt: '宝塔', linux: 'Linux' }[s.adapter] || '') }}</span></span></span>
<span v-for="(m, k) in [s.cpuPct, s.memPct, s.diskPct]" :key="k" class="srv-metric">
<template v-if="m != null"><span class="mini-bar"><span :class="bar(m).cls" :style="{ width: bar(m).w }"></span></span><span :class="{ 'warn-text': m >= 85 }">{{ m }}%</span></template>
<span class="tertiary" v-else>—</span>
Expand Down Expand Up @@ -2999,7 +2999,7 @@ const ServerApps = {
<div class="row secondary" v-else-if="!containers.length">没有容器</div>
<div class="table-wrap" v-else>
<table class="table">
<thead><tr><th>容器</th><th>镜像</th><th>状态</th><th class="num">内存</th><th class="num">CPU</th><th><span class="sr-only">操作</span></th></tr></thead>
<thead><tr><th>容器</th><th>镜像</th><th>状态</th><th class="num">内存</th><th class="num">CPU</th><th class="ops"><span class="sr-only">操作</span></th></tr></thead>
<tbody><tr v-for="c in containers" :key="c.name"><td>{{ c.name }}<div class="small tertiary" v-if="c.ports">{{ c.ports }}</div></td><td class="secondary">{{ c.image }}</td>
<td class="nowrap"><span class="sdot" :class="c.running ? 'good' : 'off'"></span>{{ c.status }}</td>
<td class="num nowrap">{{ c.mem ? c.mem.split(' / ')[0] : '—' }}</td><td class="num">{{ c.cpu || '—' }}</td>
Expand Down Expand Up @@ -3408,7 +3408,7 @@ const CloudPage = {
<div class="row secondary" v-else-if="!detail.firewall.length">没有入站规则</div>
<div class="table-wrap" v-else>
<table class="table">
<thead><tr><th>端口</th><th>协议</th><th>来源</th><th>策略</th><th>备注</th><th><span class="sr-only">操作</span></th></tr></thead>
<thead><tr><th>端口</th><th>协议</th><th>来源</th><th>策略</th><th>备注</th><th class="ops"><span class="sr-only">操作</span></th></tr></thead>
<tbody><tr v-for="(r, i) in detail.firewall" :key="i">
<td class="mono">{{ r.port }}</td><td>{{ r.protocol }}</td>
<td>{{ r.everyone ? '所有人' : r.source }}<span class="tertiary small cloud-cidr" v-if="r.everyone">{{ r.source }}</span></td>
Expand Down Expand Up @@ -3579,7 +3579,7 @@ const CdnPage = {
<div class="group" v-if="domains.length">
<div class="table-wrap">
<table class="table cdn-table">
<thead><tr><th>加速域名</th><th>状态</th><th>CNAME</th><th>源站</th><th>HTTPS</th><th><span class="sr-only">操作</span></th></tr></thead>
<thead><tr><th>加速域名</th><th>状态</th><th>CNAME</th><th>源站</th><th>HTTPS</th><th class="ops"><span class="sr-only">操作</span></th></tr></thead>
<tbody>
<tr v-for="d in domains" :key="d.provider + d.domain">
<td><div class="site-name">{{ d.domain }}</div><div class="small tertiary"><template v-if="both">{{ CLOUD_NAMES[d.provider] }} · </template>{{ CDN_TYPE[d.type] || d.type }}<template v-if="CDN_AREA[d.area]"> · {{ CDN_AREA[d.area] }}</template></div></td>
Expand Down Expand Up @@ -3812,7 +3812,7 @@ const MonitorPage = {
<div class="row secondary" v-if="!sites.length">{{ v.listed ? '没有找到网站。在「设置」里手动添加要监控的地址,或者在服务器上用 1Panel 建站。' : '正在整理要监控的网站,一分钟内出结果……' }}</div>
<div class="table-wrap" v-else>
<table class="table mon-table">
<thead><tr><th>网站</th><th>现在</th><th class="num">响应</th><th class="num">24 小时可用率</th><th>最近一小时</th><th><span class="sr-only">操作</span></th></tr></thead>
<thead><tr><th>网站</th><th>现在</th><th class="num">响应</th><th class="num">24 小时可用率</th><th>最近一小时</th><th class="ops"><span class="sr-only">操作</span></th></tr></thead>
<tbody>
<tr v-for="s in sites" :key="s.url">
<td><div class="site-name">{{ s.name }}</div><div class="small tertiary ellipsis">{{ s.source }}</div></td>
Expand Down Expand Up @@ -4460,7 +4460,7 @@ const SitePage = {
<div class="group site-group" v-else>
<div class="table-wrap">
<table class="table site-table">
<thead><tr><th>网站</th><th>类型</th><th>状态</th><th>HTTPS</th><th><span class="sr-only">操作</span></th></tr></thead>
<thead><tr><th>网站</th><th>类型</th><th>状态</th><th>HTTPS</th><th class="ops"><span class="sr-only">操作</span></th></tr></thead>
<tbody>
<tr v-for="x in s.shown" :key="x.id" class="site-row" @click="openSite(s.id, x.id)">
<td><div class="site-name">{{ x.domain }}</div><div class="small tertiary" v-if="x.remark || x.app || x.runtime">{{ [x.remark, x.app, x.runtime].filter(Boolean).join(' · ') }}</div></td>
Expand Down Expand Up @@ -5357,7 +5357,7 @@ const StoragePage = {
<table class="table cos-table">
<thead><tr>
<th class="ck"><input type="checkbox" :checked="allOn" :indeterminate="picked.length > 0 && !allOn" @change="toggleAll" aria-label="全选"></th>
<th>名称</th><th class="num">大小</th><th class="hide-sm">修改时间</th><th class="hide-sm">存储类型</th><th><span class="sr-only">操作</span></th></tr></thead>
<th>名称</th><th class="num">大小</th><th class="hide-sm">修改时间</th><th class="hide-sm">存储类型</th><th class="ops"><span class="sr-only">操作</span></th></tr></thead>
<tbody>
<tr v-for="e in shown" :key="e.key" :class="{ on: pickSet.has(e.key) }" @click="clickRow(e, $event)">
<td class="ck" @click.stop><input type="checkbox" :checked="pickSet.has(e.key)" @change="togglePick(e)" :aria-label="'选择 ' + e.name"></td>
Expand Down
Loading