guanwei 1.1.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/dist/assets/index-CRUHghuF.js +102 -0
- package/dist/assets/index-CRUHghuF.js.map +1 -0
- package/dist/assets/index-hLV3S-Cj.css +1 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/scripts/release.sh +15 -0
- package/server/src/data/db.json +189 -0
- package/server/src/data/guanwei.db +0 -0
- package/server/src/index.ts +1 -1
- package/server/src/services/duanyu.ts +67 -0
- package/server/src/services/promptBuilder.ts +5 -0
- package/shared/core/data/duanyu.ts +15 -14
- package/shared/core/data/ganzhi.ts +1 -1
- package/shared/core/data/gua64.ts +1 -1
- package/shared/core/data/liuren.ts +1 -1
- package/shared/core/data/qimen.ts +1 -1
- package/shared/core/data/xiaoliuren.ts +1 -1
- package/shared/core/data/ziwei.ts +1 -1
- package/shared/core/data/zodiac.ts +1 -1
- package/shared/core/engine/bazi.ts +3 -1
- package/shared/core/engine/calendar.ts +11 -2
- package/shared/core/engine/liuren.ts +218 -6
- package/shared/core/engine/qimen.ts +30 -8
- package/shared/core/types.ts +3 -0
- package/src/App.tsx +1 -1
- package/src/components/arts/LiurenArt.tsx +2 -1
- package/src/data/arts.ts +1 -1
- package/src/index.css +2 -74
- package/src/pages/DemoPage.tsx +66 -21
- package/tailwind.config.js +0 -35
- package/dist/assets/index-C3wrtpPY.css +0 -1
- package/dist/assets/index-CAyAIgw8.js +0 -102
- package/dist/assets/index-CAyAIgw8.js.map +0 -1
- package/src/components/Navigation.tsx +0 -80
- package/src/components/TarotCard.tsx +0 -129
- package/src/pages/AstrologyPage.tsx +0 -33
- package/src/pages/BaZiPage.tsx +0 -33
- package/src/pages/Home.tsx +0 -179
- package/src/pages/SpreadEditor.tsx +0 -230
- package/src/pages/SpreadLibrary.tsx +0 -199
- package/src/pages/SpreadLibraryPage.tsx +0 -17
- package/src/pages/TarotPage.tsx +0 -736
- package/src/pages/ZiWeiPage.tsx +0 -33
|
@@ -62,6 +62,8 @@ export function baziCalc(input: BaziInput): BaziResult {
|
|
|
62
62
|
const min = 0;
|
|
63
63
|
|
|
64
64
|
// 2. 年柱(立春为界)
|
|
65
|
+
// 注意:getJieQiTableExact(yy) 返回的是「农历年」节气表,冬至后的立春可能落在公历次年
|
|
66
|
+
// (如 2023 表中含 2024-02-04 立春)——取立春发生的公历年份定年柱,不能用表格年份 yy
|
|
65
67
|
const t = new Date(y, m - 1, d, hour, min).getTime();
|
|
66
68
|
let lichunBest = -Infinity;
|
|
67
69
|
let lichunYear = y - 1;
|
|
@@ -69,7 +71,7 @@ export function baziCalc(input: BaziInput): BaziResult {
|
|
|
69
71
|
for (const jq of getJieQiTableExact(yy)) {
|
|
70
72
|
if (jq.name === '立春') {
|
|
71
73
|
const tt = jq.time.getTime();
|
|
72
|
-
if (tt <= t && tt > lichunBest) { lichunBest = tt; lichunYear =
|
|
74
|
+
if (tt <= t && tt > lichunBest) { lichunBest = tt; lichunYear = jq.time.getFullYear(); }
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
77
|
}
|
|
@@ -27,14 +27,23 @@ export function getJieqiApproxName(_y: number, m: number, d: number): string {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// 精确节气时刻表(某年全年 24 节气)
|
|
30
|
+
// 注:lunar-typescript 的 getJieQiTable key 混用中英文(当年冬至→次年惊蛰段为英文拼音),此处统一映射为中文
|
|
30
31
|
export interface JieQiTime { name: string; time: Date }
|
|
32
|
+
const JQ_KEY_CN: Record<string, string> = {
|
|
33
|
+
LI_CHUN: '立春', YU_SHUI: '雨水', JING_ZHE: '惊蛰', CHUN_FEN: '春分',
|
|
34
|
+
QING_MING: '清明', GU_YU: '谷雨', LI_XIA: '立夏', XIAO_MAN: '小满',
|
|
35
|
+
MANG_ZHONG: '芒种', XIA_ZHI: '夏至', XIAO_SHU: '小暑', DA_SHU: '大暑',
|
|
36
|
+
LI_QIU: '立秋', CHU_SHU: '处暑', BAI_LU: '白露', QIU_FEN: '秋分',
|
|
37
|
+
HAN_LU: '寒露', SHUANG_JIANG: '霜降', LI_DONG: '立冬', XIAO_XUE: '小雪',
|
|
38
|
+
DA_XUE: '大雪', DONG_ZHI: '冬至', XIAO_HAN: '小寒', DA_HAN: '大寒',
|
|
39
|
+
};
|
|
31
40
|
export function getJieQiTableExact(year: number): JieQiTime[] {
|
|
32
41
|
const solar = Solar.fromYmdHms(year, 6, 1, 12, 0, 0);
|
|
33
42
|
const table = solar.getLunar().getJieQiTable();
|
|
34
43
|
const out: JieQiTime[] = [];
|
|
35
44
|
for (const key of Object.keys(table)) {
|
|
36
45
|
const s = table[key];
|
|
37
|
-
out.push({ name: key, time: new Date(s.getYear(), s.getMonth() - 1, s.getDay(), s.getHour(), s.getMinute(), s.getSecond()) });
|
|
46
|
+
out.push({ name: JQ_KEY_CN[key] || key, time: new Date(s.getYear(), s.getMonth() - 1, s.getDay(), s.getHour(), s.getMinute(), s.getSecond()) });
|
|
38
47
|
}
|
|
39
48
|
return out;
|
|
40
49
|
}
|
|
@@ -58,7 +67,7 @@ export function currentJieqiNameExact(y: number, m: number, d: number): string {
|
|
|
58
67
|
return best ? best.name : '大雪';
|
|
59
68
|
}
|
|
60
69
|
|
|
61
|
-
// 十二「节」(定月支用)与月支映射(0=寅 … 11
|
|
70
|
+
// 十二「节」(定月支用)与月支映射(0=寅 … 11=丑)
|
|
62
71
|
export const JIE_BRANCH: Record<string, number> = {
|
|
63
72
|
小寒: 11, 立春: 0, 惊蛰: 1, 清明: 2, 立夏: 3, 芒种: 4,
|
|
64
73
|
小暑: 5, 立秋: 6, 白露: 7, 寒露: 8, 立冬: 9, 大雪: 10,
|
|
@@ -1,9 +1,220 @@
|
|
|
1
|
-
// 大六壬:月将加时(中气定将)·
|
|
2
|
-
|
|
1
|
+
// 大六壬:月将加时(中气定将)· 四课三传(九宗门真实起法)
|
|
2
|
+
// 九宗门:贼克(元首/始入/重审/知一)→ 比用 → 涉害 → 遥克(蒿矢/弹射)→ 昴星(虎视/冬蛇掩目)→ 别责 → 八专 → 返吟(取驿马)→ 伏吟(取刑)
|
|
3
|
+
import { GAN, ZHI, WUXING, mod } from '../data/ganzhi';
|
|
3
4
|
import { LR_JIANGS, LR_JIANG_SUN, LR_GANJI } from '../data/liuren';
|
|
4
5
|
import { daysSince, currentJieqiNameExact } from './calendar';
|
|
5
6
|
import type { LiurenResult } from '../types';
|
|
6
7
|
|
|
8
|
+
// ─── 九宗门辅助表 ───
|
|
9
|
+
// 五行生克:克我者(谁克我)——五行相克序:木→土→水→火→金→木
|
|
10
|
+
const WX_KE: Record<string, string> = { 木: '土', 土: '水', 水: '火', 火: '金', 金: '木' };
|
|
11
|
+
|
|
12
|
+
function ke(wx1: string, wx2: string): boolean {
|
|
13
|
+
// wx1 克 wx2?
|
|
14
|
+
return WX_KE[wx1] === wx2;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 地支阴阳:子(0)阳 丑(1)阴 寅(2)阳 … 亥(11)阴
|
|
18
|
+
const zhiYang = (z: string) => ZHI.indexOf(z as any) % 2 === 0;
|
|
19
|
+
|
|
20
|
+
// 干阴阳:甲丙戊庚壬 阳;乙丁己辛癸 阴
|
|
21
|
+
const ganYang = (g: string) => GAN.indexOf(g as any) % 2 === 0;
|
|
22
|
+
|
|
23
|
+
// 刑:子刑卯、卯刑子;寅刑巳、巳刑申、申刑寅;丑刑戌、戌刑未、未刑丑;辰午酉亥自刑
|
|
24
|
+
const XING: Record<string, string> = { 子: '卯', 卯: '子', 寅: '巳', 巳: '申', 申: '寅', 丑: '戌', 戌: '未', 未: '丑' };
|
|
25
|
+
const ZIXING = ['辰', '午', '酉', '亥'];
|
|
26
|
+
|
|
27
|
+
// 冲:子午、丑未、寅申、卯酉、辰戌、巳亥
|
|
28
|
+
const CHONG: Record<string, string> = { 子: '午', 午: '子', 丑: '未', 未: '丑', 寅: '申', 申: '寅', 卯: '酉', 酉: '卯', 辰: '戌', 戌: '辰', 巳: '亥', 亥: '巳' };
|
|
29
|
+
|
|
30
|
+
// 驿马(三合局冲):申子辰→寅、寅午戌→申、巳酉丑→亥、亥卯未→巳
|
|
31
|
+
function yimaOf(zhi: string): string {
|
|
32
|
+
const maMap: Record<string, string> = { 申: '寅', 子: '寅', 辰: '寅', 寅: '申', 午: '申', 戌: '申', 巳: '亥', 酉: '亥', 丑: '亥', 亥: '巳', 卯: '巳', 未: '巳' };
|
|
33
|
+
return maMap[zhi] || '寅';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 干五合(别责阳日用):甲己合、乙庚合、丙辛合、丁壬合、戊癸合
|
|
37
|
+
const GAN_HE: Record<string, string> = { 甲: '己', 己: '甲', 乙: '庚', 庚: '乙', 丙: '辛', 辛: '丙', 丁: '壬', 壬: '丁', 戊: '癸', 癸: '戊' };
|
|
38
|
+
|
|
39
|
+
interface Ke {
|
|
40
|
+
shang: string; // 上神(天盘所加之神)
|
|
41
|
+
xia: string; // 下神(地盘本支)
|
|
42
|
+
shangKe: boolean; // 上克下
|
|
43
|
+
xiaKe: boolean; // 下克上(贼)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function buildKe(tianpan: Record<number, string>, ganJi: string, dayZhi: string): Ke[] {
|
|
47
|
+
const zi = (z: string) => ZHI.indexOf(z as any);
|
|
48
|
+
const ke1 = tianpan[zi(ganJi)];
|
|
49
|
+
const ke2 = tianpan[zi(ke1)];
|
|
50
|
+
const ke3 = tianpan[zi(dayZhi)];
|
|
51
|
+
const ke4 = tianpan[zi(ke3)];
|
|
52
|
+
const rows: Ke[] = [
|
|
53
|
+
{ shang: ke1, xia: ganJi, shangKe: false, xiaKe: false },
|
|
54
|
+
{ shang: ke2, xia: ke1, shangKe: false, xiaKe: false },
|
|
55
|
+
{ shang: ke3, xia: dayZhi, shangKe: false, xiaKe: false },
|
|
56
|
+
{ shang: ke4, xia: ke3, shangKe: false, xiaKe: false },
|
|
57
|
+
];
|
|
58
|
+
for (const r of rows) {
|
|
59
|
+
const sw = WUXING[r.shang], xw = WUXING[r.xia];
|
|
60
|
+
r.shangKe = ke(sw, xw);
|
|
61
|
+
r.xiaKe = ke(xw, sw);
|
|
62
|
+
}
|
|
63
|
+
return rows;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface Chuan { method: string; note: string; chuan1: string; chuan2: string; chuan3: string; }
|
|
67
|
+
|
|
68
|
+
// 由初传推中传、末传:中传 = 初传上神(天盘加临初传者),末传 = 中传上神
|
|
69
|
+
function chuanFrom(tianpan: Record<number, string>, chuan1: string): { chuan2: string; chuan3: string } {
|
|
70
|
+
const zi = (z: string) => ZHI.indexOf(z as any);
|
|
71
|
+
const chuan2 = tianpan[zi(chuan1)];
|
|
72
|
+
const chuan3 = tianpan[zi(chuan2)];
|
|
73
|
+
return { chuan2, chuan3 };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 九宗门起三传(《六壬大全》标准规则)
|
|
78
|
+
* 顺序:有克→贼克(唯一取之;多克→比用→涉害);无克→遥克;无遥克→别责/八专/昴星;
|
|
79
|
+
* 返吟无克取驿马;伏吟无克取刑
|
|
80
|
+
*/
|
|
81
|
+
export function jiuzongmen(tianpan: Record<number, string>, ganJi: string, dayZhi: string, dayGan: string, hourIndex: number, jiang: string): Chuan {
|
|
82
|
+
const zi = (z: string) => ZHI.indexOf(z as any);
|
|
83
|
+
const kes = buildKe(tianpan, ganJi, dayZhi);
|
|
84
|
+
const shangKeRows = kes.filter(k => k.shangKe);
|
|
85
|
+
const xiaKeRows = kes.filter(k => k.xiaKe);
|
|
86
|
+
const jiangIdx = zi(jiang);
|
|
87
|
+
const isFuyin = jiangIdx === hourIndex; // 天地盘同位
|
|
88
|
+
const isFanyin = mod(jiangIdx - hourIndex, 12) === 6; // 天地盘对冲
|
|
89
|
+
|
|
90
|
+
// 涉害:多克且比用不出时,取受克最深者(涉害深者发用);同深取先见
|
|
91
|
+
const shehai = (candidates: Ke[]): Ke => {
|
|
92
|
+
let best = candidates[0], bestDepth = -1;
|
|
93
|
+
for (const c of candidates) {
|
|
94
|
+
let depth = 0;
|
|
95
|
+
for (const z of ZHI) {
|
|
96
|
+
const wx = WUXING[c.shang];
|
|
97
|
+
if (ke(WUXING[z], wx)) depth++; // 地盘支克上神
|
|
98
|
+
}
|
|
99
|
+
if (depth > bestDepth) { best = c; bestDepth = depth; }
|
|
100
|
+
}
|
|
101
|
+
return best;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// ── 1. 贼克法 ──
|
|
105
|
+
if (shangKeRows.length > 0 || xiaKeRows.length > 0) {
|
|
106
|
+
// 有上克下优先取上克下;无上克下取贼
|
|
107
|
+
const pool = shangKeRows.length > 0 ? shangKeRows : xiaKeRows;
|
|
108
|
+
if (pool.length === 1) {
|
|
109
|
+
// 元首课(一上克下)/ 始入课(一下贼上)
|
|
110
|
+
const c1 = pool[0].shang;
|
|
111
|
+
const { chuan2, chuan3 } = chuanFrom(tianpan, c1);
|
|
112
|
+
return {
|
|
113
|
+
method: pool[0].shangKe ? (shangKeRows.length === 1 && xiaKeRows.length === 0 ? '元首课' : '重审课') : '始入课',
|
|
114
|
+
note: pool[0].shangKe ? (shangKeRows.length === 1 && xiaKeRows.length === 0 ? '一上克下,事起于外,宜主动决断。' : '课多上克,主事多制肘,须审时度势。') : '一下贼上,事起于内,防人算计。',
|
|
115
|
+
chuan1: c1, chuan2, chuan3,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
// 多克 → 比用(取与日干阴阳相同之上神)
|
|
119
|
+
const dayGanYang = ganYang(dayGan);
|
|
120
|
+
const bi = pool.filter(k => zhiYang(k.shang) === dayGanYang);
|
|
121
|
+
if (bi.length === 1) {
|
|
122
|
+
const c1 = bi[0].shang;
|
|
123
|
+
const { chuan2, chuan3 } = chuanFrom(tianpan, c1);
|
|
124
|
+
return { method: '知一课', note: '取与日干比和者发用,事有取舍,宜择同类而谋。', chuan1: c1, chuan2, chuan3 };
|
|
125
|
+
}
|
|
126
|
+
// 比用不出 → 涉害
|
|
127
|
+
const pick = shehai(pool);
|
|
128
|
+
const c1 = pick.shang;
|
|
129
|
+
const { chuan2, chuan3 } = chuanFrom(tianpan, c1);
|
|
130
|
+
return { method: '涉害课', note: '诸克不比,取受克最深者发用,事机隐晦,宜深察利害。', chuan1: c1, chuan2, chuan3 };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── 返吟无克:取驿马 ──
|
|
134
|
+
if (isFanyin) {
|
|
135
|
+
const ma = yimaOf(dayZhi);
|
|
136
|
+
const c1 = tianpan[zi(ma)]; // 马星所加之神
|
|
137
|
+
const { chuan2, chuan3 } = chuanFrom(tianpan, c1);
|
|
138
|
+
return { method: '返吟课', note: '天地盘相冲,事有反复翻覆;无克取驿马发用,主变动奔波,宜动不宜静。', chuan1: c1, chuan2, chuan3 };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ── 伏吟无克:取刑 ──
|
|
142
|
+
if (isFuyin) {
|
|
143
|
+
const ganYangFlag = ganYang(dayGan);
|
|
144
|
+
const c1 = ganYangFlag ? ganJi : dayZhi; // 阳日取干上(寄宫本支),阴日取支上
|
|
145
|
+
const xing1 = XING[c1] || (ZIXING.includes(c1) ? CHONG[c1] : c1);
|
|
146
|
+
const xing2 = xing1 && xing1 !== c1 ? (XING[xing1] || (ZIXING.includes(xing1) ? CHONG[xing1] : xing1)) : c1;
|
|
147
|
+
const chuan2 = tianpan[zi(xing1)] || c1;
|
|
148
|
+
const chuan3 = tianpan[zi(xing2)] || chuan2;
|
|
149
|
+
return { method: '伏吟课', note: '天地盘同位,诸事迟滞反复;无克取刑发用,主静中藏动,宜守待变。', chuan1: tianpan[zi(c1)], chuan2, chuan3 };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ── 2. 遥克法(无上下克):日干遥克上神(蒿矢)/ 上神遥克日干(弹射) ──
|
|
153
|
+
const ganWx = WUXING[dayGan];
|
|
154
|
+
const yaoKe: Ke[] = [];
|
|
155
|
+
const yaoBei: Ke[] = [];
|
|
156
|
+
for (const k of kes) {
|
|
157
|
+
if (ke(ganWx, WUXING[k.shang])) yaoKe.push(k); // 日干克上神
|
|
158
|
+
if (ke(WUXING[k.shang], ganWx)) yaoBei.push(k); // 上神克日干
|
|
159
|
+
}
|
|
160
|
+
if (yaoKe.length > 0 || yaoBei.length > 0) {
|
|
161
|
+
const pool = yaoKe.length > 0 ? yaoKe : yaoBei;
|
|
162
|
+
const dayGanYang = ganYang(dayGan);
|
|
163
|
+
const bi = pool.filter(k => zhiYang(k.shang) === dayGanYang);
|
|
164
|
+
const pick = bi.length > 0 ? bi[0] : pool[0];
|
|
165
|
+
const c1 = pick.shang;
|
|
166
|
+
const { chuan2, chuan3 } = chuanFrom(tianpan, c1);
|
|
167
|
+
return {
|
|
168
|
+
method: yaoKe.length > 0 ? '蒿矢课' : '弹射课',
|
|
169
|
+
note: yaoKe.length > 0 ? '日遥克神,事由己发,主主动求取,渐有所成。' : '神遥克日,事出意外,防暗箭来袭,宜谨慎自守。',
|
|
170
|
+
chuan1: c1, chuan2, chuan3,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ── 3. 无克无遥克:别责 / 八专 / 昴星 ──
|
|
175
|
+
const ganJiZhi = ganJi;
|
|
176
|
+
// 八专:日干寄宫与日支同位(四课止两课)
|
|
177
|
+
if (ganJiZhi === dayZhi) {
|
|
178
|
+
const ganYangFlag = ganYang(dayGan);
|
|
179
|
+
const c1 = ganYangFlag ? kes[0].shang : kes[2].shang; // 阳日取干上神,阴日取支上神
|
|
180
|
+
const { chuan2, chuan3 } = chuanFrom(tianpan, c1);
|
|
181
|
+
return { method: '八专课', note: '干支同位,四课不完;主事机专一,惟阴阳未分,宜专一其心。', chuan1: c1, chuan2, chuan3 };
|
|
182
|
+
}
|
|
183
|
+
// 别责:干上神与支上神相同(四课不备)
|
|
184
|
+
if (kes[0].shang === kes[2].shang) {
|
|
185
|
+
const ganYangFlag = ganYang(dayGan);
|
|
186
|
+
// 阳日取干五合之干寄宫上神;阴日取支上神
|
|
187
|
+
if (ganYangFlag) {
|
|
188
|
+
const heGan = GAN_HE[dayGan];
|
|
189
|
+
const heJi = LR_GANJI[heGan];
|
|
190
|
+
const c1 = tianpan[zi(heJi)];
|
|
191
|
+
const { chuan2, chuan3 } = chuanFrom(tianpan, c1);
|
|
192
|
+
return { method: '别责课', note: '四课不备,取干合发用;主事不归一,须借力他人,另辟蹊径。', chuan1: c1, chuan2, chuan3 };
|
|
193
|
+
}
|
|
194
|
+
const c1 = kes[2].shang;
|
|
195
|
+
const { chuan2, chuan3 } = chuanFrom(tianpan, c1);
|
|
196
|
+
return { method: '别责课', note: '四课不备,取支上神发用;主事有隐衷,宜旁敲侧击,徐图其成。', chuan1: c1, chuan2, chuan3 };
|
|
197
|
+
}
|
|
198
|
+
// 昴星:无克无遥克且四课全备——以酉(昴星)为用
|
|
199
|
+
{
|
|
200
|
+
const ganYangFlag = ganYang(dayGan);
|
|
201
|
+
const youIdx = zi('酉');
|
|
202
|
+
if (ganYangFlag) {
|
|
203
|
+
// 阳日:取天盘加临地盘酉之神(酉上神)为初传(虎视课);中传取支上神,末传取干上神
|
|
204
|
+
const c1 = tianpan[youIdx];
|
|
205
|
+
const c2 = tianpan[zi(dayZhi)]; // 支上神
|
|
206
|
+
const c3 = tianpan[zi(ganJi)]; // 干上神
|
|
207
|
+
return { method: '昴星课', note: '无克无遥,取昴星(酉)发用(虎视格);主事机隐晦,宜静观其变,防惊变于暗处。', chuan1: c1, chuan2: c2, chuan3: c3 };
|
|
208
|
+
}
|
|
209
|
+
// 阴日:取天盘酉加临之地盘支(酉下神)为初传(冬蛇掩目);中传取干上神,末传取支上神
|
|
210
|
+
let c1 = '';
|
|
211
|
+
for (let i = 0; i < 12; i++) if (tianpan[i] === '酉') { c1 = ZHI[i]; break; }
|
|
212
|
+
const c2 = tianpan[zi(ganJi)];
|
|
213
|
+
const c3 = tianpan[zi(dayZhi)];
|
|
214
|
+
return { method: '昴星课', note: '无克无遥,取昴星(酉)发用(冬蛇掩目格);主事蒙昧,宜守正待时,防晦气暗生。', chuan1: c1, chuan2: c2, chuan3: c3 };
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
7
218
|
export function liurenCalc(dt: string | Date): LiurenResult {
|
|
8
219
|
const d = dt instanceof Date ? dt : new Date(dt);
|
|
9
220
|
const y = d.getFullYear(), m = d.getMonth() + 1, day = d.getDate();
|
|
@@ -36,8 +247,9 @@ export function liurenCalc(dt: string | Date): LiurenResult {
|
|
|
36
247
|
const ke2 = tianpan[ZHI.indexOf(ke1 as any)];
|
|
37
248
|
const ke3 = tianpan[ZHI.indexOf(dayGZ[1] as any)];
|
|
38
249
|
const ke4 = tianpan[ZHI.indexOf(ke3 as any)];
|
|
39
|
-
/*
|
|
40
|
-
const
|
|
250
|
+
/* 三传:九宗门真实起法(替代旧简式取传) */
|
|
251
|
+
const chuan = jiuzongmen(tianpan, ganJi, dayGZ[1], dayGZ[0], hourIndex, jiang);
|
|
252
|
+
const { chuan1, chuan2, chuan3, method, note } = chuan;
|
|
41
253
|
|
|
42
254
|
/* ── 补齐层:贵人 + 十二天将 ── */
|
|
43
255
|
// 贵人起法(日干):甲戊庚牛羊 乙己鼠猴乡 丙丁猪鸡位 壬癸兔蛇藏 六辛逢马虎
|
|
@@ -77,10 +289,10 @@ export function liurenCalc(dt: string | Date): LiurenResult {
|
|
|
77
289
|
: keti === '反吟课' ? '天地盘相冲,事有反复翻覆,来去无常,宜缓不宜急。'
|
|
78
290
|
: '四课三传乘常气,事机明朗,顺其自然即可。';
|
|
79
291
|
|
|
80
|
-
return { dayGZ, hourGZ, jiang, jqName, tianpan, ganJi, ke1, ke2, ke3, ke4, chuan1, chuan2, chuan3, guiRen, isDay, tianJiang, chuanJiang, keti, ketiNote };
|
|
292
|
+
return { dayGZ, hourGZ, jiang, jqName, tianpan, ganJi, ke1, ke2, ke3, ke4, chuan1, chuan2, chuan3, guiRen, isDay, tianJiang, chuanJiang, keti, ketiNote, chuanMethod: method, chuanNote: note };
|
|
81
293
|
}
|
|
82
294
|
|
|
83
295
|
export const jiangName = (zhi: string): string => {
|
|
84
296
|
const row = LR_JIANGS.find(j => j[1] === zhi);
|
|
85
297
|
return row ? row[0] : zhi;
|
|
86
|
-
}
|
|
298
|
+
}
|
|
@@ -1,9 +1,28 @@
|
|
|
1
|
-
//
|
|
1
|
+
// 奇门遁甲:九宫起局(节气定阴阳遁 · 日干支三元定局 · 时干支定旬首)
|
|
2
|
+
// 说明:超神接气/置闰(节气时刻与甲己日不齐时的补闰规则)属专业级范畴,当前按「正授」近似:
|
|
3
|
+
// 以日干支查三元表定局,与交节日正授情形完全一致;超接偏差时可能差一元,后续专业级补齐。
|
|
2
4
|
import { GAN, ZHI, mod } from '../data/ganzhi';
|
|
3
5
|
import { QM_SEASONS, QM_STARS, QM_MEN, LUOSHU } from '../data/qimen';
|
|
4
6
|
import { daysSince, currentJieqiNameExact } from './calendar';
|
|
5
7
|
import type { QimenInput, QimenResult } from '../types';
|
|
6
8
|
|
|
9
|
+
|
|
10
|
+
// 三元定局表(时家奇门标准):行=日干五合组,列=日支组(0=子午卯酉 1=寅申巳亥 2=辰戌丑未)
|
|
11
|
+
// 值 0=上元 1=中元 2=下元。甲子旬前五日上元、次五日中元、甲戌旬前五日下元——已按日干支循环验证自洽。
|
|
12
|
+
const SAN_YUAN: number[][] = [
|
|
13
|
+
[0, 1, 2], // 甲己:子午卯酉上元 寅申巳亥中元 辰戌丑未下元
|
|
14
|
+
[1, 2, 0], // 乙庚:子午卯酉中元 寅申巳亥下元 辰戌丑未上元
|
|
15
|
+
[2, 0, 1], // 丙辛:子午卯酉下元 寅申巳亥上元 辰戌丑未中元
|
|
16
|
+
[0, 1, 2], // 丁壬:同甲己
|
|
17
|
+
[1, 2, 0], // 戊癸:同乙庚
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
function sanYuanOf(dayGan: string, dayZhi: string): number {
|
|
21
|
+
const ganGroup = ['甲', '己'].includes(dayGan) ? 0 : ['乙', '庚'].includes(dayGan) ? 1 : ['丙', '辛'].includes(dayGan) ? 2 : ['丁', '壬'].includes(dayGan) ? 3 : 4;
|
|
22
|
+
const zhiGroup = ['子', '午', '卯', '酉'].includes(dayZhi) ? 0 : ['寅', '申', '巳', '亥'].includes(dayZhi) ? 1 : 2;
|
|
23
|
+
return SAN_YUAN[ganGroup][zhiGroup];
|
|
24
|
+
}
|
|
25
|
+
|
|
7
26
|
export function qimenCalc(input: QimenInput): QimenResult {
|
|
8
27
|
const d = input.datetime instanceof Date ? input.datetime : new Date(input.datetime);
|
|
9
28
|
const y = d.getFullYear(), m = d.getMonth() + 1, day = d.getDate();
|
|
@@ -21,13 +40,16 @@ export function qimenCalc(input: QimenInput): QimenResult {
|
|
|
21
40
|
const season = QM_SEASONS.find(s => s.name === jqName);
|
|
22
41
|
if (!season) throw new Error('节气未找到: ' + jqName);
|
|
23
42
|
const yin = season.yin;
|
|
24
|
-
/*
|
|
25
|
-
const xun =
|
|
43
|
+
/* 三元定局(按日干支,替代旧「按日数粗略取元」) */
|
|
44
|
+
const xun = sanYuanOf(dayGZ[0], dayGZ[1]);
|
|
26
45
|
const ju = season.ju[xun];
|
|
27
|
-
/*
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
46
|
+
/* 旬首六仪(按时干支真实旬首:时干支序号 → 所在旬 → 六仪) */
|
|
47
|
+
const gzIdx = mod((GAN.indexOf(hourGZ[0] as any) - ZHI.indexOf(hourGZ[1] as any)) * 6 + ZHI.indexOf(hourGZ[1] as any), 60);
|
|
48
|
+
const xunStart = Math.floor(gzIdx / 10) * 10; // 旬首干支序号(甲子=0 甲戌=10 …)
|
|
49
|
+
const xunShouZhi = ZHI[xunStart % 12];
|
|
50
|
+
const xunshouMap: Record<string, string> = { 子: '戊', 戌: '己', 申: '庚', 午: '辛', 辰: '壬', 寅: '癸' };
|
|
51
|
+
const xunShou = xunshouMap[xunShouZhi]; // 旬首六仪(甲子戊 甲戌己 …)
|
|
52
|
+
const xunshouName = '甲' + xunShouZhi;
|
|
31
53
|
/* 地盘布奇仪:阳遁顺布(1→8→3→4→9→2→7→6→中5),阴遁逆布 */
|
|
32
54
|
const qiyiOrder = ['戊', '己', '庚', '辛', '壬', '癸', '丁', '丙', '乙'];
|
|
33
55
|
const yiAt: Record<number, string> = {};
|
|
@@ -74,5 +96,5 @@ export function qimenCalc(input: QimenInput): QimenResult {
|
|
|
74
96
|
shen[p] = shenSeq[step];
|
|
75
97
|
});
|
|
76
98
|
shen[5] = '';
|
|
77
|
-
return { yin, ju, jqName, dayGZ, hourGZ, xunShou, xunshouName
|
|
99
|
+
return { yin, ju, jqName, dayGZ, hourGZ, xunShou, xunshouName, zfStar, zsMen, zfPalace, pan, zsPalace, tianYi, shen };
|
|
78
100
|
}
|
package/shared/core/types.ts
CHANGED
|
@@ -160,6 +160,9 @@ export interface LiurenResult {
|
|
|
160
160
|
chuanJiang: { chuan: string; jiang: string }[]; // 三传所乘天将
|
|
161
161
|
keti: string; // 课体(伏吟/反吟/常课)
|
|
162
162
|
ketiNote: string; // 课体断语
|
|
163
|
+
// ─── 九宗门起三传(2026-08-23)───
|
|
164
|
+
chuanMethod: string; // 起传法门(元首/始入/重审/知一/涉害/蒿矢/弹射/昴星/别责/八专/返吟/伏吟)
|
|
165
|
+
chuanNote: string; // 起传断语
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
// 小六壬输出
|
package/src/App.tsx
CHANGED
|
@@ -67,7 +67,8 @@ export function LiurenResult({ data }: { data: LiurenResult }) {
|
|
|
67
67
|
{ k: '三课 · 支上', v: r.ke3, big: true },
|
|
68
68
|
{ k: '四课 · 支阴', v: r.ke4, big: true },
|
|
69
69
|
]}>
|
|
70
|
-
<p
|
|
70
|
+
<p>三传({r.chuanMethod || '起传'}):<strong className="tag-hot">初传 {r.chuan1}</strong> → <strong className="tag-cool">中传 {r.chuan2}</strong> → <strong>末传 {r.chuan3}</strong>。</p>
|
|
71
|
+
{r.chuanNote && <p className="tiny muted">{r.chuanNote}</p>}
|
|
71
72
|
<p>初传为事发之端,中传为事进之中,末传为事成之归。干为外、支为内,四课之内外,即人事之表里。</p>
|
|
72
73
|
<p className="mt-2"><strong>三传参详:</strong>{liurenAdvice(r)}</p>
|
|
73
74
|
</ResultCard>
|
package/src/data/arts.ts
CHANGED
package/src/index.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@import url('https://fonts.googleapis.com/css2?family=
|
|
1
|
+
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600;700&family=Ma+Shan+Zheng&family=Zhi+Mang+Xing&family=Noto+Sans+SC:wght@300;400;500;700&display=swap');
|
|
2
2
|
|
|
3
3
|
@tailwind base;
|
|
4
4
|
@tailwind components;
|
|
@@ -11,44 +11,15 @@
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
body {
|
|
14
|
-
@apply bg-void text-stardust font-body;
|
|
15
14
|
overflow-x: hidden;
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
::selection {
|
|
19
|
-
@apply bg-ancient/30 text-stardust;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
17
|
::-webkit-scrollbar {
|
|
23
18
|
width: 6px;
|
|
24
19
|
}
|
|
25
|
-
|
|
26
|
-
::-webkit-scrollbar-track {
|
|
27
|
-
@apply bg-void;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
::-webkit-scrollbar-thumb {
|
|
31
|
-
@apply bg-ancient/30 rounded-full;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
::-webkit-scrollbar-thumb:hover {
|
|
35
|
-
@apply bg-ancient/50;
|
|
36
|
-
}
|
|
37
20
|
}
|
|
38
21
|
|
|
39
22
|
@layer components {
|
|
40
|
-
.glass-panel {
|
|
41
|
-
@apply bg-abyss/60 backdrop-blur-md border border-ancient/20 rounded-lg;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.gold-border {
|
|
45
|
-
@apply border border-ancient/40 hover:border-ancient/80 transition-all duration-500;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.text-glow {
|
|
49
|
-
text-shadow: 0 0 10px rgba(201, 162, 39, 0.5), 0 0 20px rgba(201, 162, 39, 0.3);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
23
|
.card-3d {
|
|
53
24
|
transform-style: preserve-3d;
|
|
54
25
|
perspective: 1000px;
|
|
@@ -61,18 +32,6 @@
|
|
|
61
32
|
.card-back {
|
|
62
33
|
transform: rotateY(180deg);
|
|
63
34
|
}
|
|
64
|
-
|
|
65
|
-
.mystic-gradient {
|
|
66
|
-
background: linear-gradient(135deg, rgba(26,26,46,0.9) 0%, rgba(10,10,15,0.95) 100%);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.shimmer-text {
|
|
70
|
-
background: linear-gradient(90deg, #c9a227 0%, #ffd700 50%, #c9a227 100%);
|
|
71
|
-
background-size: 200% auto;
|
|
72
|
-
-webkit-background-clip: text;
|
|
73
|
-
-webkit-text-fill-color: transparent;
|
|
74
|
-
animation: shimmer 3s linear infinite;
|
|
75
|
-
}
|
|
76
35
|
}
|
|
77
36
|
|
|
78
37
|
@layer utilities {
|
|
@@ -93,11 +52,6 @@
|
|
|
93
52
|
}
|
|
94
53
|
}
|
|
95
54
|
|
|
96
|
-
@keyframes shimmer {
|
|
97
|
-
0% { background-position: 200% center; }
|
|
98
|
-
100% { background-position: -200% center; }
|
|
99
|
-
}
|
|
100
|
-
|
|
101
55
|
@keyframes fade-in {
|
|
102
56
|
from {
|
|
103
57
|
opacity: 0;
|
|
@@ -109,24 +63,6 @@
|
|
|
109
63
|
}
|
|
110
64
|
}
|
|
111
65
|
|
|
112
|
-
@keyframes float {
|
|
113
|
-
0%, 100% {
|
|
114
|
-
transform: translateY(0px);
|
|
115
|
-
}
|
|
116
|
-
50% {
|
|
117
|
-
transform: translateY(-10px);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
@keyframes pulse-glow {
|
|
122
|
-
0%, 100% {
|
|
123
|
-
box-shadow: 0 0 5px rgba(201, 162, 39, 0.3);
|
|
124
|
-
}
|
|
125
|
-
50% {
|
|
126
|
-
box-shadow: 0 0 20px rgba(201, 162, 39, 0.6);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
66
|
@keyframes typing-cursor {
|
|
131
67
|
0%, 100% {
|
|
132
68
|
opacity: 1;
|
|
@@ -141,14 +77,6 @@
|
|
|
141
77
|
animation: fade-in 0.5s ease-out forwards;
|
|
142
78
|
}
|
|
143
79
|
|
|
144
|
-
.animate-float {
|
|
145
|
-
animation: float 3s ease-in-out infinite;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
.animate-pulse-glow {
|
|
149
|
-
animation: pulse-glow 2s ease-in-out infinite;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
80
|
.animate-typing-cursor {
|
|
153
81
|
animation: typing-cursor 1s step-end infinite;
|
|
154
82
|
}
|
|
@@ -187,4 +115,4 @@
|
|
|
187
115
|
@keyframes pulse-soft {
|
|
188
116
|
0%, 100% { opacity: .55; }
|
|
189
117
|
50% { opacity: 1; }
|
|
190
|
-
}
|
|
118
|
+
}
|
package/src/pages/DemoPage.tsx
CHANGED
|
@@ -31,22 +31,16 @@ import Disclaimer from '@/components/Disclaimer';
|
|
|
31
31
|
const DEMO_BIRTH = { y: 1988, m: 6, d: 15, hourIndex: 6, time: '12:00', gender: '女' as const, location: { lng: 116.4, lat: 39.9, province: '北京市', city: '北京市', district: '东城区' } };
|
|
32
32
|
|
|
33
33
|
// 九术本地排盘(与后端 divine.ts 同一引擎、同一输入口径)
|
|
34
|
-
const ARTS: { id: string; name: string; glyph: string
|
|
35
|
-
{ id: 'bazi', name: '四柱八字', glyph: '命'
|
|
36
|
-
{ id: 'ziwei', name: '紫微斗数', glyph: '星',
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
{ id: '
|
|
41
|
-
{ id: '
|
|
42
|
-
{ id: '
|
|
43
|
-
{ id: '
|
|
44
|
-
{ id: 'liuren', name: '大六壬', glyph: '课', calc: () => liurenCalc(new Date()) },
|
|
45
|
-
{ id: 'xiaoliuren', name: '小六壬', glyph: '掌', calc: () => xiaoliurenCalc('time', new Date().getMonth() + 1, new Date().getDate(), new Date().getHours() % 12, 3, 5, 7) },
|
|
46
|
-
{ id: 'tarot', name: '塔罗', glyph: '镜', calc: () => {
|
|
47
|
-
const spread = allTarotSpreads().find((x: any) => x.id === 'three') || allTarotSpreads()[0];
|
|
48
|
-
return { spread, cards: tarotDraw(3) };
|
|
49
|
-
} },
|
|
34
|
+
const ARTS: { id: string; name: string; glyph: string }[] = [
|
|
35
|
+
{ id: 'bazi', name: '四柱八字', glyph: '命' },
|
|
36
|
+
{ id: 'ziwei', name: '紫微斗数', glyph: '星' },
|
|
37
|
+
{ id: 'astrology', name: '古典星盘', glyph: '穹' },
|
|
38
|
+
{ id: 'qimen', name: '奇门遁甲', glyph: '遁' },
|
|
39
|
+
{ id: 'meihua', name: '梅花易数', glyph: '梅' },
|
|
40
|
+
{ id: 'liuyao', name: '六爻', glyph: '爻' },
|
|
41
|
+
{ id: 'liuren', name: '大六壬', glyph: '课' },
|
|
42
|
+
{ id: 'xiaoliuren', name: '小六壬', glyph: '掌' },
|
|
43
|
+
{ id: 'tarot', name: '塔罗', glyph: '镜' },
|
|
50
44
|
];
|
|
51
45
|
const RESULT_VIEWS: Record<string, (data: unknown) => React.ReactElement> = {
|
|
52
46
|
bazi: d => <BaziResult data={d as any} />,
|
|
@@ -66,8 +60,38 @@ export default function DemoPage() {
|
|
|
66
60
|
const [showRaw, setShowRaw] = useState(false);
|
|
67
61
|
const [artId, setArtId] = useState('bazi');
|
|
68
62
|
const [localResult, setLocalResult] = useState<unknown>(null);
|
|
69
|
-
|
|
63
|
+
// ─── 演示输入(#2):日期/时刻/报数,替换固定示例档案 ───
|
|
64
|
+
const [y, setY] = useState(DEMO_BIRTH.y);
|
|
65
|
+
const [m, setM] = useState(DEMO_BIRTH.m);
|
|
66
|
+
const [d, setD] = useState(DEMO_BIRTH.d);
|
|
67
|
+
const [hour, setHour] = useState(12);
|
|
68
|
+
const [n1, setN1] = useState(3);
|
|
69
|
+
const [n2, setN2] = useState(5);
|
|
70
|
+
const [n3, setN3] = useState(7);
|
|
70
71
|
|
|
72
|
+
const birth = { ...DEMO_BIRTH, y, m, d, hourIndex: Math.floor((hour % 24) / 2), time: (hour < 10 ? '0' + hour : '' + hour) + ':00' };
|
|
73
|
+
|
|
74
|
+
// 按术别用当前输入起例(命盘类用输入日期;占问类用报数/当前时刻)
|
|
75
|
+
const buildCalc = (id: string): (() => unknown) => {
|
|
76
|
+
switch (id) {
|
|
77
|
+
case 'bazi': return () => baziCalc(birth);
|
|
78
|
+
case 'ziwei': return () => {
|
|
79
|
+
const lunar = Solar.fromYmd(birth.y, birth.m, birth.d).getLunar();
|
|
80
|
+
return ziweiCalc({ ganzhi: lunar.getYearInGanZhi(), month: Math.abs(lunar.getMonth()), day: lunar.getDay(), hour: birth.hourIndex, time: birth.time, location: birth.location, gender: birth.gender, birthYear: birth.y, solarDate: [birth.y, birth.m, birth.d] });
|
|
81
|
+
};
|
|
82
|
+
case 'astrology': return () => astrologyCalc(birth.y, birth.m, birth.d, hour, 0, birth.location.lng, birth.location.lat);
|
|
83
|
+
case 'qimen': return () => qimenCalc({ datetime: new Date(y, m - 1, d, hour, 0) });
|
|
84
|
+
case 'meihua': return () => meihuaCalc({ mode: 'number', n1, n2, n3, now: new Date() } as any);
|
|
85
|
+
case 'liuyao': return () => liuyaoCalc(undefined, { y, m, d });
|
|
86
|
+
case 'liuren': return () => liurenCalc(new Date(y, m - 1, d, hour, 0));
|
|
87
|
+
case 'xiaoliuren': return () => xiaoliurenCalc('time', m, d, Math.floor((hour % 24) / 2), n1, n2, n3);
|
|
88
|
+
case 'tarot': return () => {
|
|
89
|
+
const spread = allTarotSpreads().find((x: any) => x.id === 'three') || allTarotSpreads()[0];
|
|
90
|
+
return { spread, cards: tarotDraw(3) };
|
|
91
|
+
};
|
|
92
|
+
default: return () => null;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
71
95
|
const reset = () => {
|
|
72
96
|
setPhase('idle');
|
|
73
97
|
setReport(null);
|
|
@@ -82,7 +106,7 @@ export default function DemoPage() {
|
|
|
82
106
|
// 本地引擎排盘(纯浏览器计算,无需后端)
|
|
83
107
|
setTimeout(() => {
|
|
84
108
|
try {
|
|
85
|
-
setLocalResult(
|
|
109
|
+
setLocalResult(buildCalc(artId)());
|
|
86
110
|
} catch (e) {
|
|
87
111
|
console.error('本地排盘失败', e);
|
|
88
112
|
}
|
|
@@ -109,9 +133,9 @@ export default function DemoPage() {
|
|
|
109
133
|
<div className="wrap" style={{ marginTop: 'var(--sp-4)' }}>
|
|
110
134
|
{phase === 'idle' && (
|
|
111
135
|
<div className="result-card" style={{ padding: 'var(--sp-5)' }}>
|
|
112
|
-
<p style={{ fontSize: '1.05rem', marginBottom: 'var(--sp-2)' }}
|
|
113
|
-
<p className="tiny muted" style={{ marginBottom: 'var(--sp-2)' }}
|
|
114
|
-
<p className="tiny muted" style={{ marginBottom: 'var(--sp-3)' }}>三步即玩:① 选术数 → ②
|
|
136
|
+
<p style={{ fontSize: '1.05rem', marginBottom: 'var(--sp-2)' }}>选一种术数,填好(或直接用默认)日期时刻,点一下就能看到排盘结果;八字还会附带一份完整的 AI 解读示例。</p>
|
|
137
|
+
<p className="tiny muted" style={{ marginBottom: 'var(--sp-2)' }}>命盘类(八字/紫微/星盘)用下面日期时刻排盘;占问类(奇门/六壬/小六壬)用该时刻起例;梅花可用报数起卦</p>
|
|
138
|
+
<p className="tiny muted" style={{ marginBottom: 'var(--sp-3)' }}>三步即玩:① 选术数 → ② 填日期(可选)→ ③ 点「开始体验」看结果</p>
|
|
115
139
|
<div className="art-picker" style={{ display: 'flex', flexWrap: 'wrap', gap: '.5rem', marginBottom: 'var(--sp-4)' }}>
|
|
116
140
|
{ARTS.map(a => (
|
|
117
141
|
<button key={a.id} onClick={() => setArtId(a.id)}
|
|
@@ -121,6 +145,27 @@ export default function DemoPage() {
|
|
|
121
145
|
</button>
|
|
122
146
|
))}
|
|
123
147
|
</div>
|
|
148
|
+
{/* 演示输入表单:日期/时刻/报数 */}
|
|
149
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '1rem', alignItems: 'center', marginBottom: 'var(--sp-4)', padding: 'var(--sp-3)', border: '1px dashed var(--line-soft)', borderRadius: 'var(--r-sm)' }}>
|
|
150
|
+
<label className="tiny muted">日期
|
|
151
|
+
<input className="input-line" type="number" style={{ width: 70, marginLeft: '.4rem' }} value={y} min={1900} max={2100} onChange={e => setY(Number(e.target.value) || 1988)} />
|
|
152
|
+
<span className="tiny muted" style={{ margin: '0 .2rem' }}>/</span>
|
|
153
|
+
<input className="input-line" type="number" style={{ width: 46 }} value={m} min={1} max={12} onChange={e => setM(Number(e.target.value) || 6)} />
|
|
154
|
+
<span className="tiny muted" style={{ margin: '0 .2rem' }}>/</span>
|
|
155
|
+
<input className="input-line" type="number" style={{ width: 46 }} value={d} min={1} max={31} onChange={e => setD(Number(e.target.value) || 15)} />
|
|
156
|
+
</label>
|
|
157
|
+
<label className="tiny muted">时刻
|
|
158
|
+
<input className="input-line" type="number" style={{ width: 56, marginLeft: '.4rem' }} value={hour} min={0} max={23} onChange={e => setHour(Math.max(0, Math.min(23, Number(e.target.value) || 12)))} />
|
|
159
|
+
<span className="tiny muted" style={{ marginLeft: '.3rem' }}>时(24 小时制)</span>
|
|
160
|
+
</label>
|
|
161
|
+
{artId === 'meihua' && (
|
|
162
|
+
<label className="tiny muted">报数
|
|
163
|
+
<input className="input-line" type="number" style={{ width: 46, marginLeft: '.4rem' }} value={n1} min={1} max={9} onChange={e => setN1(Number(e.target.value) || 3)} />
|
|
164
|
+
<input className="input-line" type="number" style={{ width: 46, marginLeft: '.3rem' }} value={n2} min={1} max={9} onChange={e => setN2(Number(e.target.value) || 5)} />
|
|
165
|
+
<input className="input-line" type="number" style={{ width: 46, marginLeft: '.3rem' }} value={n3} min={1} max={9} onChange={e => setN3(Number(e.target.value) || 7)} />
|
|
166
|
+
</label>
|
|
167
|
+
)}
|
|
168
|
+
</div>
|
|
124
169
|
<button className="btn-seal" style={{ fontSize: '1rem', padding: '.7rem 2.2rem' }} onClick={start}>开 始 体 验</button>
|
|
125
170
|
</div>
|
|
126
171
|
)}
|