@opendesign-plus-test/components 0.0.1-rc.55 → 0.0.1-rc.57

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 (63) hide show
  1. package/dist/chunk-OElCookieNotice.cjs.js +1 -1
  2. package/dist/chunk-OElCookieNotice.es.js +112 -89
  3. package/dist/components/OHeaderSearch.vue.d.ts +814 -534
  4. package/dist/components/OHeaderUser.vue.d.ts +1 -1
  5. package/dist/components/OLanguageSwitcher.vue.d.ts +49 -0
  6. package/dist/components/OThemeSwitcher.vue.d.ts +1 -1
  7. package/dist/components/activity/OActivityMyCalendar.vue.d.ts +4 -4
  8. package/dist/components/activity/index.d.ts +2 -2
  9. package/dist/components/banner/OBanner.vue.d.ts +13 -0
  10. package/dist/components/banner/OBannerContent.vue.d.ts +7 -0
  11. package/dist/components/banner/index.d.ts +68 -0
  12. package/dist/components/banner/types.d.ts +31 -0
  13. package/dist/components/meeting/OMeetingCalendar.vue.d.ts +2 -2
  14. package/dist/components/meeting/OMeetingMyCalendar.vue.d.ts +4 -4
  15. package/dist/components/meeting/components/OMeetingCalendarSelector.vue.d.ts +1 -1
  16. package/dist/components/meeting/index.d.ts +3 -3
  17. package/dist/components/search/OSearchInput.vue.d.ts +1005 -0
  18. package/dist/components/search/composables/useImageSearch.d.ts +48 -0
  19. package/dist/components/search/composables/useKeywordHighlight.d.ts +2 -0
  20. package/dist/components/search/composables/useSearchHistory.d.ts +14 -0
  21. package/dist/components/search/index.d.ts +590 -0
  22. package/dist/components/search/internal/HighlightText.vue.d.ts +9 -0
  23. package/dist/components/search/internal/SearchImageInput.vue.d.ts +716 -0
  24. package/dist/components/search/internal/SearchPanel.vue.d.ts +100 -0
  25. package/dist/components/search/types.d.ts +20 -0
  26. package/dist/components.cjs.js +42 -42
  27. package/dist/components.css +1 -1
  28. package/dist/components.es.js +11848 -10601
  29. package/dist/index.d.ts +4 -2
  30. package/package.json +1 -1
  31. package/scripts/generate-components-index.js +1 -1
  32. package/src/assets/svg-icons/icon-delete-hover.svg +4 -0
  33. package/src/assets/svg-icons/icon-delete.svg +5 -1
  34. package/src/assets/svg-icons/icon-image-close.svg +4 -0
  35. package/src/assets/svg-icons/icon-image-upload.svg +3 -0
  36. package/src/assets/svg-icons/icon-image-zoomin.svg +3 -0
  37. package/src/assets/svg-icons/icon-refresh.svg +3 -0
  38. package/src/components/OHeaderSearch.vue +445 -418
  39. package/src/components/OLanguageSwitcher.vue +211 -0
  40. package/src/components/banner/OBanner.vue +288 -0
  41. package/src/components/banner/OBannerContent.vue +175 -0
  42. package/src/components/banner/index.ts +18 -0
  43. package/src/components/banner/types.ts +39 -0
  44. package/src/components/header/OHeader.vue +1 -1
  45. package/src/components/meeting/OMeetingCalendar.vue +4 -4
  46. package/src/components/meeting/OMeetingForm.vue +10 -2
  47. package/src/components/meeting/components/OMeetingCalendarList.vue +2 -2
  48. package/src/components/meeting/components/OMeetingDetail.vue +17 -3
  49. package/src/components/search/OSearchInput.vue +526 -0
  50. package/src/components/search/composables/useImageSearch.ts +157 -0
  51. package/src/components/search/composables/useKeywordHighlight.ts +30 -0
  52. package/src/components/search/composables/useSearchHistory.ts +75 -0
  53. package/src/components/search/index.ts +23 -0
  54. package/src/components/search/internal/HighlightText.vue +37 -0
  55. package/src/components/search/internal/SearchImageInput.vue +498 -0
  56. package/src/components/search/internal/SearchPanel.vue +431 -0
  57. package/src/components/search/types.ts +25 -0
  58. package/src/i18n/en.ts +14 -2
  59. package/src/i18n/zh.ts +14 -3
  60. package/src/index.ts +5 -3
  61. package/vite.config.ts +4 -0
  62. package/dist/components/OBanner.vue.d.ts +0 -11
  63. package/src/components/OBanner.vue +0 -398
@@ -0,0 +1,39 @@
1
+ // Banner 项的类型定义
2
+ export interface BannerItem {
3
+ title?: string;
4
+ subtitle?: string;
5
+ bg: string;
6
+ text_theme?: 'light' | 'dark';
7
+ bg_text?: string;
8
+ pc_text_width?: string;
9
+ pc_text_height?: string;
10
+ pad_text_width?: string;
11
+ pad_text_height?: string;
12
+ attach?: string;
13
+ href?: string;
14
+ btn?: string;
15
+ withStickyBg?: boolean;
16
+ pad_offset?: string;
17
+ }
18
+
19
+ // BannerContent 组件的 Props 类型
20
+ export interface BannerContentProps {
21
+ info: BannerItem;
22
+ size?: 'large' | 'medium' | 'small' | 'tiny';
23
+ contentJustifyCenter?: boolean;
24
+ isPhone?: boolean;
25
+ isLight?: boolean;
26
+ }
27
+
28
+ // Emits 事件类型
29
+ export interface HomeBannerEmits {
30
+ (e: 'before-change', index: number): void;
31
+ (e: 'click', href: string | undefined): void;
32
+ }
33
+
34
+ export interface BannerContentEmits {
35
+ (e: 'click', href: string | undefined): void;
36
+ }
37
+
38
+ // 组件尺寸类型
39
+ export type ComponentSize = 'large' | 'medium' | 'small' | 'tiny';
@@ -35,7 +35,7 @@ const handleMouseenter = (val: any) => {
35
35
  itemVisible.value = true;
36
36
  }
37
37
  };
38
- const handleMouseleave = (val: any) => {
38
+ const handleMouseleave = () => {
39
39
  itemVisible.value = false;
40
40
  };
41
41
  const handleClick = (val: any) => {
@@ -82,8 +82,8 @@ const getSummitData = async (date: string) => {
82
82
  return {
83
83
  ...v,
84
84
  type: CalendarDataType.SUMMIT,
85
- start_date_time: `${ formatDate(v.start_date) } ${ v.start }`,
86
- end_date_time: `${ formatDate(v.end_date) } ${ v.end }`,
85
+ start_date_time: `${ formatDate(v.start_date) } ${ v.start || '' }`,
86
+ end_date_time: `${ formatDate(v.end_date) } ${ v.end || '' }`,
87
87
  };
88
88
  });
89
89
  } else {
@@ -97,8 +97,8 @@ const getActivityData = async (date: string) => {
97
97
  return {
98
98
  ...v,
99
99
  type: CalendarDataType.EVENTS,
100
- start_date_time: `${ formatDate(v.start_date) } ${ v.start }`,
101
- end_date_time: `${ formatDate(v.end_date) } ${ v.end }`,
100
+ start_date_time: `${ formatDate(v.start_date) } ${ v.start || '' }`,
101
+ end_date_time: `${ formatDate(v.end_date) } ${ v.end || '' }`,
102
102
  };
103
103
  });
104
104
  } else {
@@ -689,7 +689,11 @@ defineExpose({
689
689
  <OIcon>
690
690
  <IconTip />
691
691
  </OIcon>
692
- <span>{{ t('meeting.meetingRecordDesc', [getPlatformLabel(form.platform)]) }}</span>
692
+ <div>
693
+ <span>{{ t('meeting.meetingRecordDesc1') }}</span>
694
+ <br>
695
+ <span>{{ t('meeting.meetingRecordDesc2') }}</span>
696
+ </div>
693
697
  </div>
694
698
  </div>
695
699
  </template>
@@ -701,7 +705,11 @@ defineExpose({
701
705
  <OIcon>
702
706
  <IconTip />
703
707
  </OIcon>
704
- <span>{{ t('meeting.meetingRecordDesc', [getPlatformLabel(form.platform)]) }}</span>
708
+ <div>
709
+ <span>{{ t('meeting.meetingRecordDesc1') }}</span>
710
+ <br>
711
+ <span>{{ t('meeting.meetingRecordDesc2') }}</span>
712
+ </div>
705
713
  </div>
706
714
  </div>
707
715
  </template>
@@ -90,7 +90,7 @@ const computedList = computed<any[]>(() => {
90
90
  dateRange = `${ formatDate(v.start_date) } ${ v.start }-${ formatDate(v.end_date) } ${ v.end }`;
91
91
  }
92
92
  } else {
93
- dateRange = `${ formatDate(v.start_date, 'YYYY/MM/DD HH:mm') } ${ formatDate(v.end_date, 'YYYY/MM/DD HH:mm') }`;
93
+ dateRange = `${ formatDate(v.start_date, 'YYYY/MM/DD') } ${ formatDate(v.end_date, 'YYYY/MM/DD') }`;
94
94
  }
95
95
 
96
96
  return {
@@ -169,7 +169,7 @@ const computedList = computed<any[]>(() => {
169
169
  <OCollapseItem v-for="(item, index) in computedList" :key="item.id" :value="item.id">
170
170
  <template #title>
171
171
  <div class="meet-title-left">
172
- <div class="meet-title" :title="item.topic || item.name">
172
+ <div class="meet-title" :title="item.topic || item.name || item.title">
173
173
  <OIcon
174
174
  :style="{
175
175
  backgroundColor: `${getConfig(item.type, 'color')}`
@@ -5,6 +5,7 @@ import { CalendarDataType, MeetingItemT } from '../types.ts';
5
5
  import MoreText from '@/components/common/MoreText.vue';
6
6
  import { formatDate } from '@/components/meeting/utils.ts';
7
7
  import { useMeetingConfig } from '../composables/useMeetingConfig';
8
+ import { useI18n } from '@/i18n';
8
9
 
9
10
  const props = defineProps<{
10
11
  data: MeetingItemT;
@@ -13,7 +14,8 @@ const props = defineProps<{
13
14
  page?: CalendarDataType
14
15
  }>();
15
16
  const { t, getPlatformLabel } = useMeetingConfig();
16
-
17
+ const { locale } = useI18n();
18
+ const isZh = computed(() => locale.value === 'zh');
17
19
  // 会议详情配置
18
20
  const infoList = computed(() =>
19
21
  [
@@ -83,11 +85,15 @@ const columns = computed<ColumnItemT[]>(() => {
83
85
  });
84
86
 
85
87
  const domRef = ref([]);
88
+ const SPLIT_MARK = '|';
86
89
  // 复制会议内容
87
90
  const copyInfo = () => {
88
91
  try {
89
- let text = `${ t('meeting.meetingTopic') + props.data.topic }\n`;
90
- text += [...domRef.value].reduce((pre, cur: HTMLDivElement) => `${ pre }${ cur.textContent }\n`, '');
92
+ const colon = isZh.value ? '' : ': ';
93
+ let text = `${ t('meeting.meetingTopic') }${ colon }${ props.data.topic || props.data.name || props.data.title }\n`;
94
+ text += [...domRef.value].reduce((pre, cur: HTMLDivElement) => {
95
+ return `${ pre }${ cur.textContent ? cur.textContent.replace(SPLIT_MARK, colon) : '' }\n`;
96
+ }, '');
91
97
  navigator.clipboard.writeText(text);
92
98
  return Promise.resolve();
93
99
  } catch (e) {
@@ -114,6 +120,7 @@ defineExpose({ copyInfo });
114
120
  >
115
121
  <template v-if="getField(info.key) && !info.isRecord">
116
122
  <span class="label">{{ info.label }}</span>
123
+ <span class="split-mark">{{ SPLIT_MARK }}</span>
117
124
  <MoreText :show="show" v-if="info.ellipsis" :text="getField(info.key) || '-'" />
118
125
  <OLink
119
126
  v-else-if="info.isLink"
@@ -190,6 +197,13 @@ defineExpose({ copyInfo });
190
197
  }
191
198
  }
192
199
 
200
+ .split-mark {
201
+ width: 0;
202
+ height: 0;
203
+ opacity: 0;
204
+ overflow: hidden;
205
+ }
206
+
193
207
  &.type_events {
194
208
  .label {
195
209
  width: 6em;
@@ -0,0 +1,526 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref, toRef, watch } from 'vue';
3
+ import { onClickOutside, useDebounceFn } from '@vueuse/core';
4
+
5
+ import SearchImageInput from './internal/SearchImageInput.vue';
6
+ import SearchPanel from './internal/SearchPanel.vue';
7
+ import { useSearchHistory } from './composables/useSearchHistory';
8
+ import { useI18n } from '@/i18n';
9
+ import type {
10
+ OSearchPayload,
11
+ OSearchRecommendItem,
12
+ OSearchUploadImageFn,
13
+ } from './types';
14
+
15
+ export interface OSearchInputPropsT {
16
+ modelValue?: string;
17
+ imageUrl?: string;
18
+ size?: 'small' | 'medium' | 'large';
19
+ placeholder?: string;
20
+ imagePlaceholder?: string;
21
+
22
+ /** ---- Image search ---- */
23
+ enableImageSearch?: boolean;
24
+ uploadImage?: OSearchUploadImageFn;
25
+ maxImageSize?: number;
26
+ imageUploadTooltip?: string;
27
+ allowedImageTypes?: string[];
28
+
29
+ /** ---- Recommendation lists (controlled by parent) ---- */
30
+ suggestItems?: OSearchRecommendItem[];
31
+ onestepItems?: OSearchRecommendItem[];
32
+ suggestTitle?: string;
33
+ onestepTitle?: string;
34
+ noDataText?: string;
35
+ /** When true, show "no data" placeholder for empty suggest while typing */
36
+ showSuggestEmpty?: boolean;
37
+ highlightKeyword?: boolean;
38
+ debounce?: number;
39
+
40
+ /** ---- History ---- */
41
+ enableHistory?: boolean;
42
+ historyItems?: string[];
43
+ maxHistoryCount?: number;
44
+ storeHistory?: boolean;
45
+ storageKey?: string;
46
+ historyTitle?: string;
47
+ /** Auto-record history on search; default true */
48
+ autoSaveHistory?: boolean;
49
+
50
+ /** ---- Hot searches (shown alongside history when no keyword) ---- */
51
+ hotItems?: string[];
52
+ hotTitle?: string;
53
+
54
+ /** ---- "Did you mean" list (below input) ---- */
55
+ suggestList?: string[];
56
+ suggestListLabel?: string;
57
+
58
+ /** ---- Misc ---- */
59
+ clearable?: boolean;
60
+ closeOnSearch?: boolean;
61
+ closeOnClickOutside?: boolean;
62
+ /** Show dropdown on focus even when empty (history) */
63
+ openOnFocus?: boolean;
64
+ /** Always show image as inline thumbnail inside the input; never expand preview below */
65
+ alwaysInlineThumbnail?: boolean;
66
+ }
67
+
68
+ const props = withDefaults(defineProps<OSearchInputPropsT>(), {
69
+ modelValue: '',
70
+ imageUrl: '',
71
+ size: 'large',
72
+ enableImageSearch: false,
73
+ maxImageSize: 10 * 1024 * 1024,
74
+ suggestItems: () => [],
75
+ onestepItems: () => [],
76
+ showSuggestEmpty: true,
77
+ highlightKeyword: true,
78
+ debounce: 300,
79
+ enableHistory: true,
80
+ historyItems: () => [],
81
+ maxHistoryCount: 6,
82
+ storeHistory: false,
83
+ storageKey: 'search-history',
84
+ autoSaveHistory: true,
85
+ hotItems: () => [],
86
+ suggestList: () => [],
87
+ clearable: true,
88
+ closeOnSearch: true,
89
+ closeOnClickOutside: true,
90
+ openOnFocus: true,
91
+ });
92
+
93
+ const emit = defineEmits<{
94
+ (e: 'update:modelValue', val: string): void;
95
+ (e: 'update:imageUrl', url: string): void;
96
+ (e: 'update:historyItems', items: string[]): void;
97
+ (e: 'focus'): void;
98
+ (e: 'blur'): void;
99
+ (e: 'input', val: string): void;
100
+ (e: 'clear'): void;
101
+ (e: 'search', payload: OSearchPayload): void;
102
+ (e: 'recommend-click', item: OSearchRecommendItem): void;
103
+ (e: 'onestep-click', item: OSearchRecommendItem): void;
104
+ (e: 'history-click', val: string): void;
105
+ (e: 'hot-click', val: string): void;
106
+ (e: 'suggest-list-click', val: string): void;
107
+ (e: 'delete-history', items: string[]): void;
108
+ (e: 'delete-history-item', val: string): void;
109
+ (e: 'image-upload-start', file: File): void;
110
+ (e: 'image-upload-success', url: string, file: File): void;
111
+ (e: 'image-upload-error', error: unknown, file: File): void;
112
+ (e: 'image-validate-error', reason: 'size' | 'type', file: File): void;
113
+ }>();
114
+
115
+ const { t } = useI18n();
116
+
117
+ type SuggestSegment = { text: string; em: boolean };
118
+ function parseSuggest(raw: string): SuggestSegment[] {
119
+ const segments: SuggestSegment[] = [];
120
+ const re = /<em>(.*?)<\/em>/g;
121
+ let last = 0;
122
+ let m: RegExpExecArray | null;
123
+ while ((m = re.exec(raw)) !== null) {
124
+ if (m.index > last) segments.push({ text: raw.slice(last, m.index), em: false });
125
+ segments.push({ text: m[1], em: true });
126
+ last = m.index + m[0].length;
127
+ }
128
+ if (last < raw.length) segments.push({ text: raw.slice(last), em: false });
129
+ return segments;
130
+ }
131
+
132
+ const inputRef = ref<InstanceType<typeof SearchImageInput>>();
133
+ const wrapperRef = ref<HTMLElement | null>(null);
134
+
135
+ const innerValue = computed({
136
+ get: () => props.modelValue,
137
+ set: (val: string) => emit('update:modelValue', val),
138
+ });
139
+
140
+ const isFocus = ref(false);
141
+ const isPreviewOpen = ref(false);
142
+ const justClosedPreview = ref(false);
143
+ const hasInternalImage = ref(false);
144
+
145
+ const showDropdown = computed(() => {
146
+ if (!isFocus.value) return false;
147
+ if (props.imageUrl || hasInternalImage.value) return false;
148
+ const hasSuggest =
149
+ !!innerValue.value &&
150
+ (props.suggestItems.length > 0 || props.onestepItems.length > 0 || props.showSuggestEmpty);
151
+ const hasHistory = props.enableHistory && history.items.value.length > 0 && !innerValue.value;
152
+ const hasHot = props.hotItems.length > 0 && !innerValue.value;
153
+ if (hasSuggest) return true;
154
+ if (props.openOnFocus && (hasHistory || hasHot)) return true;
155
+ return false;
156
+ });
157
+
158
+ // history
159
+ const historyItemsRef = toRef(props, 'historyItems');
160
+ const storageKeyRef = toRef(props, 'storageKey');
161
+ const storeHistoryRef = toRef(props, 'storeHistory');
162
+ const maxHistoryRef = toRef(props, 'maxHistoryCount');
163
+
164
+ const history = useSearchHistory({
165
+ initial: historyItemsRef,
166
+ storageKey: storageKeyRef,
167
+ storeHistory: storeHistoryRef,
168
+ maxHistoryCount: maxHistoryRef,
169
+ onChange: (items) => emit('update:historyItems', items),
170
+ });
171
+
172
+ // debounced input event
173
+ // suppressNextInput: set to true before programmatic value assignments (history/suggest
174
+ // clicks) so the watcher-triggered debounce doesn't fire the input event.
175
+ let suppressNextInput = false;
176
+ const emitInputDebounced = useDebounceFn((val: string) => {
177
+ if (suppressNextInput) {
178
+ suppressNextInput = false;
179
+ return;
180
+ }
181
+ emit('input', val);
182
+ }, () => props.debounce);
183
+
184
+ watch(innerValue, (val) => {
185
+ emitInputDebounced(val);
186
+ });
187
+
188
+ // click outside
189
+ onClickOutside(wrapperRef, () => {
190
+ if (!props.closeOnClickOutside) return;
191
+ if (isPreviewOpen.value || justClosedPreview.value) return;
192
+ isFocus.value = false;
193
+ });
194
+
195
+ const handleFocus = () => {
196
+ isFocus.value = true;
197
+ emit('focus');
198
+ };
199
+
200
+ const handleBlur = () => {
201
+ emit('blur');
202
+ };
203
+
204
+ const runSearch = async (overrideKeyword?: string) => {
205
+ if (inputRef.value?.getIsUploading?.()) {
206
+ await inputRef.value.awaitUpload?.();
207
+ }
208
+ const keyword = overrideKeyword !== undefined ? overrideKeyword.trim() : innerValue.value.trim();
209
+ const imageUrl = inputRef.value?.getUploadedUrl?.() || props.imageUrl;
210
+
211
+ if (!keyword && !imageUrl) return;
212
+
213
+ if (props.autoSaveHistory && keyword) {
214
+ history.push(keyword);
215
+ }
216
+
217
+ if (props.closeOnSearch) {
218
+ isFocus.value = false;
219
+ }
220
+
221
+ emit('search', { keyword, imageUrl: imageUrl || undefined });
222
+ };
223
+
224
+ const handleClear = () => {
225
+ innerValue.value = '';
226
+ emit('clear');
227
+ };
228
+
229
+ const handleSuggestClick = (item: OSearchRecommendItem) => {
230
+ emit('recommend-click', item);
231
+ suppressNextInput = true;
232
+ innerValue.value = item.key;
233
+ runSearch();
234
+ };
235
+
236
+ const handleOnestepClick = (item: OSearchRecommendItem) => {
237
+ emit('onestep-click', item);
238
+ if (item.path) {
239
+ const url = item.path;
240
+ if (typeof window !== 'undefined') window.open(url, '_blank', 'noopener,noreferrer');
241
+ }
242
+ };
243
+
244
+ const handleHistoryClick = (val: string) => {
245
+ emit('history-click', val);
246
+ runSearch(val);
247
+ };
248
+
249
+ const handleHotClick = (val: string) => {
250
+ emit('hot-click', val);
251
+ runSearch(val);
252
+ };
253
+
254
+ const handleHistoryRemove = (val: string) => {
255
+ history.remove(val);
256
+ emit('delete-history-item', val);
257
+ };
258
+
259
+ const handleHistoryClear = () => {
260
+ const removed = [...history.items.value];
261
+ history.clearAll();
262
+ emit('delete-history', removed);
263
+ };
264
+
265
+ const handleSuggestListClick = (val: string) => {
266
+ const text = val.replace(/<[^>]+>/g, '');
267
+ emit('suggest-list-click', text);
268
+ suppressNextInput = true;
269
+ innerValue.value = text;
270
+ runSearch();
271
+ };
272
+
273
+ const onPreviewChange = (visible: boolean) => {
274
+ isPreviewOpen.value = visible;
275
+ if (!visible) {
276
+ justClosedPreview.value = true;
277
+ setTimeout(() => {
278
+ justClosedPreview.value = false;
279
+ }, 100);
280
+ }
281
+ };
282
+
283
+ const showInlineThumbnail = computed(() =>
284
+ !!props.imageUrl && (props.alwaysInlineThumbnail || !isFocus.value)
285
+ );
286
+
287
+ const onImageUploadStart = (file: File) => {
288
+ hasInternalImage.value = true;
289
+ emit('image-upload-start', file);
290
+ };
291
+
292
+ const onImageUploadSuccess = (url: string, file: File) => {
293
+ hasInternalImage.value = false;
294
+ emit('image-upload-success', url, file);
295
+ };
296
+
297
+ const onImageClear = () => {
298
+ hasInternalImage.value = false;
299
+ emit('update:imageUrl', '');
300
+ };
301
+
302
+ defineExpose({
303
+ focus: () => inputRef.value?.focus?.(),
304
+ blur: () => inputRef.value?.blur?.(),
305
+ search: runSearch,
306
+ saveHistory: (val?: string) => history.push(val ?? innerValue.value),
307
+ });
308
+ </script>
309
+
310
+ <template>
311
+ <div ref="wrapperRef" class="o-search-input" :class="{ 'is-focus': isFocus }">
312
+ <div class="o-search-input-box">
313
+ <SearchImageInput
314
+ ref="inputRef"
315
+ v-model="innerValue"
316
+ :image-url="imageUrl"
317
+ :placeholder="placeholder"
318
+ :image-placeholder="imagePlaceholder"
319
+ :size="size"
320
+ :enable-image-search="enableImageSearch"
321
+ :upload-image="uploadImage"
322
+ :max-image-size="maxImageSize"
323
+ :image-upload-tooltip="imageUploadTooltip"
324
+ :allowed-image-types="allowedImageTypes"
325
+ :expanded="isFocus && !props.alwaysInlineThumbnail"
326
+ :inline-thumbnail="showInlineThumbnail"
327
+ :clearable="clearable"
328
+ class="o-search-input-field"
329
+ @update:imageUrl="(url: string) => emit('update:imageUrl', url)"
330
+ @focus="handleFocus"
331
+ @blur="handleBlur"
332
+ @enter="runSearch"
333
+ @clear="handleClear"
334
+ @image-clear="onImageClear"
335
+ @image-upload-start="onImageUploadStart"
336
+ @image-upload-success="onImageUploadSuccess"
337
+ @image-upload-error="(error: unknown, file: File) => emit('image-upload-error', error, file)"
338
+ @image-validate-error="(reason: 'size' | 'type', file: File) => emit('image-validate-error', reason, file)"
339
+ @preview-change="onPreviewChange"
340
+ >
341
+ <template #prefix><slot name="input-prefix" /></template>
342
+ <template #suffix="slotProps"><slot name="input-suffix" v-bind="slotProps" /></template>
343
+ <template v-if="$slots['image-preview']" #preview="slotProps">
344
+ <slot name="image-preview" v-bind="slotProps" />
345
+ </template>
346
+ </SearchImageInput>
347
+
348
+ <Transition name="o-search-input-dropdown">
349
+ <div v-if="showDropdown" class="o-search-input-dropdown">
350
+ <slot name="dropdown" :keyword="modelValue">
351
+ <SearchPanel
352
+ :keyword="modelValue"
353
+ :onestep-items="onestepItems"
354
+ :onestep-title="onestepTitle"
355
+ :suggest-items="suggestItems"
356
+ :suggest-title="suggestTitle"
357
+ :history-items="enableHistory ? history.items.value : []"
358
+ :history-title="historyTitle"
359
+ :hot-items="hotItems"
360
+ :hot-title="hotTitle"
361
+ :no-data-text="noDataText"
362
+ :highlight-keyword="highlightKeyword"
363
+ :hide-on-keyword="true"
364
+ :show-suggest-empty="showSuggestEmpty"
365
+ history-layout="list"
366
+ @onestep-click="handleOnestepClick"
367
+ @suggest-click="handleSuggestClick"
368
+ @history-click="handleHistoryClick"
369
+ @history-remove="handleHistoryRemove"
370
+ @history-clear="handleHistoryClear"
371
+ @hot-click="handleHotClick"
372
+ >
373
+ <template v-if="$slots['onestep-header']" #onestep-header="slotProps">
374
+ <slot name="onestep-header" v-bind="slotProps" />
375
+ </template>
376
+ <template v-if="$slots['onestep-content']" #onestep-content="slotProps">
377
+ <slot name="onestep-content" v-bind="slotProps" />
378
+ </template>
379
+ <template v-if="$slots['suggest-header']" #suggest-header="slotProps">
380
+ <slot name="suggest-header" v-bind="slotProps" />
381
+ </template>
382
+ <template v-if="$slots['suggest-content']" #suggest-content="slotProps">
383
+ <slot name="suggest-content" v-bind="slotProps" />
384
+ </template>
385
+ <template v-if="$slots['history-header']" #history-header="slotProps">
386
+ <slot name="history-header" v-bind="slotProps" />
387
+ </template>
388
+ <template v-if="$slots['history-content']" #history-content="slotProps">
389
+ <slot name="history-content" v-bind="slotProps" />
390
+ </template>
391
+ </SearchPanel>
392
+ </slot>
393
+ </div>
394
+ </Transition>
395
+ </div>
396
+
397
+ <div v-if="suggestList?.length" class="o-search-input-suggest-list-row">
398
+ <slot name="suggest-list" :items="suggestList">
399
+ <span class="o-search-input-suggest-list-label">{{ suggestListLabel ?? t('search.suggestListLabel') }}</span>
400
+ <ul class="o-search-input-suggest-list">
401
+ <li
402
+ v-for="(item, idx) in suggestList"
403
+ :key="item + idx"
404
+ class="o-search-input-suggest-list-item"
405
+ @click="handleSuggestListClick(item)"
406
+ >
407
+ <template v-for="seg in parseSuggest(item)" :key="seg.text + String(seg.em)">
408
+ <em v-if="seg.em">{{ seg.text }}</em>
409
+ <template v-else>{{ seg.text }}</template>
410
+ </template>
411
+ </li>
412
+ </ul>
413
+ </slot>
414
+ </div>
415
+ </div>
416
+ </template>
417
+
418
+ <style lang="scss" scoped>
419
+ .o-search-input {
420
+ position: relative;
421
+ width: 100%;
422
+ background-color: var(--o-color-fill2);
423
+ border-radius: var(--o-radius-xs);
424
+
425
+ :deep(.o-input.o_box-large) {
426
+ --_box-height: 48px;
427
+ }
428
+ :deep(.o-search-image-input-icon) {
429
+ font-size: 24px;
430
+ }
431
+ :deep(.o-search-image-input-upload-btn) {
432
+ width: 24px;
433
+ height: 24px;
434
+ }
435
+
436
+ @include respond-to('pad_v-pc_s') {
437
+ :deep(.o-input.o_box-large) {
438
+ --_box-height: 40px;
439
+ }
440
+ }
441
+
442
+ @include respond-to('<=pad_v') {
443
+ :deep(.o-input.o_box-large) {
444
+ --_box-height: 32px;
445
+ }
446
+ :deep(.o-search-image-input-icon) {
447
+ font-size: 16px;
448
+ }
449
+ :deep(.o-search-image-input-upload-btn) {
450
+ width: 16px;
451
+ height: 16px;
452
+ }
453
+ }
454
+ }
455
+
456
+ .o-search-input-box {
457
+ position: relative;
458
+ width: 100%;
459
+ }
460
+
461
+ .o-search-input-field {
462
+ width: 100%;
463
+ }
464
+
465
+ .o-search-input-dropdown {
466
+ position: absolute;
467
+ top: calc(100% + 4px);
468
+ left: 0;
469
+ right: 0;
470
+ z-index: 10;
471
+ padding: var(--o-gap-4);
472
+ background-color: var(--o-color-fill2);
473
+ border-radius: var(--o-radius-xs);
474
+ box-shadow: var(--o-shadow-2);
475
+ }
476
+
477
+ .o-search-input-dropdown-enter-active,
478
+ .o-search-input-dropdown-leave-active {
479
+ transition: opacity var(--o-duration-m1), transform var(--o-duration-m1);
480
+ }
481
+
482
+ .o-search-input-dropdown-enter-from,
483
+ .o-search-input-dropdown-leave-to {
484
+ opacity: 0;
485
+ transform: translateY(-4px);
486
+ }
487
+
488
+ .o-search-input-suggest-list-row {
489
+ display: flex;
490
+ margin-top: 8px;
491
+ align-items: center;
492
+ flex-wrap: wrap;
493
+ @include tip1;
494
+ color: var(--o-color-info1);
495
+ }
496
+
497
+ .o-search-input-suggest-list-label {
498
+ color: var(--o-color-info3);
499
+ margin-right: 4px;
500
+ @include tip1;
501
+ }
502
+
503
+ .o-search-input-suggest-list {
504
+ display: flex;
505
+ flex-wrap: wrap;
506
+ align-items: center;
507
+ padding: 0;
508
+ margin: 0;
509
+ list-style: none;
510
+ }
511
+
512
+ .o-search-input-suggest-list-item {
513
+ margin-right: 8px;
514
+ cursor: pointer;
515
+ color: var(--o-color-primary1);
516
+
517
+ :deep(em) {
518
+ color: var(--o-color-primary1);
519
+ font-style: normal;
520
+ }
521
+
522
+ @include hover {
523
+ text-decoration: underline;
524
+ }
525
+ }
526
+ </style>