@opendesign-plus-test/components 0.0.1-rc.49 → 0.0.1-rc.50

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 (65) hide show
  1. package/dist/chunk-OElCookieNotice.cjs.js +1 -1
  2. package/dist/chunk-OElCookieNotice.es.js +48 -68
  3. package/dist/components/OHeaderSearch.vue.d.ts +534 -814
  4. package/dist/components/OThemeSwitcher.vue.d.ts +2 -5
  5. package/dist/components/activity/index.d.ts +2 -2
  6. package/dist/components/meeting/index.d.ts +3 -3
  7. package/dist/components.cjs.js +42 -42
  8. package/dist/components.css +1 -1
  9. package/dist/components.es.js +10448 -11464
  10. package/dist/index.d.ts +0 -1
  11. package/package.json +4 -6
  12. package/src/assets/styles/element-plus.scss +204 -0
  13. package/src/assets/svg-icons/icon-delete.svg +1 -5
  14. package/src/components/OHeaderSearch.vue +415 -436
  15. package/src/components/OThemeSwitcher.vue +51 -27
  16. package/src/components/activity/OActivityApproval.vue +10 -6
  17. package/src/components/activity/OActivityForm.vue +5 -3
  18. package/src/components/activity/{OMyActivityCalendar.vue → OActivityMyCalendar.vue} +43 -21
  19. package/src/components/activity/index.ts +4 -4
  20. package/src/components/events/OEventsApply.vue +2 -1
  21. package/src/components/events/OEventsCalendar.vue +7 -5
  22. package/src/components/events/OEventsList.vue +6 -4
  23. package/src/components/meeting/OMeetingCalendar.vue +15 -12
  24. package/src/components/meeting/OMeetingForm.vue +16 -10
  25. package/src/components/meeting/{OMyMeetingCalendar.vue → OMeetingMyCalendar.vue} +73 -38
  26. package/src/components/meeting/OMeetingPlayback.vue +32 -8
  27. package/src/components/meeting/{OSigMeetingCalendar.vue → OMeetingSigCalendar.vue} +6 -3
  28. package/src/components/meeting/components/OMeetingCalendarList.vue +3 -3
  29. package/src/components/meeting/components/OMeetingCalendarSelector.vue +1 -1
  30. package/src/components/meeting/components/OMeetingPlaybackSubtitles.vue +10 -9
  31. package/src/components/meeting/components/OMeetingPlaybackVideo.vue +6 -6
  32. package/src/components/meeting/components/{OSigMeetingAside.vue → OMeetingSigAside.vue} +1 -1
  33. package/src/components/meeting/config.ts +1 -1
  34. package/src/components/meeting/index.ts +8 -8
  35. package/src/i18n/en.ts +2 -12
  36. package/src/i18n/zh.ts +0 -10
  37. package/src/index.ts +0 -1
  38. package/vite.config.ts +1 -5
  39. package/dist/components/search/OSearchInput.vue.d.ts +0 -1003
  40. package/dist/components/search/composables/useImageSearch.d.ts +0 -48
  41. package/dist/components/search/composables/useKeywordHighlight.d.ts +0 -2
  42. package/dist/components/search/composables/useSearchHistory.d.ts +0 -14
  43. package/dist/components/search/index.d.ts +0 -590
  44. package/dist/components/search/internal/HighlightText.vue.d.ts +0 -9
  45. package/dist/components/search/internal/SearchImageInput.vue.d.ts +0 -716
  46. package/dist/components/search/internal/SearchPanel.vue.d.ts +0 -100
  47. package/dist/components/search/types.d.ts +0 -20
  48. package/src/assets/svg-icons/icon-delete-hover.svg +0 -4
  49. package/src/assets/svg-icons/icon-image-close.svg +0 -4
  50. package/src/assets/svg-icons/icon-image-upload.svg +0 -3
  51. package/src/assets/svg-icons/icon-image-zoomin.svg +0 -3
  52. package/src/assets/svg-icons/icon-refresh.svg +0 -3
  53. package/src/components/search/OSearchInput.vue +0 -463
  54. package/src/components/search/composables/useImageSearch.ts +0 -157
  55. package/src/components/search/composables/useKeywordHighlight.ts +0 -30
  56. package/src/components/search/composables/useSearchHistory.ts +0 -75
  57. package/src/components/search/index.ts +0 -23
  58. package/src/components/search/internal/HighlightText.vue +0 -37
  59. package/src/components/search/internal/SearchImageInput.vue +0 -488
  60. package/src/components/search/internal/SearchPanel.vue +0 -430
  61. package/src/components/search/types.ts +0 -25
  62. /package/dist/components/activity/{OMyActivityCalendar.vue.d.ts → OActivityMyCalendar.vue.d.ts} +0 -0
  63. /package/dist/components/meeting/{OMyMeetingCalendar.vue.d.ts → OMeetingMyCalendar.vue.d.ts} +0 -0
  64. /package/dist/components/meeting/{OSigMeetingCalendar.vue.d.ts → OMeetingSigCalendar.vue.d.ts} +0 -0
  65. /package/dist/components/meeting/components/{OSigMeetingAside.vue.d.ts → OMeetingSigAside.vue.d.ts} +0 -0
@@ -1,24 +1,18 @@
1
1
  <script setup lang="ts">
2
- import { computed, ref, toRef, watch } from 'vue';
3
- import { OIcon } from '@opensig/opendesign';
4
- import { onClickOutside, useDebounceFn } from '@vueuse/core';
2
+ import { computed, onMounted, ref, watch } from 'vue';
3
+ import { OIcon, OInput, ODivider } from '@opensig/opendesign';
4
+ import { onClickOutside } from '@vueuse/core';
5
5
  import { useScreen } from '@opendesign-plus/composables';
6
6
 
7
- import SearchImageInput from './search/internal/SearchImageInput.vue';
8
- import SearchPanel from './search/internal/SearchPanel.vue';
9
- import { useSearchHistory } from './search/composables/useSearchHistory';
10
- import { useI18n } from '@/i18n';
11
- import type {
12
- OSearchPayload,
13
- OSearchRecommendItem,
14
- OSearchUploadImageFn,
15
- } from './search/types';
16
-
7
+ import IconClose from '~icons/components/icon-close.svg';
17
8
  import IconSearch from '~icons/components/icon-header-search.svg';
9
+ import IconDelete from '~icons/components/icon-header-delete.svg';
10
+ import IconDeleteAll from '~icons/components/icon-delete.svg';
18
11
  import IconBack from '~icons/components/icon-header-back.svg';
19
12
 
13
+ import { useI18n } from '@/i18n';
14
+
20
15
  export interface OHeaderSearchPropsT {
21
- /** ---- Backward-compatible props ---- */
22
16
  modelValue?: string;
23
17
  placeholder?: string; // 搜索框默认提示
24
18
  expandedPlaceholder?: string; // 搜索框展开后提示
@@ -26,39 +20,28 @@ export interface OHeaderSearchPropsT {
26
20
  clearable?: boolean; // 是否显示清除按钮,默认显示
27
21
  historyItems?: string[]; // 搜索历史记录
28
22
  maxHistoryCount?: number; // 最多保存的搜索历史记录数,默认 6 条
29
- storeHistory?: boolean; // 是否使用 localStorage 存储搜索历史记录,存储之后初始化时会自动加载搜索历史记录,默认为 false
23
+ storeHistory?: boolean; // 是否使用 localStorage 存储搜索历史记录,存储之后初始化时会自动加载搜索历史记录,默认为 false
30
24
  historyTitle?: string; // 历史记录标题
31
25
  storageKey?: string; // localStorage 存储搜索历史记录的 key,默认为 search-history
32
26
  hotItems?: string[]; // 热门搜索
33
27
  hotTitle?: string; // 推荐搜索标题
34
- /** Legacy plain-string recommend list. Shown when input is empty. */
35
- recommendItems?: string[];
36
- searchUrl?: string;
37
- searchUrlOpenBlank?: boolean;
38
- searchTextMobile?: string;
39
- imagePlaceholder?: string;
40
- enableImageSearch?: boolean;
41
- imageUrl?: string;
42
- uploadImage?: OSearchUploadImageFn;
43
- maxImageSize?: number;
44
- imageUploadTooltip?: string;
45
- suggestItems?: OSearchRecommendItem[];
46
- onestepItems?: OSearchRecommendItem[];
47
- suggestTitle?: string;
48
- onestepTitle?: string;
49
- noDataText?: string;
50
- highlightKeyword?: boolean;
51
- /** Debounce ms for the `input` event */
52
- debounce?: number;
53
- /** Auto-record history on search; default true */
54
- autoSaveHistory?: boolean;
55
- /** Show "no data" empty state in suggest section while typing */
56
- showSuggestEmpty?: boolean;
57
- allowedImageTypes?: string[];
58
- /** 强制指定移动端模式,不传时由内部 lePadV 断点自动判断 */
59
- mobile?: boolean;
28
+ recommendItems?: string[]; // 推荐搜索
29
+ searchUrl?: string; // 搜索页面 url,不为空点击热门搜索、历史记录、推荐搜索和回车搜索时自动打开页面
30
+ searchUrlOpenBlank?: boolean; // 是否在新窗口打开搜索页面,默认为 true
31
+ searchTextMobile?: string; // 手机端搜索按钮文字,默认为搜索
60
32
  }
61
33
 
34
+ export interface OHeaderSearchEmitsT {
35
+ (e: 'update:modelValue', value: string): void;
36
+ (e: 'update:historyItems', value: string[]): void;
37
+ (e: 'clear'): void;
38
+ (e: 'search', value: string): void;
39
+ (e: 'delete-history', value: string[]): void;
40
+ (e: 'delete-history-item', value: string): void;
41
+ }
42
+ const { lePadV } = useScreen();
43
+ const { t } = useI18n();
44
+
62
45
  const props = withDefaults(defineProps<OHeaderSearchPropsT>(), {
63
46
  modelValue: '',
64
47
  expandDirection: 'left',
@@ -70,352 +53,235 @@ const props = withDefaults(defineProps<OHeaderSearchPropsT>(), {
70
53
  hotItems: () => [],
71
54
  recommendItems: () => [],
72
55
  searchUrlOpenBlank: true,
73
- enableImageSearch: false,
74
- imageUrl: '',
75
- maxImageSize: 10 * 1024 * 1024,
76
- suggestItems: () => [],
77
- onestepItems: () => [],
78
- highlightKeyword: true,
79
- debounce: 300,
80
- autoSaveHistory: true,
81
- showSuggestEmpty: true,
82
56
  });
83
57
 
84
- const emit = defineEmits<{
85
- (e: 'update:modelValue', value: string): void;
86
- (e: 'update:historyItems', value: string[]): void;
87
- (e: 'update:imageUrl', url: string): void;
88
- (e: 'focus'): void;
89
- (e: 'blur'): void;
90
- (e: 'input', val: string): void;
91
- (e: 'clear'): void;
92
- /** Backward compatible: previously `(val: string)`. Now emits payload. */
93
- (e: 'search', payload: OSearchPayload): void;
94
- (e: 'recommend-click', item: OSearchRecommendItem | string): void;
95
- (e: 'onestep-click', item: OSearchRecommendItem): void;
96
- (e: 'history-click', val: string): void;
97
- (e: 'hot-click', val: string): void;
98
- (e: 'hot-refresh'): void;
99
- (e: 'delete-history', value: string[]): void;
100
- (e: 'delete-history-item', value: string): void;
101
- (e: 'image-upload-start', file: File): void;
102
- (e: 'image-upload-success', url: string, file: File): void;
103
- (e: 'image-upload-error', error: unknown, file: File): void;
104
- (e: 'image-validate-error', reason: 'size' | 'type', file: File): void;
105
- }>();
58
+ const emit = defineEmits<OHeaderSearchEmitsT>();
106
59
 
107
- const { lePadV } = useScreen();
108
- const { t } = useI18n();
109
-
110
- // mobile=true → 强制移动端;未传或 false → 回退到内部断点 lePadV
111
- const isMobileMode = computed(() => props.mobile === true || lePadV.value);
112
-
113
- const wrapperRef = ref<HTMLElement | null>(null);
114
- const inputRef = ref<InstanceType<typeof SearchImageInput>>();
60
+ const searchInput = ref(props.modelValue);
61
+ const searchHistoryItems = ref(props.historyItems);
115
62
  const isShowDrawer = ref(false);
116
- const internalImageStaged = ref(false);
117
- const internalImageUrl = ref('');
118
- const hasImage = computed(() => !!props.imageUrl || internalImageStaged.value);
119
-
120
- const innerValue = computed({
121
- get: () => props.modelValue,
122
- set: (val: string) => emit('update:modelValue', val),
123
- });
63
+ const inputRef = ref();
124
64
 
125
- const historyItemsRef = toRef(props, 'historyItems');
126
- const storageKeyRef = toRef(props, 'storageKey');
127
- const storeHistoryRef = toRef(props, 'storeHistory');
128
- const maxHistoryRef = toRef(props, 'maxHistoryCount');
129
-
130
- const history = useSearchHistory({
131
- initial: historyItemsRef,
132
- storageKey: storageKeyRef,
133
- storeHistory: storeHistoryRef,
134
- maxHistoryCount: maxHistoryRef,
135
- onChange: (items) => emit('update:historyItems', items),
65
+ const isShowClearIcon = computed(() => {
66
+ return (!lePadV.value && isShowDrawer.value) || (lePadV.value && searchInput.value);
136
67
  });
137
68
 
138
- const placeholder = computed(() => {
139
- if (props.imageUrl) {
140
- return props.imagePlaceholder ?? t('search.extendedPlaceholder');
141
- }
142
- if (isShowDrawer.value && props.enableImageSearch) {
143
- return props.imagePlaceholder ?? t('search.imagePlaceholder');
69
+ watch(
70
+ () => props.modelValue,
71
+ (val) => {
72
+ if (searchInput.value !== val) {
73
+ searchInput.value = val;
74
+ }
144
75
  }
145
- if (isShowDrawer.value) {
146
- return props.expandedPlaceholder ?? t('search.expandedPlaceholder');
76
+ );
77
+
78
+ watch(
79
+ () => searchInput.value,
80
+ (val) => {
81
+ emit('update:modelValue', val);
147
82
  }
148
- return props.placeholder ?? t('search.placeholder');
149
- });
83
+ );
150
84
 
151
- // debounce input event
152
- const emitInputDebounced = useDebounceFn((val: string) => emit('input', val), () => props.debounce);
153
- watch(innerValue, (val) => emitInputDebounced(val));
85
+ watch(
86
+ () => props.historyItems,
87
+ (val) => {
88
+ if (searchHistoryItems.value !== val) {
89
+ searchHistoryItems.value = val;
90
+ }
91
+ }
92
+ );
154
93
 
155
- // click outside (desktop only)
156
- onClickOutside(wrapperRef, () => {
157
- if (isMobileMode.value) return;
158
- closeDrawer();
94
+ watch(
95
+ () => searchHistoryItems.value,
96
+ (val) => {
97
+ emit('update:historyItems', val);
98
+ }
99
+ );
100
+
101
+ onMounted(() => {
102
+ if (props.storeHistory && props.storageKey) {
103
+ try {
104
+ const history = JSON.parse(localStorage.getItem(props.storageKey) || '[]');
105
+ if (Array.isArray(history) && history.length) {
106
+ searchHistoryItems.value = Array.from(new Set([...searchHistoryItems.value, ...history]));
107
+ }
108
+ } catch {
109
+ // nothing
110
+ }
111
+ }
159
112
  });
160
113
 
161
- const openDrawer = () => {
114
+ const onShowDrawer = () => {
162
115
  isShowDrawer.value = true;
163
116
  };
164
117
 
165
- const closeDrawer = () => {
166
- if (isMobileMode.value) return;
167
- isShowDrawer.value = false;
168
- };
169
-
170
- const handleFocus = () => {
171
- openDrawer();
172
- emit('focus');
173
- };
174
-
175
- const handleBlur = () => {
176
- emit('blur');
177
- };
178
-
179
- const runSearch = async () => {
180
- if (inputRef.value?.getIsUploading?.()) {
181
- await inputRef.value.awaitUpload?.();
118
+ const onSearch = () => {
119
+ const input = searchInput.value.trim();
120
+ if (!input) {
121
+ return;
182
122
  }
183
- const keyword = innerValue.value.trim();
184
- const imageUrl = internalImageUrl.value || inputRef.value?.getUploadedUrl?.() || props.imageUrl;
185
-
186
- if (!keyword && !imageUrl) return;
187
123
 
188
- if (props.autoSaveHistory && keyword) {
189
- history.push(keyword);
124
+ isShowDrawer.value = false;
125
+ searchHistoryItems.value.unshift(input);
126
+ searchHistoryItems.value = Array.from(new Set(searchHistoryItems.value));
127
+ if (searchHistoryItems.value.length > props.maxHistoryCount) {
128
+ searchHistoryItems.value.pop();
190
129
  }
191
130
 
192
- isShowDrawer.value = false;
193
- emit('search', { keyword, imageUrl: imageUrl || undefined });
131
+ if (props.storeHistory && props.storeHistory) {
132
+ localStorage.setItem(props.storageKey, JSON.stringify(searchHistoryItems.value));
133
+ }
134
+ emit('search', input);
194
135
 
195
136
  if (props.searchUrl) {
196
- const params = new URLSearchParams();
197
- if (keyword) params.set('q', keyword);
198
- if (imageUrl) params.set('imageUrl', imageUrl);
199
- const sep = props.searchUrl.includes('?') ? '&' : '?';
200
- const url = `${props.searchUrl}${sep}${params.toString()}`;
201
- if (typeof window !== 'undefined') {
202
- window.open(url, props.searchUrlOpenBlank ? '_blank' : '_self', 'noopener,noreferrer');
203
- }
137
+ window.open(props.searchUrl + input, props.searchUrlOpenBlank ? '_blank' : '_self', 'noopener noreferrer');
204
138
  }
205
139
  };
206
140
 
207
- const handleClear = () => {
208
- innerValue.value = '';
141
+ const onClear = () => {
142
+ searchInput.value = '';
209
143
  emit('clear');
210
- inputRef.value?.focus?.();
211
- };
212
-
213
- const handleSuggestClick = (item: OSearchRecommendItem) => {
214
- emit('recommend-click', item);
215
- innerValue.value = item.key;
216
- runSearch();
217
- };
218
-
219
- const handleRecommendClick = (val: string) => {
220
- emit('recommend-click', val);
221
- innerValue.value = val;
222
- runSearch();
144
+ if (!lePadV.value) {
145
+ isShowDrawer.value = false;
146
+ }
223
147
  };
224
148
 
225
- const handleOnestepClick = (item: OSearchRecommendItem) => {
226
- emit('onestep-click', item);
227
- if (item.path && typeof window !== 'undefined') {
228
- window.open(item.path, '_blank', 'noopener,noreferrer');
149
+ const onDeleteHistory = () => {
150
+ const history = [...searchHistoryItems.value];
151
+ searchHistoryItems.value = [];
152
+ if (props.storeHistory && props.storeHistory) {
153
+ localStorage.removeItem(props.storageKey);
229
154
  }
230
- };
231
155
 
232
- const handleHistoryClick = (val: string) => {
233
- emit('history-click', val);
156
+ emit('delete-history', history);
234
157
  };
235
158
 
236
- const handleHotClick = (val: string) => {
237
- emit('hot-click', val);
238
- };
159
+ const onDeleteHistoryItem = (val: string) => {
160
+ searchHistoryItems.value = searchHistoryItems.value.filter((item) => item !== val);
161
+ if (props.storeHistory && props.storeHistory) {
162
+ if (searchHistoryItems.value.length) {
163
+ localStorage.setItem(props.storageKey, JSON.stringify(searchHistoryItems.value));
164
+ } else {
165
+ localStorage.removeItem(props.storageKey);
166
+ }
167
+ }
239
168
 
240
- const handleHistoryRemove = (val: string) => {
241
- history.remove(val);
242
169
  emit('delete-history-item', val);
243
170
  };
244
171
 
245
- const handleHistoryClear = () => {
246
- const removed = [...history.items.value];
247
- history.clearAll();
248
- emit('delete-history', removed);
172
+ const onWordSearch = (val: string) => {
173
+ searchInput.value = val;
174
+ onSearch();
249
175
  };
250
176
 
251
- const handleBack = () => {
252
- innerValue.value = '';
253
- internalImageStaged.value = false;
254
- emit('update:imageUrl', '');
177
+ const onBack = () => {
178
+ searchInput.value = '';
255
179
  isShowDrawer.value = false;
256
180
  };
257
181
 
258
- const handleImageUploadStart = (file: File) => {
259
- internalImageStaged.value = true;
260
- internalImageUrl.value = '';
261
- emit('image-upload-start', file);
262
- };
263
-
264
- const handleImageUploadSuccess = (url: string, file: File) => {
265
- internalImageUrl.value = url;
266
- emit('update:imageUrl', url);
267
- emit('image-upload-success', url, file);
268
- };
269
-
270
- const handleImageClear = () => {
271
- internalImageStaged.value = false;
272
- internalImageUrl.value = '';
273
- emit('update:imageUrl', '');
274
- };
275
-
276
- const showPanel = computed(() => {
277
- if (!isShowDrawer.value) return false;
278
- if (hasImage.value) return false;
279
- return true;
280
- });
281
-
282
- defineExpose({
283
- focus: () => inputRef.value?.focus?.(),
284
- blur: () => inputRef.value?.blur?.(),
285
- open: openDrawer,
286
- close: closeDrawer,
287
- search: runSearch,
288
- });
182
+ const posWrapper = ref();
183
+ onClickOutside(posWrapper, onClear);
289
184
  </script>
290
185
 
291
186
  <template>
292
- <div class="o-header-search" :class="{ 'is-mobile-mode': isMobileMode }">
187
+ <div class="o-header-search">
293
188
  <div
294
- ref="wrapperRef"
295
- class="o-header-search-pos"
189
+ ref="posWrapper"
296
190
  :class="{
297
- 'is-pc': !isMobileMode,
298
- 'is-mobile': isMobileMode,
299
- 'is-left': expandDirection === 'left',
300
- 'is-right': expandDirection === 'right',
301
- 'is-focus': isShowDrawer,
191
+ 'o-header-search-input-pc-wrapper': !lePadV,
192
+ 'o-header-search-input-pc-wrapper-left': !lePadV && expandDirection === 'left',
193
+ 'o-header-search-input-pc-wrapper-right': !lePadV && expandDirection === 'right',
194
+ 'o-header-search-input-mobile-wrapper': lePadV,
195
+ focus: isShowDrawer,
302
196
  }"
303
197
  >
304
- <div class="o-header-search-row" :class="{ 'is-focus': isShowDrawer }">
305
- <OIcon v-if="isMobileMode && isShowDrawer" class="o-header-search-back-icon" @click="handleBack">
198
+ <div class="o-header-search-input-wrapper" :class="{ focus: isShowDrawer }">
199
+ <OIcon v-if="lePadV && isShowDrawer" class="o-header-search-icon" @click="onBack">
306
200
  <IconBack />
307
201
  </OIcon>
308
202
 
309
- <SearchImageInput
203
+ <OInput
310
204
  ref="inputRef"
311
- v-model="innerValue"
312
- :image-url="imageUrl"
313
- :placeholder="placeholder"
314
- :enable-image-search="enableImageSearch"
315
- :upload-image="uploadImage"
316
- :max-image-size="maxImageSize"
317
- :image-upload-tooltip="imageUploadTooltip"
318
- :clearable="clearable && isShowDrawer"
319
- :expanded="isShowDrawer && hasImage"
320
- :allowed-image-types="allowedImageTypes"
321
- size="medium"
205
+ v-model="searchInput"
322
206
  class="o-header-search-input"
323
- :class="{ 'is-collapsed': !isShowDrawer }"
324
- @update:imageUrl="(url: string) => emit('update:imageUrl', url)"
325
- @focus="handleFocus"
326
- @blur="handleBlur"
327
- @enter="runSearch"
328
- @clear="handleClear"
329
- @image-clear="handleImageClear"
330
- @image-upload-start="handleImageUploadStart"
331
- @image-upload-success="handleImageUploadSuccess"
332
- @image-upload-error="(error: unknown, file: File) => emit('image-upload-error', error, file)"
333
- @image-validate-error="(reason: 'size' | 'type', file: File) => emit('image-validate-error', reason, file)"
207
+ :placeholder="isShowDrawer ? expandedPlaceholder ?? t('search.expandedPlaceholder') : placeholder ?? t('search.placeholder')"
208
+ @focus="onShowDrawer"
209
+ @keyup.enter="onSearch"
334
210
  >
335
- <template #prefix><slot name="input-prefix" /></template>
336
- <template #suffix="slotProps"><slot name="input-suffix" v-bind="slotProps" /></template>
337
- <template v-if="$slots['image-preview']" #preview="slotProps">
338
- <slot name="image-preview" v-bind="slotProps" />
211
+ <template #prefix>
212
+ <slot name="input-prefix">
213
+ <OIcon class="o-header-search-icon">
214
+ <IconSearch />
215
+ </OIcon>
216
+ </slot>
339
217
  </template>
340
- </SearchImageInput>
341
218
 
342
- <span v-if="isMobileMode && isShowDrawer" class="o-header-search-text" @click="runSearch">
343
- {{ searchTextMobile ?? t('search') }}
344
- </span>
219
+ <template #suffix>
220
+ <slot name="input-suffix">
221
+ <OIcon v-if="clearable && isShowClearIcon" class="o-header-search-icon close" @click="onClear">
222
+ <IconClose />
223
+ </OIcon>
224
+ </slot>
225
+ </template>
226
+ </OInput>
227
+
228
+ <span v-if="lePadV && isShowDrawer" class="o-header-search-text" @click="onSearch">{{ searchTextMobile ?? t('search') }}</span>
345
229
  </div>
346
230
 
347
- <Transition name="o-header-search-drawer">
348
- <div v-if="showPanel" class="o-header-search-drawer">
349
- <slot
350
- name="drawer"
351
- :recommend-items="recommendItems"
352
- :history-items="history.items.value"
353
- :hot-items="hotItems"
354
- :suggest-items="suggestItems"
355
- :onestep-items="onestepItems"
356
- :keyword="modelValue"
357
- >
358
- <SearchPanel
359
- :keyword="modelValue"
360
- :onestep-items="onestepItems"
361
- :onestep-title="onestepTitle"
362
- :suggest-items="suggestItems"
363
- :suggest-title="suggestTitle"
364
- :recommend-items="recommendItems"
365
- :history-items="history.items.value"
366
- :history-title="historyTitle"
367
- :hot-items="hotItems"
368
- :hot-title="hotTitle"
369
- :no-data-text="noDataText"
370
- :highlight-keyword="highlightKeyword"
371
- :hide-on-keyword="true"
372
- :show-suggest-empty="showSuggestEmpty"
373
- @onestep-click="handleOnestepClick"
374
- @suggest-click="handleSuggestClick"
375
- @recommend-click="handleRecommendClick"
376
- @history-click="handleHistoryClick"
377
- @history-remove="handleHistoryRemove"
378
- @history-clear="handleHistoryClear"
379
- @hot-click="handleHotClick"
380
- @hot-refresh="emit('hot-refresh')"
381
- >
382
- <template v-if="$slots['recommend-header']" #recommend-header="slotProps">
383
- <slot name="recommend-header" v-bind="slotProps" />
384
- </template>
385
- <template v-if="$slots['recommend-content']" #recommend-content="slotProps">
386
- <slot name="recommend-content" v-bind="slotProps" />
387
- </template>
388
- <template v-if="$slots['onestep-header']" #onestep-header="slotProps">
389
- <slot name="onestep-header" v-bind="slotProps" />
390
- </template>
391
- <template v-if="$slots['onestep-content']" #onestep-content="slotProps">
392
- <slot name="onestep-content" v-bind="slotProps" />
393
- </template>
394
- <template v-if="$slots['suggest-header']" #suggest-header="slotProps">
395
- <slot name="suggest-header" v-bind="slotProps" />
396
- </template>
397
- <template v-if="$slots['suggest-content']" #suggest-content="slotProps">
398
- <slot name="suggest-content" v-bind="slotProps" />
399
- </template>
400
- <template v-if="$slots['history-header']" #history-header="slotProps">
401
- <slot name="history-header" v-bind="slotProps" />
402
- </template>
403
- <template v-if="$slots['history-content']" #history-content="slotProps">
404
- <slot name="history-content" v-bind="slotProps" />
405
- </template>
406
- <template v-if="$slots['hot-header']" #hot-header="slotProps">
407
- <slot name="hot-header" v-bind="slotProps" />
408
- </template>
409
- <template v-if="$slots['hot-content']" #hot-content="slotProps">
410
- <slot name="hot-content" v-bind="slotProps" />
411
- </template>
412
- </SearchPanel>
413
- </slot>
414
- </div>
415
- </Transition>
231
+ <div v-show="isShowDrawer" class="o-header-search-drawer">
232
+ <slot name="drawer" :recommend-items="recommendItems" :history-items="searchHistoryItems" :hot-items="hotItems">
233
+ <!-- 搜索推荐 -->
234
+ <div v-if="recommendItems.length" class="o-header-search-recommend-container">
235
+ <slot name="recommend-header" :recommend="recommendItems" />
236
+ <slot name="recommend-content" :recommend="recommendItems">
237
+ <div v-for="item in recommendItems" class="o-header-search-recommend-item" :key="item" @click="onWordSearch(item)">
238
+ {{ item }}
239
+ </div>
240
+ </slot>
241
+ </div>
242
+
243
+ <!-- 历史记录 -->
244
+ <div v-else-if="searchHistoryItems.length" class="o-header-search-history-container">
245
+ <slot name="history-header" :history="searchHistoryItems">
246
+ <div class="o-header-search-history-header">
247
+ <span class="o-header-search-history-header-title">{{ historyTitle ?? t('search.history') }}</span>
248
+ <OIcon class="o-header-search-icon" @click="onDeleteHistory">
249
+ <IconDeleteAll />
250
+ </OIcon>
251
+ </div>
252
+ </slot>
253
+
254
+ <slot name="history-content" :history="searchHistoryItems">
255
+ <div class="o-header-search-history-item-container">
256
+ <div v-for="item in searchHistoryItems" :key="item" class="o-header-search-history-item" @click="onWordSearch(item)">
257
+ <span class="o-header-search-history-item-text">{{ item }}</span>
258
+ <OIcon class="o-header-search-history-item-icon" @click.stop="onDeleteHistoryItem(item)">
259
+ <IconDelete class="icon-delete" />
260
+ </OIcon>
261
+ </div>
262
+ </div>
263
+ </slot>
264
+ </div>
265
+
266
+ <ODivider v-if="(recommendItems.length || searchHistoryItems.length) && hotItems.length" class="o-header-search-drawer-divider" />
267
+
268
+ <!-- 热门搜索 -->
269
+ <div v-if="hotItems.length" class="o-header-search-hot-container">
270
+ <slot name="hot-header" :hot="hotItems">
271
+ <div class="o-header-search-hot-header">{{ hotTitle ?? t('search.hot') }}</div>
272
+ </slot>
273
+
274
+ <slot name="hot-content" :hot="hotItems">
275
+ <div class="o-header-search-hot-item-container">
276
+ <div v-for="item in hotItems" :key="item" class="o-header-search-hot-item" @click="onWordSearch(item)">{{ item }}</div>
277
+ </div>
278
+ </slot>
279
+ </div>
280
+ </slot>
281
+ </div>
416
282
  </div>
417
283
 
418
- <OIcon v-if="isMobileMode" class="o-header-search-mobile-icon" @click="openDrawer">
284
+ <OIcon v-if="lePadV" class="o-header-search-icon-mobile" @click="onShowDrawer">
419
285
  <IconSearch />
420
286
  </OIcon>
421
287
  </div>
@@ -427,112 +293,119 @@ defineExpose({
427
293
  width: 160px;
428
294
  height: 32px;
429
295
 
430
- @include respond-to('<=laptop') {
296
+ @include respond('<=laptop') {
431
297
  width: 120px;
432
298
  }
433
299
 
434
- @include respond-to('<=pad_v') {
300
+ @media screen and (max-width: 1080px) {
435
301
  width: 24px;
436
302
  height: 24px;
437
- margin-left: auto;
438
303
  }
439
304
  }
440
305
 
441
- .o-header-search-pos {
442
- position: absolute;
443
- top: 0;
444
- width: fit-content;
445
- background-color: var(--o-color-fill2);
446
- z-index: 100;
306
+ .o-header-search-icon {
307
+ cursor: pointer;
308
+ color: var(--o-color-info1);
309
+ @include h4;
447
310
 
448
- &.is-left {
449
- right: 0;
450
- }
451
- &.is-right {
452
- left: 0;
453
- }
454
- &.is-mobile {
455
- display: none;
311
+ @include respond('<=pad_v') {
312
+ font-size: 20px;
456
313
  }
457
- &.is-pc.is-focus {
458
- box-shadow: var(--o-shadow-2);
459
- top: calc(-1 * var(--o-gap-4));
460
- border-radius: var(--o-radius-xs);
314
+
315
+ .close {
316
+ @include x-svg-hover;
461
317
  }
318
+ }
462
319
 
463
- &.is-mobile.is-focus {
464
- position: fixed;
465
- top: 0;
466
- right: 0;
467
- bottom: 0;
468
- left: 0;
469
- width: 100%;
320
+ .o-header-search-icon-mobile {
321
+ font-size: 24px;
322
+ line-height: 28px;
323
+ color: var(--o-color-info1);
324
+ cursor: pointer;
325
+ display: none;
326
+
327
+ @include respond('<=pad_v') {
470
328
  display: block;
471
- height: 100vh;
472
- background-color: var(--o-color-fill2);
473
- z-index: 100;
474
- overflow-y: auto;
475
329
  }
476
330
  }
477
331
 
478
- .o-header-search-row {
479
- display: flex;
480
- align-items: start;
332
+ .o-header-search-input-pc-wrapper {
333
+ position: absolute;
334
+ right: 0;
335
+ top: 0;
336
+ width: fit-content;
337
+ background-color: var(--o-color-fill2);
338
+ z-index: 100;
339
+ }
481
340
 
482
- &.is-focus {
483
- padding: var(--o-gap-4);
484
- border-radius: var(--o-radius-xs);
341
+ .o-header-search-input-pc-wrapper-left {
342
+ right: 0;
343
+ }
485
344
 
486
- @include respond-to('<=pad_v') {
487
- gap: var(--o-gap-4);
488
- padding: 10px var(--o-gap-4) var(--o-gap-4) var(--o-gap-4);
489
- border-radius: unset;
490
- }
491
- }
345
+ .o-header-search-input-pc-wrapper-right {
346
+ left: 0;
492
347
  }
493
348
 
494
- .o-header-search-input {
495
- width: 160px;
496
- transition: width var(--o-duration-m2) var(--o-easing-standard-in);
349
+ .o-header-search-input-pc-wrapper.focus {
350
+ box-shadow: var(--o-shadow-2);
351
+ top: calc(-1 * var(--o-gap-4));
352
+ }
497
353
 
498
- @include respond-to('<=laptop') {
499
- width: 120px;
500
- }
354
+ .o-header-search-input-mobile-wrapper {
355
+ display: none;
501
356
  }
502
357
 
503
- .o-header-search-row.is-focus .o-header-search-input {
504
- width: 480px;
358
+ .o-header-search-input-mobile-wrapper.focus {
359
+ position: fixed;
360
+ top: 0;
361
+ right: 0;
362
+ bottom: 0;
363
+ left: 0;
364
+ display: block;
365
+ height: 100vh;
366
+ background-color: var(--o-color-fill2);
367
+ z-index: 100;
368
+ overflow: hidden;
369
+ }
505
370
 
506
- @include respond-to('<=laptop') {
507
- width: 240px;
508
- }
371
+ .o-header-search-input-wrapper {
372
+ .o-header-search-input {
373
+ width: 160px;
374
+ transition: width var(--o-easing-standard-in) var(--o-duration-m2);
509
375
 
510
- @include respond-to('<=pad_v') {
511
- flex: 1;
512
- width: auto;
376
+ @include respond('<=laptop') {
377
+ width: 120px;
378
+ }
513
379
  }
514
380
  }
515
381
 
516
- .o-header-search-back-icon {
517
- cursor: pointer;
518
- color: var(--o-color-info1);
519
- font-size: 20px;
520
- height: 30px;
521
- }
382
+ .o-header-search-input-wrapper.focus {
383
+ padding: var(--o-gap-4);
522
384
 
523
- .o-header-search-text {
524
- white-space: nowrap;
525
- font-size: 16px;
526
- line-height: 24px;
527
- cursor: pointer;
528
- color: var(--o-color-info1);
529
- height: 30px;
385
+ @include respond('<=pad_v') {
386
+ display: flex;
387
+ align-items: center;
388
+ gap: var(--o-gap-4);
389
+ padding: 10px var(--o-gap-4) var(--o-gap-4) var(--o-gap-4);
390
+ }
530
391
 
531
- @include hover {
532
- color: var(--o-color-primary1);
392
+ .o-header-search-input {
393
+ width: 480px;
394
+
395
+ @include respond('<=laptop') {
396
+ width: 240px;
397
+ }
398
+
399
+ @include respond('<=pad_v') {
400
+ flex: 1;
401
+ }
533
402
  }
534
403
  }
535
404
 
405
+ .o-header-search-icon.close {
406
+ @include x-svg-hover;
407
+ }
408
+
536
409
  .o-header-search-drawer {
537
410
  position: absolute;
538
411
  width: 100%;
@@ -540,15 +413,13 @@ defineExpose({
540
413
  padding-top: var(--o-gap-2);
541
414
  background-color: var(--o-color-fill2);
542
415
  box-shadow: var(--o-shadow-2);
543
- border-radius: 0 0 var(--o-radius-xs) var(--o-radius-xs);
544
416
 
545
- @include respond-to('<=pad_v') {
417
+ @include respond('<=pad_v') {
546
418
  position: static;
547
419
  height: calc(100vh - 50px);
548
420
  padding-top: 0;
549
421
  overflow-y: auto;
550
422
  box-shadow: unset;
551
- border-radius: unset;
552
423
  }
553
424
  }
554
425
 
@@ -560,63 +431,171 @@ defineExpose({
560
431
  top: -14px;
561
432
  height: 14px;
562
433
  background-color: var(--o-color-fill2);
434
+ box-shadow: unset;
435
+
436
+ @include respond('<=laptop') {
437
+ top: -10px;
438
+ height: 10px;
439
+ }
563
440
 
564
- @include respond-to('<=pad_v') {
441
+ @include respond('<=pad') {
442
+ top: -8px;
443
+ height: 8px;
444
+ }
445
+
446
+ @include respond('<=pad_v') {
565
447
  display: none;
566
448
  }
567
449
  }
568
450
 
569
- .o-header-search-mobile-icon {
570
- font-size: 24px;
571
- line-height: 28px;
451
+ .o-header-search-recommend-container {
572
452
  color: var(--o-color-info1);
453
+ margin-bottom: var(--o-gap-3);
454
+ }
455
+
456
+ .o-header-search-recommend-item {
573
457
  cursor: pointer;
574
- display: none;
458
+ @include tip2;
575
459
 
576
- @include respond-to('<=pad_v') {
577
- display: block;
460
+ @include hover {
461
+ color: var(--o-color-primary1);
462
+ }
463
+
464
+ @include respond('<=pad_v') {
465
+ font-size: 12px;
466
+ line-height: 18px;
467
+ }
468
+
469
+ & + & {
470
+ margin-top: var(--o-gap-3);
578
471
  }
579
472
  }
580
473
 
581
- .o-header-search-drawer-enter-active {
582
- transition: opacity var(--o-duration-m1);
474
+ .o-header-search-history-container {
475
+ @include respond('<=pad_v') {
476
+ margin-bottom: var(--o-gap-5);
477
+ }
583
478
  }
584
479
 
585
- .o-header-search-drawer-enter-from {
586
- opacity: 0;
480
+ .o-header-search-history-header {
481
+ display: flex;
482
+ align-items: center;
483
+ justify-content: space-between;
587
484
  }
588
485
 
589
- // 外部传入 mobile=true 时,复制 <=pad_v 的全部样式,不依赖媒体查询
590
- .o-header-search.is-mobile-mode {
591
- width: 24px;
486
+ .o-header-search-history-header-title {
487
+ @include tip2;
488
+ color: var(--o-color-info3);
489
+
490
+ @include respond('<=pad_v') {
491
+ @include text2;
492
+ color: var(--o-color-info1);
493
+ }
494
+ }
495
+
496
+ .o-header-search-history-item-container {
497
+ display: flex;
498
+ gap: 8px;
499
+ flex-wrap: wrap;
500
+ margin-top: var(--o-gap-2);
501
+ }
502
+
503
+ .o-header-search-history-item-icon {
504
+ position: absolute;
505
+ right: -8px;
506
+ top: -8px;
507
+ display: none;
508
+ align-items: center;
509
+ justify-content: center;
510
+ width: 16px;
511
+ height: 16px;
512
+ border-radius: 50%;
513
+ background-color: rgb(var(--o-grey-9));
514
+
515
+ .icon-delete {
516
+ height: 16px;
517
+ width: 16px;
518
+ color: var(--o-color-white);
519
+ }
520
+ }
521
+
522
+ .o-header-search-history-item {
523
+ position: relative;
524
+ display: flex;
525
+ align-items: center;
526
+ max-width: 224px;
592
527
  height: 24px;
528
+ padding: 0 var(--o-gap-3);
529
+ background-color: var(--o-color-fill3);
530
+ border-radius: var(--o-radius-xs);
531
+ cursor: pointer;
593
532
 
594
- .o-header-search-mobile-icon {
595
- display: block;
533
+ @include hover {
534
+ background-color: var(--o-color-control2-light);
535
+ color: var(--o-color-primary1);
536
+
537
+ .o-header-search-history-item-icon {
538
+ display: flex;
539
+ }
596
540
  }
541
+ }
597
542
 
598
- .o-header-search-row.is-focus {
599
- gap: var(--o-gap-4);
600
- padding: 10px var(--o-gap-4) var(--o-gap-4) var(--o-gap-4);
601
- border-radius: unset;
543
+ .o-header-search-history-item-text {
544
+ max-width: 200px;
545
+ overflow: hidden;
546
+ text-overflow: ellipsis;
547
+ white-space: nowrap;
548
+ @include tip2;
549
+
550
+ @include respond('<=pad_v') {
551
+ @include text1;
552
+ }
553
+ }
554
+
555
+ .o-header-search-drawer-divider {
556
+ --o-divider-gap: var(--o-gap-4);
557
+
558
+ @include respond('<=pad_v') {
559
+ display: none;
602
560
  }
561
+ }
603
562
 
604
- .o-header-search-row.is-focus .o-header-search-input {
605
- flex: 1;
606
- width: auto;
563
+ .o-header-search-hot-header {
564
+ color: var(--o-color-info3);
565
+ @include tip2;
566
+
567
+ @include respond('<=pad_v') {
568
+ margin-bottom: var(--o-gap-3);
569
+ @include text2;
607
570
  }
571
+ }
608
572
 
609
- .o-header-search-drawer {
610
- position: static;
611
- height: calc(100vh - 50px);
612
- padding-top: 0;
613
- overflow-y: auto;
614
- box-shadow: unset;
615
- border-radius: unset;
573
+ .o-header-search-hot-item-container {
574
+ display: flex;
575
+ flex-wrap: wrap;
576
+ gap: var(--o-gap-4);
577
+ margin-top: var(--o-gap-3);
578
+ @include tip2;
579
+
580
+ @include respond('<=pad_v') {
581
+ flex-direction: column;
582
+ gap: 12px;
583
+ font-size: 12px;
584
+ line-height: 18px;
585
+ }
586
+ }
616
587
 
617
- &::before {
618
- display: none;
619
- }
588
+ .o-header-search-hot-item {
589
+ color: var(--o-color-info1);
590
+ cursor: pointer;
591
+
592
+ @include hover {
593
+ color: var(--o-color-primary1);
620
594
  }
621
595
  }
596
+
597
+ .o-header-search-text {
598
+ font-size: 16px;
599
+ line-height: 24px;
600
+ }
622
601
  </style>