Compact left column layout and require manual cast start.

Stop stretching input cards to match AI panel height, and show a start button before online/offline hexagram casting begins.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-12 06:42:02 +08:00
parent 98b83a5f75
commit 1cde9ffc9c
4 changed files with 71 additions and 14 deletions
+3 -5
View File
@@ -13,11 +13,9 @@ export function ModeWorkspace({
}) { }) {
return ( return (
<div className="py-6 lg:py-8"> <div className="py-6 lg:py-8">
<div className="mode-workspace-grid grid gap-6 lg:grid-cols-2 lg:items-stretch lg:gap-8 xl:gap-10"> <div className="mode-workspace-grid grid gap-6 lg:grid-cols-2 lg:items-start lg:gap-8 xl:gap-10">
<div className="grid h-full min-h-0 auto-rows-fr gap-5 lg:min-h-[calc(100vh-9rem)]"> <div className="flex flex-col gap-5">{children}</div>
{children} <aside className="flex min-h-[320px] flex-col lg:sticky lg:top-6 lg:max-h-[calc(100vh-7rem)]">
</div>
<aside className="flex min-h-[320px] flex-col lg:min-h-[calc(100vh-9rem)]">
<div className="zen-card flex min-h-0 flex-1 flex-col rounded-2xl border border-border/60 bg-card/90 shadow-md backdrop-blur-sm"> <div className="zen-card flex min-h-0 flex-1 flex-col rounded-2xl border border-border/60 bg-card/90 shadow-md backdrop-blur-sm">
<div className="flex items-center gap-2 border-b border-border/50 px-5 py-4"> <div className="flex items-center gap-2 border-b border-border/50 px-5 py-4">
<Sparkles size={18} className="text-primary/80" /> <Sparkles size={18} className="text-primary/80" />
+13 -2
View File
@@ -1,6 +1,6 @@
"use client"; "use client";
import { useState } from "react"; import { useEffect, useState } from "react";
import { Compass } from "lucide-react"; import { Compass } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
@@ -35,6 +35,7 @@ export default function CombinedForm() {
const [question, setQuestion] = useState(""); const [question, setQuestion] = useState("");
const [withHexagram, setWithHexagram] = useState(false); const [withHexagram, setWithHexagram] = useState(false);
const [castMode, setCastMode] = useState<"online" | "offline">("online"); const [castMode, setCastMode] = useState<"online" | "offline">("online");
const [castActive, setCastActive] = useState(false);
const [guaData, setGuaData] = useState<GuaResult | null>(null); const [guaData, setGuaData] = useState<GuaResult | null>(null);
const [chart, setChart] = useState<BaziChart | null>(null); const [chart, setChart] = useState<BaziChart | null>(null);
@@ -48,6 +49,11 @@ export default function CombinedForm() {
!withHexagram || !withHexagram ||
(question.trim() !== "" && currentLocation !== null && guaData !== null); (question.trim() !== "" && currentLocation !== null && guaData !== null);
useEffect(() => {
setCastActive(false);
setGuaData(null);
}, [currentProvince, currentCity, castMode, question, withHexagram]);
function validate(): string | null { function validate(): string | null {
if (!birthLocation) { if (!birthLocation) {
return "请选择出生地域"; return "请选择出生地域";
@@ -258,8 +264,13 @@ export default function CombinedForm() {
key={castMode} key={castMode}
mode={castMode} mode={castMode}
enabled={question.trim() !== "" && currentLocation !== null} enabled={question.trim() !== "" && currentLocation !== null}
active={castActive}
onStart={() => setCastActive(true)}
onResult={setGuaData} onResult={setGuaData}
onClear={() => setGuaData(null)} onClear={() => {
setGuaData(null);
setCastActive(false);
}}
/> />
{guaData && ( {guaData && (
<div className="rounded-lg border bg-card p-3"> <div className="rounded-lg border bg-card p-3">
+16 -3
View File
@@ -29,6 +29,7 @@ export default function LiuyaoForm() {
const [calcDate, setCalcDate] = useState(nowDateString); const [calcDate, setCalcDate] = useState(nowDateString);
const [calcTime, setCalcTime] = useState(nowTimeString); const [calcTime, setCalcTime] = useState(nowTimeString);
const [castMode, setCastMode] = useState<"online" | "offline">("online"); const [castMode, setCastMode] = useState<"online" | "offline">("online");
const [castActive, setCastActive] = useState(false);
const [guaData, setGuaData] = useState<GuaResult | null>(null); const [guaData, setGuaData] = useState<GuaResult | null>(null);
const [completion, setCompletion] = useState(""); const [completion, setCompletion] = useState("");
const [error, setError] = useState(""); const [error, setError] = useState("");
@@ -37,6 +38,11 @@ export default function LiuyaoForm() {
const location = useRegionLocation(provinceCode, cityCode); const location = useRegionLocation(provinceCode, cityCode);
const formReady = question.trim() !== "" && location !== null; const formReady = question.trim() !== "" && location !== null;
useEffect(() => {
setCastActive(false);
setGuaData(null);
}, [provinceCode, cityCode, castMode, question]);
function validate(): string | null { function validate(): string | null {
if (!question.trim()) { if (!question.trim()) {
return "请输入问事"; return "请输入问事";
@@ -92,6 +98,8 @@ export default function LiuyaoForm() {
setCityCode(""); setCityCode("");
setCalcDate(nowDateString()); setCalcDate(nowDateString());
setCalcTime(nowTimeString()); setCalcTime(nowTimeString());
setCastMode("online");
setCastActive(false);
setGuaData(null); setGuaData(null);
setCompletion(""); setCompletion("");
setError(""); setError("");
@@ -117,7 +125,7 @@ export default function LiuyaoForm() {
/> />
} }
> >
<ZenCard title="问事 · 心意" subtitle="所求何事,心诚则灵" className="flex h-full min-h-0 flex-col"> <ZenCard title="问事 · 心意" subtitle="所求何事,心诚则灵">
<Textarea <Textarea
placeholder="您想算点什么?" placeholder="您想算点什么?"
value={question} value={question}
@@ -139,7 +147,7 @@ export default function LiuyaoForm() {
</div> </div>
</ZenCard> </ZenCard>
<ZenCard title="起卦 · 时空" subtitle="地域、时辰与六爻" className="flex h-full min-h-0 flex-col"> <ZenCard title="起卦 · 时空" subtitle="地域、时辰与六爻">
<RegionSelect <RegionSelect
label="起卦地域" label="起卦地域"
provinceCode={provinceCode} provinceCode={provinceCode}
@@ -182,8 +190,13 @@ export default function LiuyaoForm() {
key={castMode} key={castMode}
mode={castMode} mode={castMode}
enabled={formReady} enabled={formReady}
active={castActive}
onStart={() => setCastActive(true)}
onResult={handleGuaResult} onResult={handleGuaResult}
onClear={() => setGuaData(null)} onClear={() => {
setGuaData(null);
setCastActive(false);
}}
/> />
{guaData && ( {guaData && (
<div className="rounded-xl border border-primary/20 bg-primary/5 p-4"> <div className="rounded-xl border border-primary/20 bg-primary/5 p-4">
+39 -4
View File
@@ -1,6 +1,7 @@
"use client"; "use client";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { Play } from "lucide-react";
import { bool } from "aimless.js"; import { bool } from "aimless.js";
import Coin from "@/components/coin"; import Coin from "@/components/coin";
import Hexagram from "@/components/hexagram"; import Hexagram from "@/components/hexagram";
@@ -18,6 +19,8 @@ const AUTO_DELAY = 800;
interface HexagramInputProps { interface HexagramInputProps {
mode: "online" | "offline"; mode: "online" | "offline";
enabled: boolean; enabled: boolean;
active: boolean;
onStart: () => void;
onResult: (data: GuaResult) => void; onResult: (data: GuaResult) => void;
onClear: () => void; onClear: () => void;
} }
@@ -25,6 +28,8 @@ interface HexagramInputProps {
export default function HexagramInput({ export default function HexagramInput({
mode, mode,
enabled, enabled,
active,
onStart,
onResult, onResult,
onClear, onClear,
}: HexagramInputProps) { }: HexagramInputProps) {
@@ -43,6 +48,14 @@ export default function HexagramInput({
setRotation(false); setRotation(false);
}, [mode]); }, [mode]);
useEffect(() => {
if (!active) {
setHexagramList([]);
setOfflineCounts(Array(6).fill(null));
setRotation(false);
}
}, [active]);
const finishList = useCallback( const finishList = useCallback(
(list: GuaResult["list"]) => { (list: GuaResult["list"]) => {
const result = computeGuaResult(list); const result = computeGuaResult(list);
@@ -76,20 +89,28 @@ export default function HexagramInput({
}, [frontList, finishList]); }, [frontList, finishList]);
const startOnlineCast = useCallback(() => { const startOnlineCast = useCallback(() => {
if (rotation || !enabled || hexagramList.length >= 6) { if (rotation || !enabled || !active || hexagramList.length >= 6) {
return; return;
} }
setFrontList([bool(), bool(), bool()]); setFrontList([bool(), bool(), bool()]);
setRotation(true); setRotation(true);
}, [rotation, enabled, hexagramList.length]); }, [rotation, enabled, active, hexagramList.length]);
useEffect(() => { useEffect(() => {
if (mode !== "online" || !enabled || rotation || complete) { if (mode !== "online" || !enabled || !active || rotation || complete) {
return; return;
} }
const timer = setTimeout(startOnlineCast, AUTO_DELAY); const timer = setTimeout(startOnlineCast, AUTO_DELAY);
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, [mode, enabled, rotation, complete, hexagramList.length, startOnlineCast]); }, [
mode,
enabled,
active,
rotation,
complete,
hexagramList.length,
startOnlineCast,
]);
function handleOfflineSelect(index: number, frontCount: number) { function handleOfflineSelect(index: number, frontCount: number) {
const next = [...offlineCounts]; const next = [...offlineCounts];
@@ -123,6 +144,20 @@ export default function HexagramInput({
); );
} }
if (!active) {
return (
<div className="flex flex-col items-center gap-3 py-1">
<p className="text-center text-sm text-muted-foreground">
</p>
<Button type="button" onClick={onStart}>
<Play size={16} className="mr-1" />
</Button>
</div>
);
}
return ( return (
<div className="flex w-full flex-col items-center gap-4"> <div className="flex w-full flex-col items-center gap-4">
{mode === "online" && ( {mode === "online" && (