@huyooo/ai-chat-frontend-react 0.2.14 → 0.2.16

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 (76) hide show
  1. package/dist/index.css +0 -1
  2. package/dist/index.js +1 -5418
  3. package/package.json +4 -5
  4. package/dist/index.css.map +0 -1
  5. package/dist/index.js.map +0 -1
  6. package/src/adapter.ts +0 -68
  7. package/src/components/ChatPanel.tsx +0 -553
  8. package/src/components/common/ConfirmDialog.css +0 -136
  9. package/src/components/common/ConfirmDialog.tsx +0 -91
  10. package/src/components/common/CopyButton.css +0 -22
  11. package/src/components/common/CopyButton.tsx +0 -46
  12. package/src/components/common/IndexingSettings.css +0 -207
  13. package/src/components/common/IndexingSettings.tsx +0 -398
  14. package/src/components/common/SettingsPanel.css +0 -337
  15. package/src/components/common/SettingsPanel.tsx +0 -215
  16. package/src/components/common/Toast.css +0 -50
  17. package/src/components/common/Toast.tsx +0 -38
  18. package/src/components/common/ToggleSwitch.css +0 -52
  19. package/src/components/common/ToggleSwitch.tsx +0 -20
  20. package/src/components/header/ChatHeader.css +0 -285
  21. package/src/components/header/ChatHeader.tsx +0 -376
  22. package/src/components/input/AtFilePicker.css +0 -147
  23. package/src/components/input/AtFilePicker.tsx +0 -519
  24. package/src/components/input/ChatInput.css +0 -283
  25. package/src/components/input/ChatInput.tsx +0 -575
  26. package/src/components/input/DropdownSelector.css +0 -231
  27. package/src/components/input/DropdownSelector.tsx +0 -333
  28. package/src/components/input/ImagePreviewModal.css +0 -124
  29. package/src/components/input/ImagePreviewModal.tsx +0 -118
  30. package/src/components/input/at-views/AtBranchView.tsx +0 -34
  31. package/src/components/input/at-views/AtBrowserView.tsx +0 -34
  32. package/src/components/input/at-views/AtChatsView.tsx +0 -34
  33. package/src/components/input/at-views/AtDocsView.tsx +0 -34
  34. package/src/components/input/at-views/AtFilesView.tsx +0 -168
  35. package/src/components/input/at-views/AtTerminalsView.tsx +0 -34
  36. package/src/components/input/at-views/AtViewStyles.css +0 -143
  37. package/src/components/input/at-views/index.ts +0 -9
  38. package/src/components/message/ContentRenderer.css +0 -9
  39. package/src/components/message/MessageBubble.css +0 -193
  40. package/src/components/message/MessageBubble.tsx +0 -240
  41. package/src/components/message/PartsRenderer.css +0 -12
  42. package/src/components/message/PartsRenderer.tsx +0 -168
  43. package/src/components/message/WelcomeMessage.css +0 -221
  44. package/src/components/message/WelcomeMessage.tsx +0 -93
  45. package/src/components/message/parts/CollapsibleCard.css +0 -80
  46. package/src/components/message/parts/CollapsibleCard.tsx +0 -80
  47. package/src/components/message/parts/ErrorPart.css +0 -9
  48. package/src/components/message/parts/ErrorPart.tsx +0 -40
  49. package/src/components/message/parts/ImagePart.css +0 -49
  50. package/src/components/message/parts/ImagePart.tsx +0 -54
  51. package/src/components/message/parts/SearchPart.css +0 -44
  52. package/src/components/message/parts/SearchPart.tsx +0 -63
  53. package/src/components/message/parts/TextPart.css +0 -579
  54. package/src/components/message/parts/TextPart.tsx +0 -213
  55. package/src/components/message/parts/ThinkingPart.css +0 -9
  56. package/src/components/message/parts/ThinkingPart.tsx +0 -48
  57. package/src/components/message/parts/ToolCallPart.css +0 -246
  58. package/src/components/message/parts/ToolCallPart.tsx +0 -289
  59. package/src/components/message/parts/ToolResultPart.css +0 -67
  60. package/src/components/message/parts/index.ts +0 -13
  61. package/src/components/message/parts/visual-predicate.ts +0 -43
  62. package/src/components/message/parts/visual-render.ts +0 -19
  63. package/src/components/message/parts/visual.ts +0 -12
  64. package/src/components/message/welcome-types.ts +0 -46
  65. package/src/context/AutoRunConfigContext.tsx +0 -13
  66. package/src/context/ChatAdapterContext.tsx +0 -8
  67. package/src/context/ChatInputContext.tsx +0 -40
  68. package/src/context/RenderersContext.tsx +0 -35
  69. package/src/hooks/useChat.ts +0 -1569
  70. package/src/hooks/useImageUpload.ts +0 -345
  71. package/src/hooks/useVoiceInput.ts +0 -454
  72. package/src/hooks/useVoiceToTextInput.ts +0 -87
  73. package/src/index.ts +0 -151
  74. package/src/styles.css +0 -330
  75. package/src/types/index.ts +0 -196
  76. package/src/utils/fileIcon.ts +0 -49
@@ -1,231 +0,0 @@
1
- /**
2
- * DropdownSelector 组件样式
3
- */
4
-
5
- .dropdown-selector {
6
- position: relative;
7
- display: flex;
8
- align-items: center;
9
- gap: 4px;
10
- height: 28px; /* 与 ChatInput 右侧按钮一致 */
11
- padding: 0 8px;
12
- /* 避免用 --chat-muted 做背景(暗色下容易与 input 背景同色) */
13
- background: rgba(0, 0, 0, 0.04);
14
- background: color-mix(in srgb, var(--chat-text, #ccc) 6%, transparent);
15
- border: none;
16
- border-radius: 6px;
17
- font-size: 14px;
18
- color: var(--chat-text-muted, #888);
19
- cursor: pointer;
20
- transition: all 0.15s;
21
- }
22
-
23
- .dropdown-selector:hover:not(.disabled) {
24
- background: rgba(0, 0, 0, 0.06);
25
- background: color-mix(in srgb, var(--chat-text, #ccc) 10%, transparent);
26
- color: var(--chat-text, #ccc);
27
- }
28
-
29
- .dropdown-selector.disabled {
30
- opacity: 0.6;
31
- cursor: not-allowed;
32
- }
33
-
34
- .dropdown-selector .chevron {
35
- color: var(--chat-text-muted, #666);
36
- }
37
-
38
- /* 下拉菜单 */
39
- .dropdown-selector .dropdown-menu {
40
- position: absolute;
41
- left: 0;
42
- right: auto;
43
- min-width: 180px;
44
- max-height: 320px;
45
- overflow-y: auto;
46
- background: var(--chat-dropdown-bg, #252526);
47
- border: 1px solid rgba(255, 255, 255, 0.1);
48
- border-radius: 8px;
49
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
50
- z-index: 9999;
51
- padding: 4px 8px 4px 4px;
52
- display: flex;
53
- flex-direction: column;
54
- gap: 2px;
55
- }
56
-
57
- /* 向上弹出 */
58
- .dropdown-selector .dropdown-menu.dropdown-up {
59
- bottom: 100%;
60
- top: auto;
61
- margin-bottom: 4px;
62
- }
63
-
64
- /* 向下弹出 */
65
- .dropdown-selector .dropdown-menu.dropdown-down {
66
- top: 100%;
67
- bottom: auto;
68
- margin-top: 4px;
69
- }
70
-
71
- /* 右对齐 */
72
- .dropdown-selector .dropdown-menu.dropdown-align-right {
73
- left: auto;
74
- right: 0;
75
- }
76
-
77
- .dropdown-selector .dropdown-item {
78
- display: flex;
79
- align-items: center;
80
- gap: 8px;
81
- width: 100%;
82
- padding: 8px 10px;
83
- border: none;
84
- background: transparent;
85
- border-radius: 4px;
86
- font-size: 14px;
87
- color: var(--chat-text-muted, #999);
88
- cursor: pointer;
89
- transition: all 0.15s;
90
- white-space: nowrap;
91
- }
92
-
93
- .dropdown-selector .dropdown-item:hover {
94
- background: rgba(0, 0, 0, 0.06);
95
- background: color-mix(in srgb, var(--chat-text, #ccc) 10%, transparent);
96
- color: var(--chat-text, #ccc);
97
- }
98
-
99
- .dropdown-selector .dropdown-item.active {
100
- background: rgba(0, 0, 0, 0.08);
101
- background: color-mix(in srgb, var(--chat-text, #ccc) 14%, transparent);
102
- color: var(--chat-text, #fff);
103
- }
104
-
105
- .dropdown-selector .check-icon {
106
- margin-left: auto;
107
- color: var(--chat-text, #ccc);
108
- flex-shrink: 0;
109
- }
110
-
111
- .dropdown-selector .selector-text {
112
- /* max-width: 150px; */
113
- overflow: hidden;
114
- text-overflow: ellipsis;
115
- white-space: nowrap;
116
- }
117
-
118
- /* 空状态 */
119
- .dropdown-selector .dropdown-empty {
120
- padding: 12px 10px;
121
- font-size: 12px;
122
- color: var(--chat-text-muted, #666);
123
- text-align: center;
124
- }
125
-
126
- /* 分组标题 */
127
- .dropdown-selector .group-title {
128
- padding: 8px 10px 4px;
129
- margin-top: 4px;
130
- font-size: 11px;
131
- color: var(--chat-text-muted, #666);
132
- }
133
-
134
- .dropdown-selector .group-title:first-child {
135
- margin-top: 0;
136
- }
137
-
138
- /* 选项标签 */
139
- .dropdown-selector .option-label {
140
- flex: 1;
141
- text-align: left;
142
- }
143
-
144
- /* 右侧容器(标签和勾选图标) */
145
- .dropdown-selector .option-right {
146
- display: flex;
147
- align-items: center;
148
- gap: 8px;
149
- margin-left: auto;
150
- }
151
-
152
- /* 提供商标记 */
153
- .dropdown-selector .provider-badge {
154
- padding: 2px 6px;
155
- font-size: 11px;
156
- font-weight: 500;
157
- color: var(--chat-text-muted, #999);
158
- background: rgba(255, 255, 255, 0.08);
159
- border-radius: 3px;
160
- white-space: nowrap;
161
- }
162
-
163
- .dropdown-selector .provider-badge.native {
164
- color: var(--chat-text-muted, #999);
165
- }
166
-
167
- /* 选项包装器 */
168
- .dropdown-selector .dropdown-item-wrapper {
169
- position: relative;
170
- }
171
-
172
- /* 模型特性提示框(组件内渲染,但使用 fixed 定位) */
173
- .ai-chat-model-tooltip {
174
- /* 比之前更窄一点,避免“太宽” */
175
- min-width: 160px;
176
- max-width: 240px;
177
- width: max-content;
178
- padding: 12px;
179
- background: var(--chat-dropdown-bg, #252526);
180
- border: 1px solid rgba(255, 255, 255, 0.15);
181
- border-radius: 8px;
182
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
183
- pointer-events: none;
184
- }
185
-
186
- .ai-chat-model-tooltip__section {
187
- margin-bottom: 10px;
188
- }
189
-
190
- .ai-chat-model-tooltip__section:last-child {
191
- margin-bottom: 0;
192
- }
193
-
194
- .ai-chat-model-tooltip__title {
195
- font-size: 11px;
196
- font-weight: 500;
197
- color: var(--chat-text-muted, #888);
198
- margin-bottom: 6px;
199
- text-transform: uppercase;
200
- letter-spacing: 0.5px;
201
- }
202
-
203
- .ai-chat-model-tooltip__features {
204
- display: flex;
205
- flex-direction: column;
206
- gap: 4px;
207
- }
208
-
209
- .ai-chat-model-tooltip__feature {
210
- display: flex;
211
- align-items: center;
212
- gap: 6px;
213
- font-size: 13px;
214
- color: var(--chat-text, #ccc);
215
- }
216
-
217
- .ai-chat-model-tooltip__check {
218
- color: #4ade80;
219
- flex-shrink: 0;
220
- }
221
-
222
- .ai-chat-model-tooltip__cost {
223
- font-size: 13px;
224
- color: var(--chat-text, #ccc);
225
- }
226
-
227
- .ai-chat-model-tooltip__description {
228
- font-size: 12px;
229
- color: var(--chat-text-muted, #999);
230
- line-height: 1.5;
231
- }
@@ -1,333 +0,0 @@
1
- /**
2
- * DropdownSelector Component
3
- * 与 Vue 版本 DropdownSelector.vue 保持一致
4
- * 前端只负责渲染,分组数据由后端提供
5
- */
6
-
7
- import { useState, useRef, useCallback, useEffect, useMemo, useLayoutEffect } from 'react';
8
- import { Icon } from '@iconify/react';
9
- import './DropdownSelector.css';
10
-
11
- /** 下拉选项 */
12
- export interface DropdownOption {
13
- value: string;
14
- label: string;
15
- icon?: string;
16
- /** 分组名称(由后端决定,前端只负责渲染) */
17
- group?: string;
18
- /** 模型特性信息(用于 hover 提示) */
19
- tooltip?: {
20
- /** 可用功能列表 */
21
- features?: string[];
22
- /** 开销信息(数组,分行显示) */
23
- cost?: string[];
24
- /** 其他描述信息 */
25
- description?: string;
26
- };
27
- }
28
-
29
- /** 分组后的选项(由后端提供) */
30
- export interface GroupedOptions {
31
- [groupName: string]: DropdownOption[];
32
- }
33
-
34
- interface DropdownSelectorProps {
35
- /** 当前选中的值 */
36
- value: string;
37
- /** 选项列表(扁平列表) */
38
- options?: DropdownOption[];
39
- /** 分组后的选项(优先级高于 options,由后端提供) */
40
- groupedOptions?: GroupedOptions;
41
- /** 占位符文本 */
42
- placeholder?: string;
43
- /** 选择回调 */
44
- onSelect?: (value: string) => void;
45
- /** 是否禁用 */
46
- disabled?: boolean;
47
- /** 下拉菜单对齐方式 */
48
- align?: 'left' | 'right';
49
- }
50
-
51
- export function DropdownSelector({
52
- value,
53
- options,
54
- groupedOptions,
55
- placeholder = '请选择',
56
- onSelect,
57
- disabled = false,
58
- align = 'left',
59
- }: DropdownSelectorProps) {
60
- const selectorRef = useRef<HTMLDivElement>(null);
61
- const tooltipRef = useRef<HTMLDivElement>(null);
62
- const hoveredItemRef = useRef<HTMLElement | null>(null);
63
- const [menuOpen, setMenuOpen] = useState(false);
64
- const [dropdownDirection, setDropdownDirection] = useState<'up' | 'down'>('up');
65
- const [hoveredOption, setHoveredOption] = useState<DropdownOption | null>(null);
66
- const [tooltipPosition, setTooltipPosition] = useState({ top: 0, left: 0 });
67
-
68
- // 检查是否有选项
69
- const hasOptions = useMemo(() => {
70
- if (groupedOptions && Object.keys(groupedOptions).length > 0) {
71
- return Object.values(groupedOptions).some(group => group.length > 0);
72
- }
73
- return options && options.length > 0;
74
- }, [options, groupedOptions]);
75
-
76
- // 当前选中的选项(从扁平列表或分组数据中查找)
77
- const currentOption = useMemo(() => {
78
- if (groupedOptions) {
79
- for (const group of Object.values(groupedOptions)) {
80
- const found = group.find(opt => opt.value === value);
81
- if (found) return found;
82
- }
83
- }
84
- return options?.find(opt => opt.value === value);
85
- }, [value, options, groupedOptions]);
86
-
87
- // 排序后的选项(扁平列表模式使用)
88
- const sortedOptions = useMemo(() => {
89
- if (!options) return [];
90
- return [...options].sort((a, b) => a.label.localeCompare(b.label));
91
- }, [options]);
92
-
93
- /**
94
- * 计算下拉方向(根据可用空间)
95
- */
96
- const calculateDropdownDirection = useCallback((): 'up' | 'down' => {
97
- if (!selectorRef.current) return 'up';
98
- const rect = selectorRef.current.getBoundingClientRect();
99
- const spaceAbove = rect.top;
100
- const spaceBelow = window.innerHeight - rect.bottom;
101
- // 如果上方空间小于 200px 且下方空间更大,则向下弹出
102
- return spaceAbove < 200 && spaceBelow > spaceAbove ? 'down' : 'up';
103
- }, []);
104
-
105
- /**
106
- * 切换菜单
107
- */
108
- const toggleMenu = useCallback(
109
- (e: React.MouseEvent) => {
110
- e.stopPropagation();
111
- if (disabled) return;
112
- if (!menuOpen) {
113
- setDropdownDirection(calculateDropdownDirection());
114
- }
115
- setMenuOpen((prev) => !prev);
116
- },
117
- [menuOpen, calculateDropdownDirection, disabled]
118
- );
119
-
120
- /**
121
- * 选择选项
122
- */
123
- const selectOption = useCallback(
124
- (optValue: string) => {
125
- onSelect?.(optValue);
126
- setMenuOpen(false);
127
- },
128
- [onSelect]
129
- );
130
-
131
- /**
132
- * 处理选项 hover 事件
133
- */
134
- const handleItemHover = useCallback((event: React.MouseEvent<HTMLDivElement>, opt: DropdownOption) => {
135
- const target = event.currentTarget as HTMLElement;
136
- hoveredItemRef.current = target;
137
- setHoveredOption(opt);
138
-
139
- if (!opt.tooltip) {
140
- setTooltipPosition({ top: 0, left: 0 });
141
- return;
142
- }
143
-
144
- if (!target) return;
145
-
146
- const rect = target.getBoundingClientRect();
147
- // 提示框初始位置:右侧,距离 8px
148
- setTooltipPosition({
149
- top: rect.top,
150
- left: rect.right + 8,
151
- });
152
- }, []);
153
-
154
- /**
155
- * 当提示框显示后,调整位置(避免超出视口)
156
- * 使用 requestAnimationFrame 确保 tooltip 已完成渲染
157
- */
158
- useLayoutEffect(() => {
159
- if (!hoveredOption?.tooltip || !hoveredItemRef.current) return;
160
-
161
- // 使用 rAF 确保 tooltip 已渲染完成
162
- const rafId = requestAnimationFrame(() => {
163
- if (!tooltipRef.current || !hoveredItemRef.current) return;
164
-
165
- const tooltip = tooltipRef.current;
166
- const item = hoveredItemRef.current;
167
- const itemRect = item.getBoundingClientRect();
168
- const tooltipRect = tooltip.getBoundingClientRect();
169
-
170
- let top = itemRect.top;
171
- let left = itemRect.right + 8;
172
-
173
- // 检查是否会超出视口右侧
174
- if (left + tooltipRect.width > window.innerWidth) {
175
- // 显示在左侧
176
- left = itemRect.left - tooltipRect.width - 8;
177
- }
178
-
179
- // 检查是否会超出视口底部
180
- if (top + tooltipRect.height > window.innerHeight) {
181
- top = window.innerHeight - tooltipRect.height - 8;
182
- }
183
-
184
- // 检查是否会超出视口顶部
185
- if (top < 8) {
186
- top = 8;
187
- }
188
-
189
- setTooltipPosition({ top, left });
190
- });
191
-
192
- return () => cancelAnimationFrame(rafId);
193
- }, [hoveredOption]);
194
-
195
- /**
196
- * 点击外部关闭菜单
197
- */
198
- useEffect(() => {
199
- const handleClickOutside = (event: MouseEvent) => {
200
- const target = event.target as HTMLElement;
201
- if (selectorRef.current && !selectorRef.current.contains(target)) {
202
- setMenuOpen(false);
203
- }
204
- };
205
-
206
- document.addEventListener('click', handleClickOutside);
207
- return () => document.removeEventListener('click', handleClickOutside);
208
- }, []);
209
-
210
- return (
211
- <div
212
- ref={selectorRef}
213
- className={`dropdown-selector${disabled ? ' disabled' : ''}`}
214
- onClick={!disabled ? toggleMenu : undefined}
215
- >
216
- {currentOption?.icon && <Icon icon={currentOption.icon} width={14} />}
217
- <span className="selector-text">{currentOption?.label || placeholder}</span>
218
- <Icon icon="lucide:chevron-down" width={14} className="chevron" />
219
-
220
- {menuOpen && (
221
- <div
222
- className={`dropdown-menu dropdown-${dropdownDirection}${align === 'right' ? ' dropdown-align-right' : ''}`}
223
- onClick={(e) => e.stopPropagation()}
224
- >
225
- {/* 空状态 */}
226
- {!hasOptions && (
227
- <div className="dropdown-empty">暂无数据</div>
228
- )}
229
-
230
- {/* 分组模式:使用后端返回的分组数据 */}
231
- {groupedOptions && Object.keys(groupedOptions).length > 0 ? (
232
- <>
233
- {Object.entries(groupedOptions).map(([groupName, groupItems]) => (
234
- <div key={groupName}>
235
- <div className="group-title">{groupName}</div>
236
- {groupItems.map((opt) => (
237
- <div
238
- key={opt.value}
239
- className="dropdown-item-wrapper"
240
- onMouseEnter={(e) => handleItemHover(e, opt)}
241
- onMouseLeave={() => {
242
- setHoveredOption(null);
243
- hoveredItemRef.current = null;
244
- }}
245
- >
246
- <button
247
- className={`dropdown-item${value === opt.value ? ' active' : ''}`}
248
- onClick={() => selectOption(opt.value)}
249
- >
250
- {opt.icon && <Icon icon={opt.icon} width={14} />}
251
- <span className="option-label">{opt.label}</span>
252
- <span className="option-right">
253
- {value === opt.value && <Icon icon="lucide:check" width={14} className="check-icon" />}
254
- </span>
255
- </button>
256
- </div>
257
- ))}
258
- </div>
259
- ))}
260
- </>
261
- ) : (
262
- /* 扁平列表模式:无分组数据时使用 */
263
- sortedOptions.map((opt) => (
264
- <div
265
- key={opt.value}
266
- className="dropdown-item-wrapper"
267
- onMouseEnter={(e) => handleItemHover(e, opt)}
268
- onMouseLeave={() => {
269
- setHoveredOption(null);
270
- hoveredItemRef.current = null;
271
- }}
272
- >
273
- <button
274
- className={`dropdown-item${value === opt.value ? ' active' : ''}`}
275
- onClick={() => selectOption(opt.value)}
276
- >
277
- {opt.icon && <Icon icon={opt.icon} width={14} />}
278
- <span className="option-label">{opt.label}</span>
279
- <span className="option-right">
280
- {value === opt.value && <Icon icon="lucide:check" width={14} className="check-icon" />}
281
- </span>
282
- </button>
283
- </div>
284
- ))
285
- )}
286
- </div>
287
- )}
288
-
289
- {/* Hover 提示:不使用 Portal,采用 fixed 定位避免 overflow 裁剪,同时样式保持组件内可控 */}
290
- {hoveredOption?.tooltip && (
291
- <div
292
- ref={tooltipRef}
293
- className="ai-chat-model-tooltip"
294
- style={{
295
- position: 'fixed',
296
- top: `${tooltipPosition.top}px`,
297
- left: `${tooltipPosition.left}px`,
298
- zIndex: 10000,
299
- }}
300
- >
301
- {hoveredOption.tooltip.features && hoveredOption.tooltip.features.length > 0 && (
302
- <div className="ai-chat-model-tooltip__section">
303
- <div className="ai-chat-model-tooltip__title">可用功能</div>
304
- <div className="ai-chat-model-tooltip__features">
305
- {hoveredOption.tooltip.features.map((feature) => (
306
- <div key={feature} className="ai-chat-model-tooltip__feature">
307
- <Icon icon="lucide:check" width={12} className="ai-chat-model-tooltip__check" />
308
- <span>{feature}</span>
309
- </div>
310
- ))}
311
- </div>
312
- </div>
313
- )}
314
- {hoveredOption.tooltip.cost && hoveredOption.tooltip.cost.length > 0 && (
315
- <div className="ai-chat-model-tooltip__section">
316
- <div className="ai-chat-model-tooltip__title">开销</div>
317
- <div className="ai-chat-model-tooltip__cost">
318
- {hoveredOption.tooltip.cost.map((line, i) => (
319
- <div key={i}>{line}</div>
320
- ))}
321
- </div>
322
- </div>
323
- )}
324
- {hoveredOption.tooltip.description && (
325
- <div className="ai-chat-model-tooltip__section">
326
- <div className="ai-chat-model-tooltip__description">{hoveredOption.tooltip.description}</div>
327
- </div>
328
- )}
329
- </div>
330
- )}
331
- </div>
332
- );
333
- }
@@ -1,124 +0,0 @@
1
- /* 遮罩层 */
2
- .image-preview-modal {
3
- position: fixed;
4
- inset: 0;
5
- z-index: 99999;
6
- display: flex;
7
- flex-direction: column;
8
- background: rgb(61 61 61 / 40%);
9
- backdrop-filter: blur(8px);
10
- animation: fadeIn 0.2s ease;
11
- }
12
-
13
- @keyframes fadeIn {
14
- from {
15
- opacity: 0;
16
- }
17
- to {
18
- opacity: 1;
19
- }
20
- }
21
-
22
- /* 顶部栏 */
23
- .preview-header {
24
- position: relative;
25
- z-index: 10;
26
- display: flex;
27
- align-items: center;
28
- justify-content: center;
29
- gap: 12px;
30
- padding: 16px 20px;
31
- min-height: 52px;
32
- }
33
-
34
- .preview-counter {
35
- color: rgba(255, 255, 255, 0.85);
36
- font-size: 14px;
37
- font-variant-numeric: tabular-nums;
38
- }
39
-
40
- .preview-close-btn {
41
- position: absolute;
42
- right: 16px;
43
- top: 50%;
44
- transform: translateY(-50%);
45
- display: flex;
46
- align-items: center;
47
- justify-content: center;
48
- height: 36px;
49
- padding: 0 12px;
50
- background: rgba(255, 255, 255, 0.1);
51
- border: none;
52
- border-radius: 8px;
53
- color: #fff;
54
- cursor: pointer;
55
- transition: all 0.15s;
56
- }
57
-
58
- .preview-close-btn:hover {
59
- background: rgba(255, 255, 255, 0.2);
60
- transform: translateY(-50%) scale(1.05);
61
- }
62
-
63
- .preview-close-btn:active {
64
- transform: translateY(-50%) scale(0.95);
65
- }
66
-
67
- /* 导航按钮 */
68
- .preview-nav-btn {
69
- position: absolute;
70
- top: 50%;
71
- transform: translateY(-50%);
72
- z-index: 10;
73
- display: flex;
74
- align-items: center;
75
- justify-content: center;
76
- height: 48px;
77
- padding: 0 12px;
78
- background: rgba(255, 255, 255, 0.1);
79
- border: none;
80
- border-radius: 8px;
81
- color: #fff;
82
- cursor: pointer;
83
- transition: all 0.15s;
84
- }
85
-
86
- .preview-nav-btn:hover:not(.disabled) {
87
- background: rgba(255, 255, 255, 0.2);
88
- transform: translateY(-50%) scale(1.1);
89
- }
90
-
91
- .preview-nav-btn:active:not(.disabled) {
92
- transform: translateY(-50%) scale(0.95);
93
- }
94
-
95
- .preview-nav-btn.disabled {
96
- opacity: 0.4;
97
- cursor: not-allowed;
98
- }
99
-
100
- .preview-nav-prev {
101
- left: 24px;
102
- }
103
-
104
- .preview-nav-next {
105
- right: 24px;
106
- }
107
-
108
- /* 主图片区域 */
109
- .preview-main {
110
- flex: 1;
111
- display: flex;
112
- align-items: center;
113
- justify-content: center;
114
- overflow: hidden;
115
- padding: 0 80px;
116
- }
117
-
118
- .preview-image {
119
- max-width: calc(100% - 80px);
120
- max-height: calc(100vh - 140px);
121
- object-fit: contain;
122
- border-radius: 8px;
123
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
124
- }