diff --git a/CHANGELOG.md b/CHANGELOG.md index c72daff1..7126a19c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ 版本号遵循 [SemVer](https://semver.org/lang/zh-CN/)。所有成员包(`apps/*`、`packages/*`)与根 `deno.json` 使用同一个版本号;`/api/v1/health` 与备份清单里的版本取自 `apps/server/deno.json`。 +## [Unreleased] + +### 调整 +- 顶栏搜索移进右侧玻璃胶囊(与通知、日夜切换、账户同一块),桌面是带 Ctrl K 提示的淡底胶囊,手机只留图标;不再是旁边单独一个黑边输入框。 +- 输入框、下拉选择改成淡玻璃:半透明底、细边,悬停时边线加深;聚焦和出错的光晕与红线保持清楚。 +- 分段选择统一换成滑动的玻璃滑块,不再有实心黑色的选中块:站点的「全部 / 已启用 / 需要处理」和「卡片 / 列表」、下载面板的「进行中 / 已完成」、订阅的类型与视图、文件管理的筛选、智能体会话列表的分栏与筛选、页面里的标签页(媒体库、日志、规则、运行状态的时间范围等)、站点凭证类型与 RSS 用途。 +- 按钮角色统一:页头与工具栏的次要操作一律是描边胶囊(整理记录的「批量」、订阅的「规则」「批量」、日志的「刷新」、下载盘的「调整阈值」),开关型按钮按下后显示选中外观;文件管理工具栏的图标按钮统一为描边圆钮,排序改用下拉选择。 +- 下载面板的总速度标上「总速度」,空闲时显示 0 B/s,不再是两道横线。 +- 分页:一页就放得下时只留总数,不显示翻页按钮;没有条目时不显示;窄卡片里按宽度收起首页 / 末页和「上一页 / 下一页」文字,不折成两行。 +- 智能体页:列表页和会话页共用同一个外壳,标题和左栏会话列表切换时不再消失、不重播动画,两栏正好占满一屏;会话列表顶部的分栏、搜索和筛选拉开间距。 +- 设置页左侧当前分区、整理记录顶部的状态计数、搜索结果的多选条件改用玻璃选中态。 + +### 修复 +- 运行状态 → 存储:缓存与数据目录在宽屏并排,「数据库里最大的 10 张表」「数据库语句统计」整行放在下面,不再留出大片空白的左栏。 +- 站点卡片底部的「⋯」不再单独掉到下一行。 + ## [0.3.39] - 2026-10-08 ### 修复 diff --git a/apps/web/app/app.css b/apps/web/app/app.css index 0e578197..38277442 100644 --- a/apps/web/app/app.css +++ b/apps/web/app/app.css @@ -392,14 +392,16 @@ --mat-lens-shadow: 0 6px 16px -10px rgba(20, 22, 28, 0.28); --mat-lens-blur: blur(2px) saturate(160%); /* field:输入框 / 下拉选择的触发器,与按钮同一套边光;底比按钮实一点,保证字和占位符好读 */ - /* 输入框是填充型:靠比卡片略深的底色区分,边线按 WCAG 非文本对比 3:1 给(压在白卡片上) */ - --mat-field-bg: rgba(20, 22, 28, 0.045); - --mat-field-edge: rgba(20, 22, 28, 0.46); - --mat-field-edge-focus: rgba(20, 22, 28, 0.78); + /* 输入框是淡玻璃:半透明白底 + 细边 + 上沿内高光(2026-10-08 调轻:0.46 的边在控制台的筛选条、搜索框里读成一圈黑框); + 聚焦 / 展开时边线加深并带光晕,出错时红线,这两种状态仍要一眼看得出 */ + --mat-field-bg: rgba(255, 255, 255, 0.5); + --mat-field-edge: rgba(20, 22, 28, 0.16); + --mat-field-edge-focus: rgba(20, 22, 28, 0.5); + --mat-field-edge-hover: rgba(20, 22, 28, 0.28); /* 登录页的表单卡片:实在的底,先把卡片和页面分开 */ --mat-auth-card-bg: rgba(255, 255, 255, 0.84); - --mat-field-ring: rgba(20, 22, 28, 0.06); - --mat-field-highlight: rgba(255, 255, 255, 0.08); + --mat-field-ring: rgba(20, 22, 28, 0.08); + --mat-field-highlight: rgba(255, 255, 255, 0.7); /* 小玻璃圆片的烟色玻璃:白色图标坐在上面。日间烟墨、夜间淡白;压在图片上的恒用 media 档 */ --mat-chip-glass: rgba(20, 22, 28, 0.56); /* media-hero:详情页头图。剧照铺在内容区顶部、渐隐到页面底色;左侧文字区压近乎不透明的底色,右侧露出剧照。 @@ -539,12 +541,14 @@ --mat-lens-edge: 0 0 0 1px rgba(0, 0, 0, 0.35); --mat-lens-sheen: inset 1px 1px 0 -0.5px rgba(255, 255, 255, 0.28); --mat-lens-shadow: 0 6px 16px -10px rgba(0, 0, 0, 0.55); - /* 夜间:底比卡片略亮,外线是浅色且对比 3:1(黑线压在深色卡片上看不见) */ - --mat-field-bg: rgba(255, 255, 255, 0.075); - --mat-field-edge: rgba(255, 255, 255, 0.36); - --mat-field-edge-focus: rgba(255, 255, 255, 0.78); + /* 夜间:底比卡片略亮,外线是浅色细线(黑线压在深色卡片上看不见) */ + --mat-field-bg: rgba(255, 255, 255, 0.05); + --mat-field-edge: rgba(255, 255, 255, 0.16); + --mat-field-edge-focus: rgba(255, 255, 255, 0.5); + --mat-field-edge-hover: rgba(255, 255, 255, 0.28); --mat-auth-card-bg: rgba(26, 27, 32, 0.74); - --mat-field-ring: rgba(255, 255, 255, 0.06); + --mat-field-ring: rgba(255, 255, 255, 0.07); + --mat-field-highlight: rgba(255, 255, 255, 0.04); --mat-chip-glass: rgba(255, 255, 255, 0.12); --mat-hero-image-opacity: 0.85; --mat-hero-scrim-side: linear-gradient(90deg, var(--background) 0%, color-mix(in oklab, var(--background) 94%, transparent) 38%, color-mix(in oklab, var(--background) 78%, transparent) 55%, color-mix(in oklab, var(--background) 36%, transparent) 72%, transparent 92%); @@ -1115,6 +1119,14 @@ background: var(--mat-lens-fill-hover); } + /* 开关型的次按钮(批量、按作品分组、显示隐藏文件)按下后与 pill-selected 同一外观:多一层填充、描边稍深 */ + .pill-secondary[aria-pressed="true"], + .pill-secondary[aria-current="page"] { + background: var(--mat-pill-selected-bg); + box-shadow: var(--mat-lens-rim), 0 0 0 1px var(--mat-pill-selected-border); + color: var(--content-strong); + } + /* 主操作(方案 A,2026-09-26):和其他按钮一样是半透明胶囊,不做实心;靠沿边框缓慢流转的 一整圈棱镜光谱突出,内侧再透一层很淡的同色光。门户 .pill-primary 与流媒体 .btn-glass-primary 共用;减少动态效果时停转,只留彩色边框 */ @@ -1434,9 +1446,14 @@ box-shadow: inset 0 1px 0 var(--mat-field-highlight); transition: box-shadow var(--duration-base) var(--ease-standard), + border-color var(--duration-base) var(--ease-standard), background-color var(--duration-base) var(--ease-standard); } + .field-glass:hover:not(:focus-within, [data-state="open"], [aria-invalid="true"], :disabled) { + border-color: var(--mat-field-edge-hover); + } + .field-glass:focus-visible, .field-glass:focus-within, .field-glass[data-state="open"] { @@ -1446,7 +1463,8 @@ } .field-glass[aria-invalid="true"] { - border-color: color-mix(in oklch, var(--destructive) 65%, transparent); + border-color: color-mix(in oklch, var(--destructive) 70%, transparent); + box-shadow: 0 0 0 3px color-mix(in oklch, var(--destructive) 12%, transparent); } /* 登录类页面的表单卡片:内容卡的配方,但底换成实在的磨砂(夜间内容卡几乎透明,卡片、输入框、页面三层挤在一起分不开) */ diff --git a/apps/web/app/components/agent/agent-session-list.tsx b/apps/web/app/components/agent/agent-session-list.tsx index b0436f33..039abf80 100644 --- a/apps/web/app/components/agent/agent-session-list.tsx +++ b/apps/web/app/components/agent/agent-session-list.tsx @@ -1,4 +1,4 @@ -import { Bot, ChevronLeft, ChevronRight } from "lucide-react"; +import { Bot, ChevronLeft, ChevronRight, Search } from "lucide-react"; import { type ReactNode, useEffect, useLayoutEffect, useRef, useState } from "react"; import { Link } from "react-router"; import { type AgentRunGroup, type AgentRunView, TRIGGER_LABEL, useAgentRuns } from "~/api/agent.ts"; @@ -8,6 +8,7 @@ import { useListPagination } from "~/components/console/use-list-pagination.ts"; import { Button } from "~/components/ui/button.tsx"; import { Input } from "~/components/ui/input.tsx"; import type { PaginationProps } from "~/components/ui/pagination.tsx"; +import { SegmentedControl } from "~/components/ui/segmented.tsx"; import { Tabs, TabsList, TabsTrigger } from "~/components/ui/tabs.tsx"; import { relTime } from "~/components/workbench/release-fields.ts"; import { cn } from "~/lib/utils.ts"; @@ -323,40 +324,38 @@ export function AgentSessionList({ const rowProps = { activeId, hrefFor, onOpen }; return ( + ); } diff --git a/apps/web/app/components/ui/segmented.tsx b/apps/web/app/components/ui/segmented.tsx new file mode 100644 index 00000000..4a8ba558 --- /dev/null +++ b/apps/web/app/components/ui/segmented.tsx @@ -0,0 +1,138 @@ +import { type ReactNode, type RefObject, useLayoutEffect, useRef, useState } from "react"; + +import { cn } from "~/lib/utils.ts"; + +type Thumb = { x: number; y: number; w: number; h: number }; + +/** + * 分段控件的滑块:量出容器里当前选中项(`selector`)的位置写进 style,选中项变化、尺寸变化时跟着滑过去。 + * 第一次量到之前不画,量到后才挂过渡,避免首屏从左上角滑进来。与 uhd_web SegmentedTabs、控制台侧栏同一做法。 + */ +export function useSlidingThumb(container: RefObject, selector: string, deps: unknown[] = []) { + const [thumb, setThumb] = useState(null); + const [animate, setAnimate] = useState(false); + useLayoutEffect(() => { + const el = container.current; + if (!el) return; + const measure = () => { + const active = el.querySelector(selector); + setThumb((prev) => { + if (!active) return null; + const next = { x: active.offsetLeft, y: active.offsetTop, w: active.offsetWidth, h: active.offsetHeight }; + return prev && prev.x === next.x && prev.y === next.y && prev.w === next.w && prev.h === next.h ? prev : next; + }); + }; + measure(); + const raf = requestAnimationFrame(() => setAnimate(true)); + const ro = new ResizeObserver(measure); + ro.observe(el); + for (const child of Array.from(el.children)) ro.observe(child); + // radix Tabs 的选中态写在 data-state 上,不一定引起本组件重渲染 + const mo = new MutationObserver(measure); + mo.observe(el, { subtree: true, attributes: true, attributeFilter: ["data-state", "aria-checked"] }); + return () => { + cancelAnimationFrame(raf); + ro.disconnect(); + mo.disconnect(); + }; + }, [container, selector, ...deps]); + return { thumb, animate }; +} + +/** 滑块本体(`seg-thumb` 玻璃),放在相对定位的容器里、选项之前 */ +export function SegThumb({ thumb, animate, className }: { thumb: Thumb | null; animate: boolean; className?: string }) { + if (!thumb) return null; + return ( + + ); +} + +export type SegmentedOption = { + value: V; + label: ReactNode; + icon?: ReactNode; + /** 跟在标签后面的数量,弱一档显示 */ + count?: ReactNode; + /** 只有图标时给读屏与悬停提示 */ + title?: string; + disabled?: boolean; +}; + +/** + * 单选分段控件:筛选(全部 / 已启用)、视图(卡片 / 列表)、分组(进行中 / 已完成)这类互斥选项。 + * 选中项由一枚滑动的 `seg-thumb` 承担,文字提一档;没有实心选中块。多选的筛选条件用 `pill-selected` 胶囊,不用它。 + */ +export function SegmentedControl({ + value, + onChange, + options, + label, + size = "sm", + className, + fill = false, +}: { + value: V; + onChange: (v: V) => void; + options: SegmentedOption[]; + /** 读屏用的组名 */ + label: string; + size?: "xs" | "sm"; + className?: string; + /** 铺满容器宽度,选项等分 */ + fill?: boolean; +}) { + const ref = useRef(null); + const { thumb, animate } = useSlidingThumb(ref, '[aria-checked="true"]', [value, options.length]); + return ( +
+ + {options.map((o) => { + const on = o.value === value; + return ( + + ); + })} +
+ ); +} diff --git a/apps/web/app/components/ui/tabs.tsx b/apps/web/app/components/ui/tabs.tsx index 3bf9aad3..a3509bb7 100644 --- a/apps/web/app/components/ui/tabs.tsx +++ b/apps/web/app/components/ui/tabs.tsx @@ -1,20 +1,28 @@ import { Tabs as TabsPrimitive } from "radix-ui"; import type * as React from "react"; +import { useRef } from "react"; +import { SegThumb, useSlidingThumb } from "~/components/ui/segmented.tsx"; import { cn } from "~/lib/utils.ts"; -// 分段标签:列表横向可滚(scroll-bleed 保留描边不被裁),选中态用静态 pill-selected +// 分段标签:列表横向可滚(scroll-bleed 保留滑块的投影与描边不被裁),选中项由一枚滑动的 seg-thumb 承担(同 SegmentedControl) function Tabs({ className, ...props }: React.ComponentProps) { return ; } -function TabsList({ className, ...props }: React.ComponentProps) { +function TabsList({ className, children, ...props }: React.ComponentProps) { + const ref = useRef(null); + const { thumb, animate } = useSlidingThumb(ref, '[data-slot="tabs-trigger"][data-state="active"]'); return ( + > + + {children} + ); } @@ -23,7 +31,7 @@ function TabsTrigger({ className, ...props }: React.ComponentProps(); return ( - <> -

智能体会话

-
- -
- + { + showAgentRunInDrawer(runId); + navigate(agentReturnPath() ?? "/console"); + }} + > + - { - showAgentRunInDrawer(runId); - navigate(agentReturnPath() ?? "/console"); - }} - > - - - } - /> -
-
- + } + /> + ); } diff --git a/apps/web/app/routes/console/agent._index.tsx b/apps/web/app/routes/console/agent._index.tsx new file mode 100644 index 00000000..1444da60 --- /dev/null +++ b/apps/web/app/routes/console/agent._index.tsx @@ -0,0 +1,35 @@ +import { useNavigate } from "react-router"; +import { toast } from "sonner"; +import { agentMockEnabled, useCreateAgentRun } from "~/api/agent.ts"; +import { agentRunHref, DRAFT_PAGE_GOAL, writeDraft } from "~/components/agent/agent-nav.ts"; +import { GoalComposer } from "~/components/agent/agent-run-panel.tsx"; +import { quickGoalsFor } from "~/components/agent/quick-goals.ts"; +import { errorMessage } from "~/lib/api.ts"; + +// 智能体列表页的右栏:新对话输入框 + 3 个快捷目标(标题与左栏会话列表在外壳 agent.tsx) +export default function AgentIndexPage() { + const create = useCreateAgentRun(); + const navigate = useNavigate(); + const mock = agentMockEnabled(); + return ( +
+ + create.mutate( + { goal, ...opts }, + { + onSuccess: (run) => { + writeDraft(DRAFT_PAGE_GOAL, ""); + navigate(agentRunHref(run.id, mock)); + }, + onError: (e) => toast.error(errorMessage(e)), + }, + ) + } + /> +
+ ); +} diff --git a/apps/web/app/routes/console/agent.tsx b/apps/web/app/routes/console/agent.tsx index d498e7bf..54f92338 100644 --- a/apps/web/app/routes/console/agent.tsx +++ b/apps/web/app/routes/console/agent.tsx @@ -1,31 +1,27 @@ import { FlaskConical } from "lucide-react"; -import { useNavigate } from "react-router"; -import { toast } from "sonner"; -import { agentMockEnabled, useCreateAgentRun } from "~/api/agent.ts"; -import { - agentRunHref, - DRAFT_PAGE_GOAL, - pageListMemory, - usePageListGroup, - writeDraft, -} from "~/components/agent/agent-nav.ts"; -import { GoalComposer } from "~/components/agent/agent-run-panel.tsx"; +import { Outlet, useParams } from "react-router"; +import { agentMockEnabled } from "~/api/agent.ts"; +import { agentRunHref, pageListMemory, usePageListGroup, useRunGroup } from "~/components/agent/agent-nav.ts"; import { AgentSessionList } from "~/components/agent/agent-session-list.tsx"; -import { quickGoalsFor } from "~/components/agent/quick-goals.ts"; import { PageHeader } from "~/components/console/page-header.tsx"; import { Button } from "~/components/ui/button.tsx"; -import { errorMessage } from "~/lib/api.ts"; +import { cn } from "~/lib/utils.ts"; -// 智能体入口页(AGUI-3):左栏会话列表(等你处理置顶),右边新对话输入框 + 3 个快捷目标。 -export default function AgentPage() { - const create = useCreateAgentRun(); - const navigate = useNavigate(); +/** + * 智能体页的外壳(AGUI-3):列表页与会话页共用。标题、演示提示与左栏会话列表在这里,只挂载一次; + * 右栏是子路由(新对话输入框 / 某个会话)。切换会话、回到列表都不重挂载左栏,也不重播入场动画, + * 标题与两栏的位置不跳。 + */ +export default function AgentLayout() { + const { runId } = useParams(); const mock = agentMockEnabled(); - const [group, setGroup] = usePageListGroup(); + const runGroupOf = useRunGroup(runId); + // 列表页没有 tab 就是「对话」;会话页的左栏分栏跟随当前会话(巡检会话及其子会话在「自动巡检」),也可以手动切换(记在 URL) + const [group, setGroup] = usePageListGroup(runId ? runGroupOf : undefined); return ( <> - + {mock ? (
@@ -37,37 +33,31 @@ export default function AgentPage() { ) : null}
-
+ {/* 窄屏:列表页把会话列表排在输入框下面;会话页不显示列表(顶部有「全部会话」返回) */} +
-
- - create.mutate( - { goal, ...opts }, - { - onSuccess: (run) => { - writeDraft(DRAFT_PAGE_GOAL, ""); - navigate(agentRunHref(run.id, mock)); - }, - onError: (e) => toast.error(errorMessage(e)), - }, - ) - } - /> + +
+
); } + +export type AgentOutletContext = { group: ReturnType[0] }; diff --git a/apps/web/app/routes/console/files.tsx b/apps/web/app/routes/console/files.tsx index b92faf95..cb5c0e26 100644 --- a/apps/web/app/routes/console/files.tsx +++ b/apps/web/app/routes/console/files.tsx @@ -97,6 +97,8 @@ import { type OrganizeSource, OrganizeWizard } from "~/components/organize-wizar import { Button } from "~/components/ui/button.tsx"; import { Input } from "~/components/ui/input.tsx"; import { Pagination } from "~/components/ui/pagination.tsx"; +import { SegmentedControl } from "~/components/ui/segmented.tsx"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select.tsx"; import { Sheet, SheetContent, SheetHeader, SheetTitle } from "~/components/ui/sheet.tsx"; import { relTime } from "~/components/workbench/release-fields.ts"; import { useSse } from "~/lib/sse.ts"; @@ -797,23 +799,14 @@ export default function FilesPage() { // ── 工具栏 ── const chips = ( -
- {FILTER_CHIPS.map((c) => ( - - ))} +
+ patch({ f: v === "all" ? null : v })} + options={FILTER_CHIPS.map((c) => ({ value: c.value || "all", label: c.label }))} + />
); const searchBox = ( @@ -838,21 +831,21 @@ export default function FilesPage() {
); const sortControls = ( -
- +
+ @@ -1007,7 +999,7 @@ export default function FilesPage() { )} - ))} -
- ); -} diff --git a/apps/web/app/routes/console/subscriptions.tsx b/apps/web/app/routes/console/subscriptions.tsx index 9590b83f..2140ee44 100644 --- a/apps/web/app/routes/console/subscriptions.tsx +++ b/apps/web/app/routes/console/subscriptions.tsx @@ -32,6 +32,7 @@ import { SubscriptionCard, SubscriptionRow } from "~/components/subscriptions/ov import { SubscriptionFormDialog } from "~/components/subscriptions/subscription-form.tsx"; import { Button } from "~/components/ui/button.tsx"; import { Pagination } from "~/components/ui/pagination.tsx"; +import { SegmentedControl } from "~/components/ui/segmented.tsx"; import { cn } from "~/lib/utils.ts"; import { legacyRedirectLoader } from "./redirects.ts"; @@ -74,27 +75,13 @@ function Pills({ items: { value: T | ""; label: string; count?: number }[]; }) { return ( -
- {items.map((it) => ( - - ))} +
+ + label={label} + value={value || "all"} + onChange={(v) => onChange(v === "all" ? "" : v)} + options={items.map((it) => ({ value: it.value || "all", label: it.label, count: it.count }))} + />
); } @@ -207,7 +194,7 @@ export default function SubscriptionsPage() { title="订阅" actions={ <> - )} -
- - - -
+ (x === "calendar" ? f.set("view", "calendar") : setView(x))} + options={[ + { value: "card", label: "", title: "卡片视图", icon: }, + { value: "list", label: "", title: "列表视图", icon: }, + { value: "calendar", label: "", title: "日历", icon: }, + ]} + />
{calendar ? ( @@ -459,7 +425,7 @@ export default function SubscriptionsPage() { ) : ( renderItems(visible) )} - {items.length > 0 || query.hasNextPage ? : null} + {items.length > 0 || query.hasNextPage ? : null} )} diff --git a/docs/design/README.md b/docs/design/README.md index 994aa5b5..2fbc564f 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -23,3 +23,4 @@ - `EmptyState`:空态/建设中占位;`plain` 用于卡片内部。 - `StatusBadge`:`success | warning | destructive | neutral` 四种语气,必须带文字。 - `nav.ts`:12 个工作区的导航表,标题与说明同时供占位页使用。 +- `SegmentedControl`(`components/ui/segmented.tsx`):单选分段,滑动的 `seg-thumb`;用法见 [ui-controls.md](ui-controls.md)「本项目(控制台)的落地」。 diff --git a/docs/design/interface-materials.md b/docs/design/interface-materials.md index 3739279a..6b77e570 100644 --- a/docs/design/interface-materials.md +++ b/docs/design/interface-materials.md @@ -123,9 +123,9 @@ - 侧栏的选中项由滑块(`seg-thumb`)承担主要形态,导航文字与 hover 保持清晰;分组展开、滚动和路由变化都会影响滑块位置。 - 控制台侧栏贴边,不做浮起卡片;右边不画硬分隔线,由 `pg-chrome-side` 给内侧细亮边、外侧细暗线和淡投影。分组小标题(概览、获取、整理、系统)保留:它们和菜单项不同名,是导航的分区。折叠开关在标志行:展开时在名称右侧,收起后标志悬停变为展开图标。 -- 控制台顶栏与登录页顶栏不铺磨砂(磨砂栏会成为背景根,里面的玻璃糊不到页面),用页面底色渐隐托住标题;控制台右侧通知、智能体、日夜切换与账户收进一枚玻璃胶囊,胶囊里的按钮是平的(`bare`);登录页右侧只有日夜切换一颗,不套胶囊。 +- 控制台顶栏与登录页顶栏不铺磨砂(磨砂栏会成为背景根,里面的玻璃糊不到页面),用页面底色渐隐托住标题;控制台右侧搜索、通知、智能体、日夜切换与账户收进一枚玻璃胶囊,胶囊里的按钮是平的(`bare`),搜索入口是胶囊里的一格平涂淡底,不套 `.field-glass`;登录页右侧只有日夜切换一颗,不套胶囊。 - 登录页只留标题(初始化和二次验证保留一句操作说明),整页居中一张卡,桌面与手机同一套;卡片是 `pg-slab auth-card`(底换成实在的磨砂 `--mat-auth-card-bg`,卡片、输入框、页面三层分得开)。 -- 输入框、多行输入与下拉选择的触发器统一用 `.field-glass`(淡底 + 1px 实边,聚焦 / 展开时外线加深并带淡光晕,出错时红线;参数 `--mat-field-*`),由 `Input` / `Textarea` / `SelectTrigger` 挂上;原生 `` 与长得像下拉的按钮也挂它。调用处只给尺寸和内边距,不再各写底色、边框、阴影。 - 改支付界面的样式时,要覆盖确认、二维码、错误与返回路径。套餐的优惠金额从价格推导,不写死。交易摘要用现有的 RemarkText 展示,不为排版改写后端摘要的含义。 主动使用 Taste 的触发条件和项目适配见前端规范。 diff --git a/docs/design/ui-controls.md b/docs/design/ui-controls.md index 1dd45c49..dba431ec 100644 --- a/docs/design/ui-controls.md +++ b/docs/design/ui-controls.md @@ -20,6 +20,15 @@ 两边的底、边、字色、投影都引用 `--mat-lens-*` / `--mat-pill-*`,改外观只改参数。弹窗底部的按钮行间距 12px(`gap-3`):按钮带外侧细线与光谱高光,8px 会挤在一起。放进玻璃胶囊里的按钮(控制台顶栏的通知、日夜切换、账户)用 `ghost`,不再自带玻璃。调用处只给布局(宽度、高度、间距、定位),不改圆角、底色、边框、投影和字色;不是 `