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.
Files changed (45) hide show
  1. package/README.en.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/assets/index-CRUHghuF.js +102 -0
  4. package/dist/assets/index-CRUHghuF.js.map +1 -0
  5. package/dist/assets/index-hLV3S-Cj.css +1 -0
  6. package/dist/index.html +2 -2
  7. package/package.json +1 -1
  8. package/scripts/release.sh +15 -0
  9. package/server/src/data/db.json +189 -0
  10. package/server/src/data/guanwei.db +0 -0
  11. package/server/src/index.ts +1 -1
  12. package/server/src/services/duanyu.ts +67 -0
  13. package/server/src/services/promptBuilder.ts +5 -0
  14. package/shared/core/data/duanyu.ts +15 -14
  15. package/shared/core/data/ganzhi.ts +1 -1
  16. package/shared/core/data/gua64.ts +1 -1
  17. package/shared/core/data/liuren.ts +1 -1
  18. package/shared/core/data/qimen.ts +1 -1
  19. package/shared/core/data/xiaoliuren.ts +1 -1
  20. package/shared/core/data/ziwei.ts +1 -1
  21. package/shared/core/data/zodiac.ts +1 -1
  22. package/shared/core/engine/bazi.ts +3 -1
  23. package/shared/core/engine/calendar.ts +11 -2
  24. package/shared/core/engine/liuren.ts +218 -6
  25. package/shared/core/engine/qimen.ts +30 -8
  26. package/shared/core/types.ts +3 -0
  27. package/src/App.tsx +1 -1
  28. package/src/components/arts/LiurenArt.tsx +2 -1
  29. package/src/data/arts.ts +1 -1
  30. package/src/index.css +2 -74
  31. package/src/pages/DemoPage.tsx +66 -21
  32. package/tailwind.config.js +0 -35
  33. package/dist/assets/index-C3wrtpPY.css +0 -1
  34. package/dist/assets/index-CAyAIgw8.js +0 -102
  35. package/dist/assets/index-CAyAIgw8.js.map +0 -1
  36. package/src/components/Navigation.tsx +0 -80
  37. package/src/components/TarotCard.tsx +0 -129
  38. package/src/pages/AstrologyPage.tsx +0 -33
  39. package/src/pages/BaZiPage.tsx +0 -33
  40. package/src/pages/Home.tsx +0 -179
  41. package/src/pages/SpreadEditor.tsx +0 -230
  42. package/src/pages/SpreadLibrary.tsx +0 -199
  43. package/src/pages/SpreadLibraryPage.tsx +0 -17
  44. package/src/pages/TarotPage.tsx +0 -736
  45. package/src/pages/ZiWeiPage.tsx +0 -33
@@ -1,230 +0,0 @@
1
- import { useState, useEffect } from 'react';
2
- import { useNavigate, useParams } from 'react-router-dom';
3
- import { Plus, Trash2, Save, ArrowLeft, GripVertical } from 'lucide-react';
4
- import type { Spread, SpreadPosition } from '@/types';
5
- import { saveCustomSpread, getCustomSpreads, deleteCustomSpread } from '@/utils/spreadStorage';
6
-
7
- export default function SpreadEditor() {
8
- const navigate = useNavigate();
9
- const { id } = useParams();
10
- const isEdit = !!id;
11
-
12
- const [name, setName] = useState('');
13
- const [description, setDescription] = useState('');
14
- const [positions, setPositions] = useState<SpreadPosition[]>([
15
- { id: 0, name: '现状', description: '当前状态', x: 50, y: 50 },
16
- ]);
17
- const [draggingIdx, setDraggingIdx] = useState<number | null>(null);
18
-
19
- useEffect(() => {
20
- if (isEdit && id) {
21
- const custom = getCustomSpreads();
22
- const spread = custom.find(s => s.id === id);
23
- if (spread) {
24
- setName(spread.name);
25
- setDescription(spread.description);
26
- setPositions(spread.positions);
27
- }
28
- }
29
- }, [id, isEdit]);
30
-
31
- const addPosition = () => {
32
- if (positions.length >= 12) return;
33
- const newId = positions.length;
34
- const cols = Math.ceil(Math.sqrt(positions.length + 1));
35
- const row = Math.floor(positions.length / cols);
36
- const col = positions.length % cols;
37
- setPositions([
38
- ...positions,
39
- {
40
- id: newId,
41
- name: `位置${newId + 1}`,
42
- description: '请输入描述',
43
- x: 15 + (col * 70 / (cols - 1 || 1)),
44
- y: 15 + (row * 70 / (cols - 1 || 1)),
45
- },
46
- ]);
47
- };
48
-
49
- const updatePosition = (idx: number, field: keyof SpreadPosition, value: string | number) => {
50
- setPositions(positions.map((p, i) => i === idx ? { ...p, [field]: value } : p));
51
- };
52
-
53
- const removePosition = (idx: number) => {
54
- if (positions.length <= 2) return;
55
- setPositions(positions.filter((_, i) => i !== idx).map((p, i) => ({ ...p, id: i })));
56
- };
57
-
58
- const handleSave = () => {
59
- if (!name.trim() || positions.length < 2) return;
60
-
61
- const spread: Spread = {
62
- id: isEdit && id ? id : `custom-${Date.now()}`,
63
- name: name.trim(),
64
- description: description.trim() || '我的自定义牌阵',
65
- positions,
66
- category: 'custom',
67
- isCustom: true,
68
- scene: ['general'],
69
- };
70
-
71
- saveCustomSpread(spread);
72
- navigate('/spread-library');
73
- };
74
-
75
- const handleDelete = () => {
76
- if (!isEdit || !id) return;
77
- if (confirm('确定删除这个牌阵?')) {
78
- deleteCustomSpread(id);
79
- navigate('/spread-library');
80
- }
81
- };
82
-
83
- const handlePositionDrag = (e: React.MouseEvent<HTMLDivElement>) => {
84
- if (draggingIdx === null) return;
85
- const rect = e.currentTarget.getBoundingClientRect();
86
- const x = ((e.clientX - rect.left) / rect.width) * 100;
87
- const y = ((e.clientY - rect.top) / rect.height) * 100;
88
- setPositions(positions.map((p, i) => i === draggingIdx ? { ...p, x: Math.max(5, Math.min(95, x)), y: Math.max(5, Math.min(95, y)) } : p));
89
- };
90
-
91
- return (
92
- <div className="relative min-h-screen pt-20 pb-16 px-4">
93
- <div className="max-w-5xl mx-auto">
94
- <button
95
- onClick={() => navigate(-1)}
96
- className="inline-flex items-center gap-2 text-stardust/60 hover:text-ancient text-sm mb-6"
97
- >
98
- <ArrowLeft className="w-4 h-4" />
99
- 返回
100
- </button>
101
-
102
- <h1 className="font-display text-3xl text-ancient tracking-wider mb-8">
103
- {isEdit ? '编辑牌阵' : '创建自定义牌阵'}
104
- </h1>
105
-
106
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
107
- {/* Editor Form */}
108
- <div className="space-y-6">
109
- <div className="glass-panel p-6">
110
- <div className="space-y-4">
111
- <div>
112
- <label className="block text-ancient/80 text-sm mb-2">牌阵名称</label>
113
- <input
114
- type="text"
115
- value={name}
116
- onChange={(e) => setName(e.target.value)}
117
- placeholder="例:感情发展三步"
118
- className="w-full bg-void/50 border border-ancient/20 rounded px-3 py-2 text-stardust placeholder-stardust/30 focus:border-ancient/60 focus:outline-none"
119
- />
120
- </div>
121
- <div>
122
- <label className="block text-ancient/80 text-sm mb-2">牌阵描述</label>
123
- <textarea
124
- value={description}
125
- onChange={(e) => setDescription(e.target.value)}
126
- placeholder="描述这个牌阵的用途..."
127
- className="w-full bg-void/50 border border-ancient/20 rounded px-3 py-2 text-stardust placeholder-stardust/30 focus:border-ancient/60 focus:outline-none resize-none h-20"
128
- />
129
- </div>
130
- </div>
131
- </div>
132
-
133
- <div className="glass-panel p-6">
134
- <div className="flex items-center justify-between mb-4">
135
- <h3 className="font-display text-ancient text-sm tracking-wider">牌位({positions.length}/12)</h3>
136
- <button
137
- onClick={addPosition}
138
- disabled={positions.length >= 12}
139
- className="inline-flex items-center gap-1 px-3 py-1 bg-ancient/20 border border-ancient/50 text-ancient rounded-sm text-xs hover:bg-ancient/30 transition-all disabled:opacity-30"
140
- >
141
- <Plus className="w-3 h-3" />
142
- 增加
143
- </button>
144
- </div>
145
-
146
- <div className="space-y-3 max-h-96 overflow-y-auto">
147
- {positions.map((pos, idx) => (
148
- <div key={pos.id} className="bg-abyss/40 border border-ancient/10 rounded p-3 space-y-2">
149
- <div className="flex items-center justify-between">
150
- <span className="text-xs text-ancient/60">#{idx + 1}</span>
151
- <button
152
- onClick={() => removePosition(idx)}
153
- disabled={positions.length <= 2}
154
- className="text-stardust/40 hover:text-crimson disabled:opacity-30"
155
- >
156
- <Trash2 className="w-3 h-3" />
157
- </button>
158
- </div>
159
- <input
160
- type="text"
161
- value={pos.name}
162
- onChange={(e) => updatePosition(idx, 'name', e.target.value)}
163
- placeholder="牌位名称"
164
- className="w-full bg-void/50 border border-ancient/20 rounded px-2 py-1 text-sm text-stardust focus:border-ancient/60 focus:outline-none"
165
- />
166
- <input
167
- type="text"
168
- value={pos.description}
169
- onChange={(e) => updatePosition(idx, 'description', e.target.value)}
170
- placeholder="牌位含义"
171
- className="w-full bg-void/50 border border-ancient/20 rounded px-2 py-1 text-xs text-stardust focus:border-ancient/60 focus:outline-none"
172
- />
173
- </div>
174
- ))}
175
- </div>
176
- </div>
177
-
178
- <div className="flex gap-3">
179
- <button
180
- onClick={handleSave}
181
- disabled={!name.trim() || positions.length < 2}
182
- className="flex-1 inline-flex items-center justify-center gap-2 px-6 py-3 bg-ancient/20 border border-ancient/50 text-ancient hover:bg-ancient/30 transition-all rounded-sm disabled:opacity-30"
183
- >
184
- <Save className="w-4 h-4" />
185
- {isEdit ? '保存修改' : '保存牌阵'}
186
- </button>
187
- {isEdit && (
188
- <button
189
- onClick={handleDelete}
190
- className="inline-flex items-center gap-2 px-4 py-3 border border-crimson/40 text-crimson/70 hover:border-crimson hover:text-crimson transition-all rounded-sm"
191
- >
192
- <Trash2 className="w-4 h-4" />
193
- 删除
194
- </button>
195
- )}
196
- </div>
197
- </div>
198
-
199
- {/* Layout Preview */}
200
- <div className="glass-panel p-6">
201
- <h3 className="font-display text-ancient text-sm tracking-wider mb-4">布局预览</h3>
202
- <p className="text-xs text-stardust/40 mb-4">点击下方画布的牌位可拖拽调整位置</p>
203
- <div
204
- className="relative w-full bg-void/40 border border-ancient/20 rounded-lg"
205
- style={{ aspectRatio: '1', minHeight: '300px' }}
206
- onMouseMove={handlePositionDrag}
207
- onMouseUp={() => setDraggingIdx(null)}
208
- onMouseLeave={() => setDraggingIdx(null)}
209
- >
210
- {positions.map((pos, idx) => (
211
- <div
212
- key={pos.id}
213
- onMouseDown={() => setDraggingIdx(idx)}
214
- className="absolute -translate-x-1/2 -translate-y-1/2 cursor-move group"
215
- style={{ left: `${pos.x}%`, top: `${pos.y}%` }}
216
- >
217
- <div className="w-16 h-24 bg-abyss border-2 border-ancient/60 rounded flex flex-col items-center justify-center text-center p-1 group-hover:border-ancient transition-colors">
218
- <GripVertical className="w-3 h-3 text-ancient/40 absolute -top-3 opacity-0 group-hover:opacity-100" />
219
- <span className="text-[9px] text-ancient/60">#{idx + 1}</span>
220
- <span className="text-[10px] text-stardust truncate w-full">{pos.name}</span>
221
- </div>
222
- </div>
223
- ))}
224
- </div>
225
- </div>
226
- </div>
227
- </div>
228
- </div>
229
- );
230
- }
@@ -1,199 +0,0 @@
1
- import { useState, useMemo } from 'react';
2
- import { Link } from 'react-router-dom';
3
- import { Search, Plus, Sparkles, Filter, X, Star } from 'lucide-react';
4
- import { defaultSpreads } from '@core/data/spreads';
5
- import { getCustomSpreads } from '@/utils/spreadStorage';
6
- import type { Spread } from '@/types';
7
- import { useEffect } from 'react';
8
-
9
- const SCENE_FILTERS: { value: string; label: string }[] = [
10
- { value: 'all', label: '全部' },
11
- { value: 'love', label: '爱情' },
12
- { value: 'career', label: '事业' },
13
- { value: 'wealth', label: '财运' },
14
- { value: 'health', label: '健康' },
15
- { value: 'general', label: '通用' },
16
- ];
17
-
18
- const COMPLEXITY_FILTERS = [
19
- { value: 'all', label: '全部复杂度' },
20
- { value: 'simple', label: '简单 (1-3张)' },
21
- { value: 'medium', label: '中等 (4-7张)' },
22
- { value: 'complex', label: '复杂 (8张+)' },
23
- ];
24
-
25
- interface SpreadLibraryProps {
26
- onSelect?: (spread: Spread) => void;
27
- showCustomButton?: boolean;
28
- }
29
-
30
- export default function SpreadLibrary({ onSelect, showCustomButton = true }: SpreadLibraryProps) {
31
- const [search, setSearch] = useState('');
32
- const [sceneFilter, setSceneFilter] = useState('all');
33
- const [complexityFilter, setComplexityFilter] = useState('all');
34
- const [customSpreads, setCustomSpreads] = useState<Spread[]>([]);
35
-
36
- useEffect(() => {
37
- setCustomSpreads(getCustomSpreads());
38
- }, []);
39
-
40
- const allSpreads = useMemo(() => [...defaultSpreads, ...customSpreads], [customSpreads]);
41
-
42
- const filtered = useMemo(() => {
43
- return allSpreads.filter((spread) => {
44
- if (search && !spread.name.toLowerCase().includes(search.toLowerCase()) && !spread.description.includes(search)) {
45
- return false;
46
- }
47
- if (sceneFilter !== 'all' && spread.scene && !spread.scene.includes(sceneFilter)) {
48
- return false;
49
- }
50
- const cardCount = spread.positions.length;
51
- if (complexityFilter === 'simple' && cardCount > 3) return false;
52
- if (complexityFilter === 'medium' && (cardCount < 4 || cardCount > 7)) return false;
53
- if (complexityFilter === 'complex' && cardCount < 8) return false;
54
- return true;
55
- });
56
- }, [allSpreads, search, sceneFilter, complexityFilter]);
57
-
58
- const presetCount = filtered.filter(s => !s.isCustom).length;
59
- const customCount = filtered.filter(s => s.isCustom).length;
60
-
61
- return (
62
- <div className="space-y-6">
63
- {/* Header */}
64
- <div className="flex flex-wrap items-center justify-between gap-4">
65
- <div>
66
- <h2 className="font-display text-xl text-ancient tracking-wider flex items-center gap-2">
67
- <Sparkles className="w-5 h-5" />
68
- 牌阵库
69
- </h2>
70
- <p className="text-stardust/50 text-xs mt-1">共 {allSpreads.length} 个牌阵 · {presetCount} 预设 · {customCount} 自定义</p>
71
- </div>
72
- {showCustomButton && (
73
- <Link
74
- to="/spread-editor"
75
- className="inline-flex items-center gap-2 px-4 py-2 bg-ancient/20 border border-ancient/50 text-ancient hover:bg-ancient/30 transition-all rounded-sm text-sm"
76
- >
77
- <Plus className="w-4 h-4" />
78
- 创建自定义牌阵
79
- </Link>
80
- )}
81
- </div>
82
-
83
- {/* Search & Filter */}
84
- <div className="glass-panel p-4 space-y-3">
85
- <div className="relative">
86
- <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-stardust/40" />
87
- <input
88
- type="text"
89
- value={search}
90
- onChange={(e) => setSearch(e.target.value)}
91
- placeholder="搜索牌阵名称或描述..."
92
- className="w-full bg-void/50 border border-ancient/20 rounded pl-10 pr-10 py-2 text-sm text-stardust placeholder-stardust/30 focus:border-ancient/60 focus:outline-none"
93
- />
94
- {search && (
95
- <button
96
- onClick={() => setSearch('')}
97
- className="absolute right-3 top-1/2 -translate-y-1/2 text-stardust/40 hover:text-stardust"
98
- >
99
- <X className="w-4 h-4" />
100
- </button>
101
- )}
102
- </div>
103
-
104
- <div className="space-y-2">
105
- <div className="flex items-center gap-2">
106
- <Filter className="w-3 h-3 text-ancient/60" />
107
- <span className="text-xs text-ancient/60">场景</span>
108
- </div>
109
- <div className="flex flex-wrap gap-2">
110
- {SCENE_FILTERS.map((f) => (
111
- <button
112
- key={f.value}
113
- onClick={() => setSceneFilter(f.value)}
114
- className={`px-3 py-1 rounded-full text-xs transition-all ${
115
- sceneFilter === f.value
116
- ? 'bg-ancient/20 border border-ancient/50 text-ancient'
117
- : 'border border-stardust/20 text-stardust/60 hover:border-ancient/30'
118
- }`}
119
- >
120
- {f.label}
121
- </button>
122
- ))}
123
- </div>
124
- </div>
125
-
126
- <div className="space-y-2">
127
- <span className="text-xs text-ancient/60">复杂度</span>
128
- <div className="flex flex-wrap gap-2">
129
- {COMPLEXITY_FILTERS.map((f) => (
130
- <button
131
- key={f.value}
132
- onClick={() => setComplexityFilter(f.value)}
133
- className={`px-3 py-1 rounded-full text-xs transition-all ${
134
- complexityFilter === f.value
135
- ? 'bg-ancient/20 border border-ancient/50 text-ancient'
136
- : 'border border-stardust/20 text-stardust/60 hover:border-ancient/30'
137
- }`}
138
- >
139
- {f.label}
140
- </button>
141
- ))}
142
- </div>
143
- </div>
144
- </div>
145
-
146
- {/* Spread Grid */}
147
- {filtered.length === 0 ? (
148
- <div className="glass-panel p-12 text-center">
149
- <p className="text-stardust/50 text-sm">没有匹配的牌阵</p>
150
- </div>
151
- ) : (
152
- <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
153
- {filtered.map((spread) => (
154
- <SpreadCard key={spread.id} spread={spread} onSelect={onSelect} />
155
- ))}
156
- </div>
157
- )}
158
- </div>
159
- );
160
- }
161
-
162
- function SpreadCard({ spread, onSelect }: { spread: Spread; onSelect?: (s: Spread) => void }) {
163
- const isCustom = spread.isCustom;
164
- const cardCount = spread.positions.length;
165
-
166
- return (
167
- <button
168
- onClick={() => onSelect?.(spread)}
169
- className={`glass-panel p-4 text-left hover:border-ancient/40 transition-all ${
170
- isCustom ? 'border-mystic/30' : ''
171
- }`}
172
- >
173
- <div className="flex items-center justify-between mb-2">
174
- <h3 className="font-display text-stardust text-sm flex items-center gap-2">
175
- {spread.name}
176
- {isCustom && <Star className="w-3 h-3 text-mystic/60" />}
177
- </h3>
178
- <span className="text-xs text-stardust/40">{cardCount}张</span>
179
- </div>
180
- <p className="text-stardust/50 text-xs leading-relaxed mb-3 line-clamp-3">
181
- {spread.description}
182
- </p>
183
- <div className="flex flex-wrap gap-1">
184
- {spread.scene?.slice(0, 2).map((s) => (
185
- <span key={s} className="px-2 py-0.5 rounded text-[10px] bg-ancient/5 border border-ancient/20 text-ancient/60">
186
- {SCENE_FILTERS.find(f => f.value === s)?.label || s}
187
- </span>
188
- ))}
189
- <span className={`px-2 py-0.5 rounded text-[10px] border ${
190
- cardCount <= 3 ? 'border-green-500/20 text-green-400/60' :
191
- cardCount <= 7 ? 'border-yellow-500/20 text-yellow-400/60' :
192
- 'border-red-500/20 text-red-400/60'
193
- }`}>
194
- {cardCount <= 3 ? '简单' : cardCount <= 7 ? '中等' : '复杂'}
195
- </span>
196
- </div>
197
- </button>
198
- );
199
- }
@@ -1,17 +0,0 @@
1
- import SpreadLibrary from './SpreadLibrary';
2
-
3
- export default function SpreadLibraryPage() {
4
- return (
5
- <div className="relative min-h-screen pt-20 pb-16 px-4">
6
- <div className="max-w-6xl mx-auto">
7
- <div className="text-center mb-12">
8
- <h1 className="font-display text-3xl md:text-4xl text-ancient tracking-wider mb-3">
9
- 牌阵库
10
- </h1>
11
- <p className="text-stardust/50 text-sm">选择适合你问题的牌阵,或创建专属的</p>
12
- </div>
13
- <SpreadLibrary />
14
- </div>
15
- </div>
16
- );
17
- }