refactor(web): 液态玻璃视觉重构 + 全站中文化 - #9
Conversation
- Introduce glass design system (@Utility glass/glass-hover/glass-inset) - New shared components: Panel, CodeBlock, CopyButton, StatusDot, Banner, EmptyState, Logo - Redesign App shell: sticky glass header, purple gradient active nav indicator, logo glow - Redesign Dashboard: glass stat cards, enhanced traffic panel (total trend chart with gradient area + stat chips), gradient sparklines - Localize all UI copy to Chinese across 7 pages; add detailed field hints on Releases add-source form - Fix mobile overflow: add grid-cols-1 to responsive grids; code blocks wrap on small screens - Add AGENTS.md project conventions
📝 WalkthroughWalkthrough新增共享 UI 组件,统一全局视觉样式,并将登录、代理、镜像、发布、搜索和设置页面本地化为中文。新增开发指南和前端重构任务说明。 Changes前端界面重构
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
web/src/views/Dashboard.vue (1)
177-181: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win同步粒度选择和异步响应状态。
当请求失败时,选择器会显示新粒度,但图表仍使用旧数据。快速切换时,较早响应也可能覆盖较新响应。为请求分配递增 ID 或取消旧请求。仅在最新请求成功后同时提交粒度和数据。失败时保留旧状态并显示错误。
建议的最小修复
+let granularityRequestId = 0 + async function switchGranularity(level: 'hourly' | 'daily' | 'weekly') { - chartGranularity.value = level - const data = await getTraffic(undefined, undefined, level) - hourlyTraffic.value = Array.isArray(data) ? data : [] + const requestId = ++granularityRequestId + try { + const data = await getTraffic(undefined, undefined, level) + if (requestId !== granularityRequestId) return + hourlyTraffic.value = Array.isArray(data) ? data : [] + chartGranularity.value = level + } catch { + if (requestId === granularityRequestId) { + errorMsg.value = '流量数据加载失败' + } + } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/views/Dashboard.vue` around lines 177 - 181, 更新 switchGranularity,为每次请求分配递增请求 ID 或取消前一个请求,并仅允许最新请求成功后同时更新 chartGranularity 和 hourlyTraffic;请求失败或响应过期时保留现有粒度与图表数据,并通过现有错误提示机制报告失败。
🧹 Nitpick comments (6)
docs/frontend-refactor-prompt.md (1)
15-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winInclude all shared components in the documented scope.
Line 15 lists only
StatusCard.vueunderweb/src/components/. The refactor also addsBanner.vue,CodeBlock.vue,CopyButton.vue,EmptyState.vue,Logo.vue,Panel.vue, andStatusDot.vue. Replace the explicit list withweb/src/components/*.vueor enumerate these files.Proposed documentation update
-代码位置:`web/src/views/*.vue`(7 个页面)、`web/src/components/StatusCard.vue`、`web/src/style.css`、`web/src/api/client.ts`、`web/src/router/index.ts`、`web/src/App.vue` +代码位置:`web/src/views/*.vue`(7 个页面)、`web/src/components/*.vue`、`web/src/style.css`、`web/src/api/client.ts`、`web/src/router/index.ts`、`web/src/App.vue`🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/frontend-refactor-prompt.md` at line 15, Update the documented scope in the refactor prompt to cover all shared components under web/src/components, replacing the StatusCard.vue-only reference with web/src/components/*.vue. Keep the other listed paths unchanged.web/src/style.css (2)
9-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winNew
--brand-*/--accenttheme variables are defined but never reused; every consumer re-hardcodes the same literal colors. The shared root cause is thatweb/src/style.cssintroduces--brand-1,--brand-2,--brand-3,--brand-light, and--accentas the single source of truth for the brand palette, but no rule in either file references them — each rule repeats the equivalent hex/rgba literal instead. This defeats the purpose of centralizing the palette and risks future drift if one occurrence is updated and others are missed.
web/src/style.css#L9-L15: keep this as the single source of truth; convert the consumers below to reference these variables (withcolor-mix()or pre-defined rgba custom properties for the translucent variants) instead of repeating literals.web/src/style.css#L103-L108: replace the#bd8bff,#67e8f9`` gradient stops in.page-kickerwith `var(--brand-3)` and `var(--accent)`.web/src/style.css#L156-L164: replace thergba(124, 58, 237, ...),rgba(148, 85, 245, ...), andrgba(189, 139, 255, ...)values in.btn-brandwith derivations ofvar(--brand-1),var(--brand-2), andvar(--brand-3).web/src/style.css#L237-L240: replace thergba(124, 58, 237, 0.1)background in.tag-brandwith a derivation ofvar(--brand-1).web/src/App.vue#L86-L91: replace thergba(189, 139, 255, ...),#f2e4ff, andrgba(124, 58, 237, ...)values in.nav-link.activewith derivations ofvar(--brand-3),var(--brand-light), andvar(--brand-1).web/src/App.vue#L93-L103: replace thelinear-gradient(90deg,#7c3aed,#67e8f9)and matchingrgba(124, 58, 237, 0.8)shadow in.nav-link.active::afterwithvar(--brand-1)andvar(--accent).web/src/App.vue#L105-L107: replace the#bd8bffcolor withvar(--brand-3).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/style.css` around lines 9 - 15, Use the palette variables declared at web/src/style.css lines 9-15 as the single source of truth. Update web/src/style.css lines 103-108, 156-164, and 237-240 to reference --brand-1, --brand-2, --brand-3, and --accent, deriving translucent values with color-mix() or predefined rgba custom properties; update web/src/App.vue lines 86-91, 93-103, and 105-107 similarly for --brand-3, --brand-light, --brand-1, and --accent, removing repeated color literals while preserving the existing visual styling.
63-96: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winStylelint flags on
@utilityand nested&:hoverare false positives from an outdated syntax config.
@utilityis a valid Tailwind CSS v4 directive for CSS-first custom utilities. Thescss/at-rule-no-unknownandnesting-selector-no-missing-scoping-rootfindings at lines 63, 77, 80, and 90 come from a Stylelint config/syntax that does not recognize Tailwind v4 at-rules. If this runs in CI, it can produce noisy or blocking failures unrelated to real defects. Configure Stylelint with a Tailwind v4-aware syntax (for example, a PostCSS/Tailwind-aware customSyntax or an allowlist for@utility) so it stops flagging valid v4 syntax.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/style.css` around lines 63 - 96, Update the Stylelint configuration used for web/src/style.css to recognize Tailwind CSS v4 syntax, including the `@utility` at-rule and nested &:hover selectors. Use the project’s supported PostCSS/Tailwind-aware custom syntax or allowlist `@utility` so these valid declarations no longer produce CI-blocking false positives.Source: Linters/SAST tools
web/src/components/Banner.vue (1)
14-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd ARIA live-region semantics to the banner.
The banner div has no
roleoraria-live. Screen readers may not announce the message when it appears after an action (for example, a failed save). Addrole="alert"(oraria-live="assertive") for theerrortone, androle="status"(oraria-live="polite") forsuccess/info.♻️ Proposed fix
<div v-if="message || $slots.default" class="mb-4 flex items-start gap-2.5 rounded-md border px-3 py-2 text-sm" + role="status" + :aria-live="tone === 'error' ? 'assertive' : 'polite'" :class="{🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/components/Banner.vue` around lines 14 - 23, Update the banner div in the Banner component to expose live-region semantics based on tone: use alert/assertive behavior for error messages and status/polite behavior for success and info messages, while preserving the existing conditional rendering and styling.web/src/components/StatusDot.vue (1)
11-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the duplicated tone-to-color maps into a single computed value.
The pulse ring and the solid dot each embed their own
tone-keyed color map inline in:class. Adding or renaming a tone requires updating two maps and the prop type in sync.♻️ Proposed refactor
<script setup lang="ts"> +import { computed } from 'vue' + withDefaults(defineProps<{ tone?: 'ok' | 'warn' | 'danger' | 'off' | 'brand' | 'accent' pulse?: boolean }>(), { tone: 'off', pulse: false, }) + +const props = withDefaults(defineProps<{ ... }>(), { ... }) +const ringClass = computed(() => ({ + ok: 'bg-emerald-400/60', warn: 'bg-amber-400/60', danger: 'bg-red-400/60', + off: 'bg-slate-500/40', brand: 'bg-violet-400/60', accent: 'bg-cyan-400/60', +}[props.tone])) +const dotClass = computed(() => ({ + ok: 'bg-emerald-400 shadow-[0_0_8px_rgba(52,211,153,0.8)]', + warn: 'bg-amber-400 shadow-[0_0_8px_rgba(251,191,36,0.8)]', + danger: 'bg-red-400 shadow-[0_0_8px_rgba(248,113,113,0.8)]', + off: 'bg-slate-600 shadow-[0_0_6px_rgba(71,85,105,0.6)]', + brand: 'bg-violet-400 shadow-[0_0_8px_rgba(167,139,250,0.8)]', + accent: 'bg-cyan-400 shadow-[0_0_8px_rgba(34,211,238,0.8)]', +}[props.tone])) </script>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/components/StatusDot.vue` around lines 11 - 38, Extract the duplicated tone-to-color mappings used by the pulse ring and solid dot in StatusDot.vue into a single computed tone-class value. Update both :class bindings to consume that shared value while preserving each tone’s existing colors and pulse behavior.web/src/components/StatusCard.vue (1)
4-4: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReplace the
anyprop type with a properMirrorinterface.
mirror: anydiscards type checking for every field accessed in the template (name,status,enabled,pattern,upstream,error). Import or define aMirrortype (likely already used by the API client that feeds this data) and use it here instead.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/components/StatusCard.vue` at line 4, Replace the any type in StatusCard’s defineProps mirror declaration with the existing Mirror interface, importing it from the API client or defining it if unavailable. Ensure the type includes the template-accessed fields name, status, enabled, pattern, upstream, and error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/frontend-refactor-prompt.md`:
- Line 64: Update the visual-validation step around the local dev startup
command to provide a Windows-compatible PowerShell command, or explicitly state
that the existing nohup and /tmp/dev.log command must run in Git Bash. Keep the
requirement to run the dev server in the background, capture logs, perform
screenshot-based checks, and stop the process afterward.
- Around line 43-44: 更新设计方向条目 1 和 2,明确采用 liquid-glass
液态玻璃风格作为目标,同时保留暗色终端极客基调、统一组件视觉语言及紫青品牌色系统。删除或改写可能引导后续工作恢复旧视觉语言的表述,并确保文本与
AGENTS.md 及 PR 目标一致。
In `@web/src/components/CopyButton.vue`:
- Around line 18-32: Update the `copy` function to track whether the fallback
`document.execCommand('copy')` call succeeds, and only set `copied.value` to
true and start the reset timer when copying succeeds. Preserve the existing
primary clipboard path and textarea cleanup behavior.
In `@web/src/views/Dashboard.vue`:
- Around line 216-225: Update the peak-point rendering near the chart circle and
reuse bigChartPoints’s normalized source and coordinate calculations for both cx
and cy. Ensure the single-bucket case uses the same [0, value] fallback so cx
never becomes NaN, and place the circle at the actual peak coordinates instead
of the fixed cy value.
- Around line 142-153: Update the trendStats computed property to calculate
active mirror count from the complete, untruncated aggregation data rather than
trendRows. Keep slice(0, 8) limited to the table-rendering data so
trendStats.active reflects all active mirrors.
In `@web/src/views/Mirrors.vue`:
- Around line 109-111: 避免同一次编辑重复调用 updateUpstream:调整模板中 `@change` 和 `@keydown.enter`
的事件绑定,保留单一保存入口,或让 Enter 仅触发输入框失焦并由 change 统一执行保存,确保配置 API 每次编辑只收到一个请求。
In `@web/src/views/Search.vue`:
- Line 168: Update the EmptyState condition at line 168 in the Search.vue
component to use the normalized query value. Replace the direct `query`
reference in the v-if condition with `query.trim()` to ensure that queries
containing only whitespace are not treated as valid searches. This prevents the
"no results" message from appearing when the user enters only spaces, since
doSearch does not execute API requests for such inputs.
---
Outside diff comments:
In `@web/src/views/Dashboard.vue`:
- Around line 177-181: 更新 switchGranularity,为每次请求分配递增请求 ID
或取消前一个请求,并仅允许最新请求成功后同时更新 chartGranularity 和
hourlyTraffic;请求失败或响应过期时保留现有粒度与图表数据,并通过现有错误提示机制报告失败。
---
Nitpick comments:
In `@docs/frontend-refactor-prompt.md`:
- Line 15: Update the documented scope in the refactor prompt to cover all
shared components under web/src/components, replacing the StatusCard.vue-only
reference with web/src/components/*.vue. Keep the other listed paths unchanged.
In `@web/src/components/Banner.vue`:
- Around line 14-23: Update the banner div in the Banner component to expose
live-region semantics based on tone: use alert/assertive behavior for error
messages and status/polite behavior for success and info messages, while
preserving the existing conditional rendering and styling.
In `@web/src/components/StatusCard.vue`:
- Line 4: Replace the any type in StatusCard’s defineProps mirror declaration
with the existing Mirror interface, importing it from the API client or defining
it if unavailable. Ensure the type includes the template-accessed fields name,
status, enabled, pattern, upstream, and error.
In `@web/src/components/StatusDot.vue`:
- Around line 11-38: Extract the duplicated tone-to-color mappings used by the
pulse ring and solid dot in StatusDot.vue into a single computed tone-class
value. Update both :class bindings to consume that shared value while preserving
each tone’s existing colors and pulse behavior.
In `@web/src/style.css`:
- Around line 9-15: Use the palette variables declared at web/src/style.css
lines 9-15 as the single source of truth. Update web/src/style.css lines
103-108, 156-164, and 237-240 to reference --brand-1, --brand-2, --brand-3, and
--accent, deriving translucent values with color-mix() or predefined rgba custom
properties; update web/src/App.vue lines 86-91, 93-103, and 105-107 similarly
for --brand-3, --brand-light, --brand-1, and --accent, removing repeated color
literals while preserving the existing visual styling.
- Around line 63-96: Update the Stylelint configuration used for
web/src/style.css to recognize Tailwind CSS v4 syntax, including the `@utility`
at-rule and nested &:hover selectors. Use the project’s supported
PostCSS/Tailwind-aware custom syntax or allowlist `@utility` so these valid
declarations no longer produce CI-blocking false positives.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3292812f-bb43-4ffb-b857-ba457e102feb
📒 Files selected for processing (19)
AGENTS.mddocs/frontend-refactor-prompt.mdweb/src/App.vueweb/src/components/Banner.vueweb/src/components/CodeBlock.vueweb/src/components/CopyButton.vueweb/src/components/EmptyState.vueweb/src/components/Logo.vueweb/src/components/Panel.vueweb/src/components/StatusCard.vueweb/src/components/StatusDot.vueweb/src/style.cssweb/src/views/Dashboard.vueweb/src/views/GitProxy.vueweb/src/views/Login.vueweb/src/views/Mirrors.vueweb/src/views/Releases.vueweb/src/views/Search.vueweb/src/views/Settings.vue
- Fix granularity switch race: track latest request id, ignore stale responses - Fix traffic peak marker: compute cx/cy from data (no NaN on single bucket) - Count active mirrors from full data, not top-8 table rows - Localize remaining English fallback strings - CopyButton: only show copied state when fallback copy succeeds - Mirrors: Enter blurs input so change handles save once (no double request) - Search: trim query for empty-state conditions - Banner: add role/aria-live semantics - StatusDot: extract tone maps into computed values - StatusCard: replace any with Mirror interface - Use palette CSS vars (--brand-*/--accent) with color-mix in glass/buttons/nav - Remove docs/ plan file from tracking; update view.png preview - AGENTS.md/docs: document liquid-glass direction
变更概览
对前端做完整的 UI/UX 重构:暗色终端极客风 → 液态玻璃(liquid glass)风格,并全站中文化。
设计系统(web/src/style.css)
@utility玻璃表面:glass(半透明渐变 + 高光描边 + backdrop-blur)、glass-hover(hover 紫/青辉光)、glass-inset(内嵌命令卡)新通用组件(web/src/components/)
Panel(卡片容器)、CodeBlock(代码块+复制)、CopyButton、StatusDot(发光状态点)、Banner(错误/成功提示)、EmptyState、Logo(立方体 logo 抽取复用)
页面重构
Bug 修复
grid-cols-1(单列 auto 轨道被代码块 nowrap / 表格 min-w-max 撑宽的根因),375px 下已无横向滚动其他
docs/frontend-refactor-prompt.md(本次重构的任务提示词存档)验证
pnpm run build通过(vue-tsc 无类型错误)Summary by CodeRabbit