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:
@@ -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" />
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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" && (
|
||||||
|
|||||||
Reference in New Issue
Block a user