@opendesign-plus-test/components 0.0.1-rc.45 → 0.0.1-rc.46

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