@opendesign-plus-test/components 0.0.1-rc.51 → 0.0.1-rc.52

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 (73) hide show
  1. package/dist/chunk-OElCookieNotice.cjs.js +1 -1
  2. package/dist/chunk-OElCookieNotice.es.js +49 -69
  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/OMeetingCalendar.vue.d.ts +3 -1
  7. package/dist/components/meeting/OMeetingPlayback.vue.d.ts +50 -1
  8. package/dist/components/meeting/components/OMeetingPlaybackSubtitles.vue.d.ts +16 -1
  9. package/dist/components/meeting/composables/useMeetingConfig.d.ts +1 -1
  10. package/dist/components/meeting/index.d.ts +17 -7
  11. package/dist/components/meeting/types.d.ts +1 -1
  12. package/dist/components.cjs.js +43 -43
  13. package/dist/components.css +1 -1
  14. package/dist/components.es.js +10946 -11901
  15. package/dist/index.d.ts +0 -1
  16. package/package.json +2 -4
  17. package/src/assets/styles/element-plus.scss +211 -0
  18. package/src/assets/svg-icons/icon-delete.svg +1 -5
  19. package/src/components/OHeaderSearch.vue +415 -437
  20. package/src/components/OThemeSwitcher.vue +51 -27
  21. package/src/components/activity/OActivityApproval.vue +10 -6
  22. package/src/components/activity/OActivityForm.vue +12 -6
  23. package/src/components/activity/{OMyActivityCalendar.vue → OActivityMyCalendar.vue} +59 -28
  24. package/src/components/activity/index.ts +4 -4
  25. package/src/components/events/OEventsApply.vue +2 -1
  26. package/src/components/events/OEventsCalendar.vue +7 -5
  27. package/src/components/events/OEventsList.vue +6 -4
  28. package/src/components/meeting/OMeetingCalendar.vue +26 -18
  29. package/src/components/meeting/OMeetingForm.vue +65 -17
  30. package/src/components/meeting/{OMyMeetingCalendar.vue → OMeetingMyCalendar.vue} +87 -46
  31. package/src/components/meeting/OMeetingPlayback.vue +42 -12
  32. package/src/components/meeting/{OSigMeetingCalendar.vue → OMeetingSigCalendar.vue} +7 -4
  33. package/src/components/meeting/components/OMeetingCalendarList.vue +56 -24
  34. package/src/components/meeting/components/OMeetingCalendarSelector.vue +12 -9
  35. package/src/components/meeting/components/OMeetingDetail.vue +1 -1
  36. package/src/components/meeting/components/OMeetingPlaybackSubtitles.vue +17 -13
  37. package/src/components/meeting/components/OMeetingPlaybackVideo.vue +6 -6
  38. package/src/components/meeting/components/{OSigMeetingAside.vue → OMeetingSigAside.vue} +1 -1
  39. package/src/components/meeting/composables/useMeetingConfig.ts +5 -5
  40. package/src/components/meeting/config.ts +1 -1
  41. package/src/components/meeting/index.ts +8 -8
  42. package/src/components/meeting/types.ts +1 -1
  43. package/src/i18n/en.ts +2 -12
  44. package/src/i18n/zh.ts +1 -11
  45. package/src/index.ts +0 -1
  46. package/vite.config.ts +1 -5
  47. package/dist/components/search/OSearchInput.vue.d.ts +0 -1003
  48. package/dist/components/search/composables/useImageSearch.d.ts +0 -48
  49. package/dist/components/search/composables/useKeywordHighlight.d.ts +0 -2
  50. package/dist/components/search/composables/useSearchHistory.d.ts +0 -14
  51. package/dist/components/search/index.d.ts +0 -590
  52. package/dist/components/search/internal/HighlightText.vue.d.ts +0 -9
  53. package/dist/components/search/internal/SearchImageInput.vue.d.ts +0 -716
  54. package/dist/components/search/internal/SearchPanel.vue.d.ts +0 -100
  55. package/dist/components/search/types.d.ts +0 -20
  56. package/src/assets/svg-icons/icon-delete-hover.svg +0 -4
  57. package/src/assets/svg-icons/icon-image-close.svg +0 -4
  58. package/src/assets/svg-icons/icon-image-upload.svg +0 -3
  59. package/src/assets/svg-icons/icon-image-zoomin.svg +0 -3
  60. package/src/assets/svg-icons/icon-refresh.svg +0 -3
  61. package/src/components/search/OSearchInput.vue +0 -463
  62. package/src/components/search/composables/useImageSearch.ts +0 -157
  63. package/src/components/search/composables/useKeywordHighlight.ts +0 -30
  64. package/src/components/search/composables/useSearchHistory.ts +0 -75
  65. package/src/components/search/index.ts +0 -23
  66. package/src/components/search/internal/HighlightText.vue +0 -37
  67. package/src/components/search/internal/SearchImageInput.vue +0 -488
  68. package/src/components/search/internal/SearchPanel.vue +0 -430
  69. package/src/components/search/types.ts +0 -25
  70. /package/dist/components/activity/{OMyActivityCalendar.vue.d.ts → OActivityMyCalendar.vue.d.ts} +0 -0
  71. /package/dist/components/meeting/{OMyMeetingCalendar.vue.d.ts → OMeetingMyCalendar.vue.d.ts} +0 -0
  72. /package/dist/components/meeting/{OSigMeetingCalendar.vue.d.ts → OMeetingSigCalendar.vue.d.ts} +0 -0
  73. /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,353 +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
- emit('search', { keyword, imageUrl: imageUrl || undefined });
193
- inputRef.value?.blur?.();
194
- isShowDrawer.value = false;
131
+ if (props.storeHistory && props.storeHistory) {
132
+ localStorage.setItem(props.storageKey, JSON.stringify(searchHistoryItems.value));
133
+ }
134
+ emit('search', input);
195
135
 
196
136
  if (props.searchUrl) {
197
- const params = new URLSearchParams();
198
- if (keyword) params.set('q', keyword);
199
- if (imageUrl) params.set('imageUrl', imageUrl);
200
- const sep = props.searchUrl.includes('?') ? '&' : '?';
201
- const url = `${props.searchUrl}${sep}${params.toString()}`;
202
- if (typeof window !== 'undefined') {
203
- window.open(url, props.searchUrlOpenBlank ? '_blank' : '_self', 'noopener,noreferrer');
204
- }
137
+ window.open(props.searchUrl + input, props.searchUrlOpenBlank ? '_blank' : '_self', 'noopener noreferrer');
205
138
  }
206
139
  };
207
140
 
208
- const handleClear = () => {
209
- innerValue.value = '';
141
+ const onClear = () => {
142
+ searchInput.value = '';
210
143
  emit('clear');
211
- inputRef.value?.focus?.();
212
- };
213
-
214
- const handleSuggestClick = (item: OSearchRecommendItem) => {
215
- emit('recommend-click', item);
216
- innerValue.value = item.key;
217
- runSearch();
218
- };
219
-
220
- const handleRecommendClick = (val: string) => {
221
- emit('recommend-click', val);
222
- innerValue.value = val;
223
- runSearch();
144
+ if (!lePadV.value) {
145
+ isShowDrawer.value = false;
146
+ }
224
147
  };
225
148
 
226
- const handleOnestepClick = (item: OSearchRecommendItem) => {
227
- emit('onestep-click', item);
228
- if (item.path && typeof window !== 'undefined') {
229
- 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);
230
154
  }
231
- };
232
155
 
233
- const handleHistoryClick = (val: string) => {
234
- emit('history-click', val);
156
+ emit('delete-history', history);
235
157
  };
236
158
 
237
- const handleHotClick = (val: string) => {
238
- emit('hot-click', val);
239
- };
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
+ }
240
168
 
241
- const handleHistoryRemove = (val: string) => {
242
- history.remove(val);
243
169
  emit('delete-history-item', val);
244
170
  };
245
171
 
246
- const handleHistoryClear = () => {
247
- const removed = [...history.items.value];
248
- history.clearAll();
249
- emit('delete-history', removed);
172
+ const onWordSearch = (val: string) => {
173
+ searchInput.value = val;
174
+ onSearch();
250
175
  };
251
176
 
252
- const handleBack = () => {
253
- innerValue.value = '';
254
- internalImageStaged.value = false;
255
- emit('update:imageUrl', '');
177
+ const onBack = () => {
178
+ searchInput.value = '';
256
179
  isShowDrawer.value = false;
257
180
  };
258
181
 
259
- const handleImageUploadStart = (file: File) => {
260
- internalImageStaged.value = true;
261
- internalImageUrl.value = '';
262
- emit('image-upload-start', file);
263
- };
264
-
265
- const handleImageUploadSuccess = (url: string, file: File) => {
266
- internalImageUrl.value = url;
267
- emit('update:imageUrl', url);
268
- emit('image-upload-success', url, file);
269
- };
270
-
271
- const handleImageClear = () => {
272
- internalImageStaged.value = false;
273
- internalImageUrl.value = '';
274
- emit('update:imageUrl', '');
275
- };
276
-
277
- const showPanel = computed(() => {
278
- if (!isShowDrawer.value) return false;
279
- if (hasImage.value) return false;
280
- return true;
281
- });
282
-
283
- defineExpose({
284
- focus: () => inputRef.value?.focus?.(),
285
- blur: () => inputRef.value?.blur?.(),
286
- open: openDrawer,
287
- close: closeDrawer,
288
- search: runSearch,
289
- });
182
+ const posWrapper = ref();
183
+ onClickOutside(posWrapper, onClear);
290
184
  </script>
291
185
 
292
186
  <template>
293
- <div class="o-header-search" :class="{ 'is-mobile-mode': isMobileMode }">
187
+ <div class="o-header-search">
294
188
  <div
295
- ref="wrapperRef"
296
- class="o-header-search-pos"
189
+ ref="posWrapper"
297
190
  :class="{
298
- 'is-pc': !isMobileMode,
299
- 'is-mobile': isMobileMode,
300
- 'is-left': expandDirection === 'left',
301
- 'is-right': expandDirection === 'right',
302
- '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,
303
196
  }"
304
197
  >
305
- <div class="o-header-search-row" :class="{ 'is-focus': isShowDrawer }">
306
- <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">
307
200
  <IconBack />
308
201
  </OIcon>
309
202
 
310
- <SearchImageInput
203
+ <OInput
311
204
  ref="inputRef"
312
- v-model="innerValue"
313
- :image-url="imageUrl"
314
- :placeholder="placeholder"
315
- :enable-image-search="enableImageSearch"
316
- :upload-image="uploadImage"
317
- :max-image-size="maxImageSize"
318
- :image-upload-tooltip="imageUploadTooltip"
319
- :clearable="clearable && isShowDrawer"
320
- :expanded="isShowDrawer && hasImage"
321
- :allowed-image-types="allowedImageTypes"
322
- size="medium"
205
+ v-model="searchInput"
323
206
  class="o-header-search-input"
324
- :class="{ 'is-collapsed': !isShowDrawer }"
325
- @update:imageUrl="(url: string) => emit('update:imageUrl', url)"
326
- @focus="handleFocus"
327
- @blur="handleBlur"
328
- @enter="runSearch"
329
- @clear="handleClear"
330
- @image-clear="handleImageClear"
331
- @image-upload-start="handleImageUploadStart"
332
- @image-upload-success="handleImageUploadSuccess"
333
- @image-upload-error="(error: unknown, file: File) => emit('image-upload-error', error, file)"
334
- @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"
335
210
  >
336
- <template #prefix><slot name="input-prefix" /></template>
337
- <template #suffix="slotProps"><slot name="input-suffix" v-bind="slotProps" /></template>
338
- <template v-if="$slots['image-preview']" #preview="slotProps">
339
- <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>
340
217
  </template>
341
- </SearchImageInput>
342
218
 
343
- <span v-if="isMobileMode && isShowDrawer" class="o-header-search-text" @click="runSearch">
344
- {{ searchTextMobile ?? t('search') }}
345
- </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>
346
229
  </div>
347
230
 
348
- <Transition name="o-header-search-drawer">
349
- <div v-if="showPanel" class="o-header-search-drawer">
350
- <slot
351
- name="drawer"
352
- :recommend-items="recommendItems"
353
- :history-items="history.items.value"
354
- :hot-items="hotItems"
355
- :suggest-items="suggestItems"
356
- :onestep-items="onestepItems"
357
- :keyword="modelValue"
358
- >
359
- <SearchPanel
360
- :keyword="modelValue"
361
- :onestep-items="onestepItems"
362
- :onestep-title="onestepTitle"
363
- :suggest-items="suggestItems"
364
- :suggest-title="suggestTitle"
365
- :recommend-items="recommendItems"
366
- :history-items="history.items.value"
367
- :history-title="historyTitle"
368
- :hot-items="hotItems"
369
- :hot-title="hotTitle"
370
- :no-data-text="noDataText"
371
- :highlight-keyword="highlightKeyword"
372
- :hide-on-keyword="true"
373
- :show-suggest-empty="showSuggestEmpty"
374
- @onestep-click="handleOnestepClick"
375
- @suggest-click="handleSuggestClick"
376
- @recommend-click="handleRecommendClick"
377
- @history-click="handleHistoryClick"
378
- @history-remove="handleHistoryRemove"
379
- @history-clear="handleHistoryClear"
380
- @hot-click="handleHotClick"
381
- @hot-refresh="emit('hot-refresh')"
382
- >
383
- <template v-if="$slots['recommend-header']" #recommend-header="slotProps">
384
- <slot name="recommend-header" v-bind="slotProps" />
385
- </template>
386
- <template v-if="$slots['recommend-content']" #recommend-content="slotProps">
387
- <slot name="recommend-content" v-bind="slotProps" />
388
- </template>
389
- <template v-if="$slots['onestep-header']" #onestep-header="slotProps">
390
- <slot name="onestep-header" v-bind="slotProps" />
391
- </template>
392
- <template v-if="$slots['onestep-content']" #onestep-content="slotProps">
393
- <slot name="onestep-content" v-bind="slotProps" />
394
- </template>
395
- <template v-if="$slots['suggest-header']" #suggest-header="slotProps">
396
- <slot name="suggest-header" v-bind="slotProps" />
397
- </template>
398
- <template v-if="$slots['suggest-content']" #suggest-content="slotProps">
399
- <slot name="suggest-content" v-bind="slotProps" />
400
- </template>
401
- <template v-if="$slots['history-header']" #history-header="slotProps">
402
- <slot name="history-header" v-bind="slotProps" />
403
- </template>
404
- <template v-if="$slots['history-content']" #history-content="slotProps">
405
- <slot name="history-content" v-bind="slotProps" />
406
- </template>
407
- <template v-if="$slots['hot-header']" #hot-header="slotProps">
408
- <slot name="hot-header" v-bind="slotProps" />
409
- </template>
410
- <template v-if="$slots['hot-content']" #hot-content="slotProps">
411
- <slot name="hot-content" v-bind="slotProps" />
412
- </template>
413
- </SearchPanel>
414
- </slot>
415
- </div>
416
- </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>
417
282
  </div>
418
283
 
419
- <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">
420
285
  <IconSearch />
421
286
  </OIcon>
422
287
  </div>
@@ -428,112 +293,119 @@ defineExpose({
428
293
  width: 160px;
429
294
  height: 32px;
430
295
 
431
- @include respond-to('<=laptop') {
296
+ @include respond('<=laptop') {
432
297
  width: 120px;
433
298
  }
434
299
 
435
- @include respond-to('<=pad_v') {
300
+ @media screen and (max-width: 1080px) {
436
301
  width: 24px;
437
302
  height: 24px;
438
- margin-left: auto;
439
303
  }
440
304
  }
441
305
 
442
- .o-header-search-pos {
443
- position: absolute;
444
- top: 0;
445
- width: fit-content;
446
- background-color: var(--o-color-fill2);
447
- z-index: 100;
306
+ .o-header-search-icon {
307
+ cursor: pointer;
308
+ color: var(--o-color-info1);
309
+ @include h4;
448
310
 
449
- &.is-left {
450
- right: 0;
451
- }
452
- &.is-right {
453
- left: 0;
454
- }
455
- &.is-mobile {
456
- display: none;
311
+ @include respond('<=pad_v') {
312
+ font-size: 20px;
457
313
  }
458
- &.is-pc.is-focus {
459
- box-shadow: var(--o-shadow-2);
460
- top: calc(-1 * var(--o-gap-4));
461
- border-radius: var(--o-radius-xs);
314
+
315
+ .close {
316
+ @include x-svg-hover;
462
317
  }
318
+ }
463
319
 
464
- &.is-mobile.is-focus {
465
- position: fixed;
466
- top: 0;
467
- right: 0;
468
- bottom: 0;
469
- left: 0;
470
- 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') {
471
328
  display: block;
472
- height: 100vh;
473
- background-color: var(--o-color-fill2);
474
- z-index: 100;
475
- overflow-y: auto;
476
329
  }
477
330
  }
478
331
 
479
- .o-header-search-row {
480
- display: flex;
481
- 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
+ }
482
340
 
483
- &.is-focus {
484
- padding: var(--o-gap-4);
485
- border-radius: var(--o-radius-xs);
341
+ .o-header-search-input-pc-wrapper-left {
342
+ right: 0;
343
+ }
486
344
 
487
- @include respond-to('<=pad_v') {
488
- gap: var(--o-gap-4);
489
- padding: 10px var(--o-gap-4) var(--o-gap-4) var(--o-gap-4);
490
- border-radius: unset;
491
- }
492
- }
345
+ .o-header-search-input-pc-wrapper-right {
346
+ left: 0;
493
347
  }
494
348
 
495
- .o-header-search-input {
496
- width: 160px;
497
- 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
+ }
498
353
 
499
- @include respond-to('<=laptop') {
500
- width: 120px;
501
- }
354
+ .o-header-search-input-mobile-wrapper {
355
+ display: none;
502
356
  }
503
357
 
504
- .o-header-search-row.is-focus .o-header-search-input {
505
- 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
+ }
506
370
 
507
- @include respond-to('<=laptop') {
508
- width: 240px;
509
- }
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);
510
375
 
511
- @include respond-to('<=pad_v') {
512
- flex: 1;
513
- width: auto;
376
+ @include respond('<=laptop') {
377
+ width: 120px;
378
+ }
514
379
  }
515
380
  }
516
381
 
517
- .o-header-search-back-icon {
518
- cursor: pointer;
519
- color: var(--o-color-info1);
520
- font-size: 20px;
521
- height: 30px;
522
- }
382
+ .o-header-search-input-wrapper.focus {
383
+ padding: var(--o-gap-4);
523
384
 
524
- .o-header-search-text {
525
- white-space: nowrap;
526
- font-size: 16px;
527
- line-height: 24px;
528
- cursor: pointer;
529
- color: var(--o-color-info1);
530
- 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
+ }
531
391
 
532
- @include hover {
533
- 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
+ }
534
402
  }
535
403
  }
536
404
 
405
+ .o-header-search-icon.close {
406
+ @include x-svg-hover;
407
+ }
408
+
537
409
  .o-header-search-drawer {
538
410
  position: absolute;
539
411
  width: 100%;
@@ -541,15 +413,13 @@ defineExpose({
541
413
  padding-top: var(--o-gap-2);
542
414
  background-color: var(--o-color-fill2);
543
415
  box-shadow: var(--o-shadow-2);
544
- border-radius: 0 0 var(--o-radius-xs) var(--o-radius-xs);
545
416
 
546
- @include respond-to('<=pad_v') {
417
+ @include respond('<=pad_v') {
547
418
  position: static;
548
419
  height: calc(100vh - 50px);
549
420
  padding-top: 0;
550
421
  overflow-y: auto;
551
422
  box-shadow: unset;
552
- border-radius: unset;
553
423
  }
554
424
  }
555
425
 
@@ -561,63 +431,171 @@ defineExpose({
561
431
  top: -14px;
562
432
  height: 14px;
563
433
  background-color: var(--o-color-fill2);
434
+ box-shadow: unset;
435
+
436
+ @include respond('<=laptop') {
437
+ top: -10px;
438
+ height: 10px;
439
+ }
564
440
 
565
- @include respond-to('<=pad_v') {
441
+ @include respond('<=pad') {
442
+ top: -8px;
443
+ height: 8px;
444
+ }
445
+
446
+ @include respond('<=pad_v') {
566
447
  display: none;
567
448
  }
568
449
  }
569
450
 
570
- .o-header-search-mobile-icon {
571
- font-size: 24px;
572
- line-height: 28px;
451
+ .o-header-search-recommend-container {
573
452
  color: var(--o-color-info1);
453
+ margin-bottom: var(--o-gap-3);
454
+ }
455
+
456
+ .o-header-search-recommend-item {
574
457
  cursor: pointer;
575
- display: none;
458
+ @include tip2;
576
459
 
577
- @include respond-to('<=pad_v') {
578
- 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);
579
471
  }
580
472
  }
581
473
 
582
- .o-header-search-drawer-enter-active {
583
- 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
+ }
584
478
  }
585
479
 
586
- .o-header-search-drawer-enter-from {
587
- opacity: 0;
480
+ .o-header-search-history-header {
481
+ display: flex;
482
+ align-items: center;
483
+ justify-content: space-between;
588
484
  }
589
485
 
590
- // 外部传入 mobile=true 时,复制 <=pad_v 的全部样式,不依赖媒体查询
591
- .o-header-search.is-mobile-mode {
592
- 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;
593
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;
594
532
 
595
- .o-header-search-mobile-icon {
596
- 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
+ }
597
540
  }
541
+ }
598
542
 
599
- .o-header-search-row.is-focus {
600
- gap: var(--o-gap-4);
601
- padding: 10px var(--o-gap-4) var(--o-gap-4) var(--o-gap-4);
602
- 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;
603
560
  }
561
+ }
604
562
 
605
- .o-header-search-row.is-focus .o-header-search-input {
606
- flex: 1;
607
- 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;
608
570
  }
571
+ }
609
572
 
610
- .o-header-search-drawer {
611
- position: static;
612
- height: calc(100vh - 50px);
613
- padding-top: 0;
614
- overflow-y: auto;
615
- box-shadow: unset;
616
- 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
+ }
617
587
 
618
- &::before {
619
- display: none;
620
- }
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);
621
594
  }
622
595
  }
596
+
597
+ .o-header-search-text {
598
+ font-size: 16px;
599
+ line-height: 24px;
600
+ }
623
601
  </style>