fix(ui): 空态居中、大运页去重、星历加日历、顶栏动作按 46px 收
真机走查四条: 1. 空态不居中——问候+输入框+pill 整组贴在屏幕底部。加一个可压缩的 配重行,让 chat-panel 变成 46px/1fr/auto/1fr,两个 1fr 抵消, 整组居中;composer 仍是同一个 DOM 位置,不重挂载。 2. 大运页「Vimshottari」出现两遍、「当前 当前 Chara 段…」两个当前。 两条轨道的 method 与自身 title 同字符串,method 只在不同时才渲染; 兜底文案自带的「当前」去掉——它同时被参数表当「当前大运」的值用, 不能把前缀塞进数据。 3. 星历只能一天一天点。日期改成可点的日历弹层,复用出生日期字段 同一套 Calendar+Popover。新增 parseEphemerisDate/toEphemerisDate, 都走本地日历字段、不经 UTC,避免跨时区差一天。 4. 「生成完整报告」在 46px 顶栏里是 44px 实心大按钮,像个横幅 CTA。 顶栏作用域内动作统一 32px + outline,触摸目标靠 ::after 扩热区补。 测试 3372,fail 仍 31 且与基线逐条一致;四个路由标记不变; CSS gzip 40,080。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
This commit is contained in:
co-authored by
Claude Opus 5
parent
d548bd0554
commit
49bc4a8ba1
@@ -12,8 +12,12 @@ function Track({
|
||||
<section className="chart-page-dasha-track">
|
||||
<header>
|
||||
<h3>{title}</h3>
|
||||
<p>{method}</p>
|
||||
<p>当前 {currentLabel}</p>
|
||||
{/* Both tracks ship a `method` equal to their own title, which rendered
|
||||
the name twice; only show it when it actually adds something. */}
|
||||
{method && method !== title ? <p>{method}</p> : null}
|
||||
{/* No space, and no second 当前: the fallback labels used to start with
|
||||
it themselves, which read as 「当前 当前 Chara 段…」. */}
|
||||
<p>当前{currentLabel}</p>
|
||||
</header>
|
||||
<ol className="chart-page-dasha-list">
|
||||
{periods.map((period) => (
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
import Link from "next/link";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import { zhCN } from "date-fns/locale";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { SecondaryShell } from "@/components/secondary-shell";
|
||||
import { setComposerDraft } from "@/lib/composer-draft";
|
||||
import { parseEphemerisOkResponse, type EphemerisOkResponse } from "@/lib/ephemeris-contract";
|
||||
@@ -15,6 +19,8 @@ import {
|
||||
formatHouse,
|
||||
qualityLabel,
|
||||
shiftEphemerisDate,
|
||||
parseEphemerisDate,
|
||||
toEphemerisDate,
|
||||
} from "@/lib/ephemeris-view";
|
||||
|
||||
type PageState =
|
||||
@@ -94,6 +100,7 @@ export function EphemerisPage() {
|
||||
onPrevious={() => activeDate && changeDate(shiftEphemerisDate(activeDate, -1))}
|
||||
onNext={() => activeDate && changeDate(shiftEphemerisDate(activeDate, 1))}
|
||||
onToday={() => today && changeDate(today)}
|
||||
onSelectDate={(iso) => changeDate(iso)}
|
||||
onAsk={askAboutDate}
|
||||
/>
|
||||
);
|
||||
@@ -106,6 +113,7 @@ export function EphemerisView(props: {
|
||||
readonly onPrevious: () => void;
|
||||
readonly onNext: () => void;
|
||||
readonly onToday: () => void;
|
||||
readonly onSelectDate: (iso: string) => void;
|
||||
readonly onAsk: () => void;
|
||||
}) {
|
||||
const factors = props.payload?.panchanga.available ? props.payload.panchanga.factors : [];
|
||||
@@ -114,6 +122,8 @@ export function EphemerisView(props: {
|
||||
const eventsUnavailable = !props.payload || props.payload.events.status === "unavailable";
|
||||
const events = props.payload?.events.status === "ok" ? props.payload.events.items : [];
|
||||
const isToday = Boolean(props.date) && props.date === props.today;
|
||||
const [pickerOpen, setPickerOpen] = useState(false);
|
||||
const selectedDay = props.date ? parseEphemerisDate(props.date) : undefined;
|
||||
|
||||
return (
|
||||
<SecondaryShell
|
||||
@@ -142,7 +152,41 @@ export function EphemerisView(props: {
|
||||
>
|
||||
<span aria-hidden="true">‹</span>
|
||||
</Button>
|
||||
<p className="ephemeris-date-label">{props.date ? formatEphemerisDate(props.date) : ""}</p>
|
||||
{/* The date itself opens a month grid. Stepping a day at a time was the
|
||||
only way to reach another date, so anything more than a few days
|
||||
away meant repeated clicking. Same Calendar + Popover the birth
|
||||
date field uses. */}
|
||||
<Popover open={pickerOpen} onOpenChange={setPickerOpen}>
|
||||
<PopoverTrigger
|
||||
render={<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
className="ephemeris-date-label"
|
||||
aria-label={`${props.date ? formatEphemerisDate(props.date) : ""},选择日期`}
|
||||
/>}
|
||||
>
|
||||
<CalendarIcon aria-hidden="true" />
|
||||
<span>{props.date ? formatEphemerisDate(props.date) : ""}</span>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="center" className="w-auto p-0">
|
||||
<Calendar
|
||||
mode="single"
|
||||
locale={zhCN}
|
||||
className="[--cell-size:2.5rem]"
|
||||
selected={selectedDay}
|
||||
defaultMonth={selectedDay}
|
||||
captionLayout="dropdown"
|
||||
navLayout="after"
|
||||
startMonth={new Date(1900, 0)}
|
||||
endMonth={new Date(2100, 11)}
|
||||
onSelect={(next) => {
|
||||
if (next === undefined) return;
|
||||
setPickerOpen(false);
|
||||
props.onSelectDate(toEphemerisDate(next));
|
||||
}}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
|
||||
@@ -192,15 +192,20 @@ export function GeneratePersonalReportButton({
|
||||
}
|
||||
|
||||
return (
|
||||
/* Sized for the 46px page header it now lives in. It used to be the
|
||||
default filled Button at full height, which read as a banner CTA
|
||||
floating in the top-right corner rather than the header's own action. */
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="shrink-0 whitespace-nowrap"
|
||||
onClick={() => void handleGenerate()}
|
||||
disabled={submitting}
|
||||
title="根据已保存的具体出生分钟生成完整报告;未校正会标明方向性参考"
|
||||
>
|
||||
<FileText aria-hidden="true" />
|
||||
{submitting ? "正在创建…" : "生成完整报告"}
|
||||
{submitting ? "正在创建…" : "生成报告"}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user