@coffic/cosy-ui 0.6.14 → 0.6.18
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.
- package/dist/app.css +1 -1
- package/dist/vue/BannerBox/BannerBox.vue +21 -160
- package/dist/vue/BannerBox/DownloadButton.vue +202 -0
- package/dist/vue/BannerBox/ExampleDisplayModeAlways.vue +34 -0
- package/dist/vue/BannerBox/ExampleDisplayModeHover.vue +34 -0
- package/dist/vue/BannerBox/ExampleDisplayModeNever.vue +34 -0
- package/dist/vue/BannerBox/FeatureCard.vue +28 -28
- package/dist/vue/BannerBox/bgStyles.ts +55 -0
- package/dist/vue/BannerBox/index.ts +23 -16
- package/dist/vue/BannerBox/sizePresets.ts +23 -0
- package/dist/vue/MacWindow/MacWindow.vue +10 -4
- package/dist/vue/MacWindow/WithTabs.vue +2 -2
- package/package.json +1 -1
- package/dist/vue/BannerBox/CustomComponent.vue +0 -60
- package/dist/vue/BannerBox/DisplayMode.vue +0 -49
- package/dist/vue/shims-vue.d.ts +0 -5
- /package/dist/vue/BannerBox/{Basic.vue → ExampleBasic.vue} +0 -0
- /package/dist/vue/BannerBox/{CustomBg.vue → ExampleCustomBg.vue} +0 -0
- /package/dist/vue/BannerBox/{ImageExport.vue → ExampleImageExport.vue} +0 -0
- /package/dist/vue/BannerBox/{SizePreset.vue → ExampleSizePreset.vue} +0 -0
@@ -47,11 +47,9 @@ BannerBox 组件是一个可定制的横幅容器,支持自定义背景、尺
|
|
47
47
|
@prop {String} [title=''] - 横幅标题
|
48
48
|
@prop {String} [description=''] - 横幅描述
|
49
49
|
@prop {Array} [features=[]] - 特性列表,每项包含{emoji, title, link}
|
50
|
-
@prop {Object} [customComponent=null] - 自定义组件
|
51
|
-
@prop {Object} [customComponentProps={}] - 自定义组件的属性
|
52
50
|
|
53
51
|
@slots
|
54
|
-
@slot default -
|
52
|
+
@slot default - 横幅内容
|
55
53
|
-->
|
56
54
|
|
57
55
|
<script lang="ts">
|
@@ -59,9 +57,12 @@ import { ref, onMounted, watch, onUnmounted, computed, defineComponent } from 'v
|
|
59
57
|
import { RiDownloadLine } from '@remixicon/vue';
|
60
58
|
import { toPng } from 'html-to-image';
|
61
59
|
import FeatureCard from './FeatureCard.vue';
|
60
|
+
import DownloadButton from './DownloadButton.vue';
|
61
|
+
import { bgClasses } from './bgStyles';
|
62
|
+
import { sizePresets } from './sizePresets';
|
62
63
|
import '../../style'
|
63
64
|
|
64
|
-
interface
|
65
|
+
export interface IFeature {
|
65
66
|
emoji: string;
|
66
67
|
title: string;
|
67
68
|
link?: string;
|
@@ -71,7 +72,8 @@ export default defineComponent({
|
|
71
72
|
name: 'BannerBox',
|
72
73
|
components: {
|
73
74
|
RiDownloadLine,
|
74
|
-
FeatureCard
|
75
|
+
FeatureCard,
|
76
|
+
DownloadButton
|
75
77
|
},
|
76
78
|
props: {
|
77
79
|
displayMode: {
|
@@ -92,19 +94,9 @@ export default defineComponent({
|
|
92
94
|
default: ''
|
93
95
|
},
|
94
96
|
features: {
|
95
|
-
type: Array as () =>
|
97
|
+
type: Array as () => IFeature[],
|
96
98
|
default: () => []
|
97
99
|
},
|
98
|
-
// 自定义组件
|
99
|
-
customComponent: {
|
100
|
-
type: Object,
|
101
|
-
default: null
|
102
|
-
},
|
103
|
-
// 自定义组件的属性
|
104
|
-
customComponentProps: {
|
105
|
-
type: Object,
|
106
|
-
default: () => ({})
|
107
|
-
}
|
108
100
|
},
|
109
101
|
setup(props) {
|
110
102
|
const componentRef = ref<HTMLElement | null>(null);
|
@@ -112,19 +104,6 @@ export default defineComponent({
|
|
112
104
|
const isLoadedFromStorage = ref(false);
|
113
105
|
const selectedBgIndex = ref(props.backgroundClassIndex);
|
114
106
|
|
115
|
-
const sizePresets = [
|
116
|
-
{ name: 'Default', width: 'cosy:w-full', height: 'cosy:h-full' },
|
117
|
-
{ name: 'Square', width: 'cosy:w-[600px]', height: 'cosy:h-[600px]' },
|
118
|
-
{ name: 'Landscape', width: 'cosy:w-[800px]', height: 'cosy:h-[450px]' },
|
119
|
-
{ name: 'Portrait', width: 'cosy:w-[450px]', height: 'cosy:h-[800px]' },
|
120
|
-
{ name: 'Wide', width: 'cosy:w-[1200px]', height: 'cosy:h-[675px]' },
|
121
|
-
{ name: 'Banner', width: 'cosy:w-[1200px]', height: 'cosy:h-[300px]' },
|
122
|
-
{ name: '1280 × 800', width: 'cosy:w-[1280px]', height: 'cosy:h-[800px]' },
|
123
|
-
{ name: '1440 × 900', width: 'cosy:w-[1440px]', height: 'cosy:h-[900px]' },
|
124
|
-
{ name: '2560 × 1600', width: 'cosy:w-[2560px]', height: 'cosy:h-[1600px]' },
|
125
|
-
{ name: '2880 × 1800', width: 'cosy:w-[2880px]', height: 'cosy:h-[1800px]' },
|
126
|
-
];
|
127
|
-
|
128
107
|
const selectedSize = ref(sizePresets[0]);
|
129
108
|
|
130
109
|
const toggleDropdown = () => {
|
@@ -213,56 +192,6 @@ export default defineComponent({
|
|
213
192
|
}
|
214
193
|
};
|
215
194
|
|
216
|
-
const bgClasses = [
|
217
|
-
'cosy:bg-gradient-to-b cosy:from-blue-100/50 cosy:to-blue-200/50 dark:cosy:from-blue-500/10 dark:cosy:to-blue-200/10',
|
218
|
-
'cosy:bg-gradient-to-b cosy:from-blue-200/50 cosy:to-purple-200/50 dark:cosy:from-blue-500/10 dark:cosy:to-purple-200/10',
|
219
|
-
'cosy:bg-gradient-to-b cosy:from-yellow-200/50 cosy:to-green-200/50 dark:cosy:from-yellow-500/10 dark:cosy:to-green-200/10',
|
220
|
-
'cosy:bg-gradient-to-b cosy:from-teal-200/50 cosy:to-blue-200/50 dark:cosy:from-teal-500/10 dark:cosy:to-blue-200/10',
|
221
|
-
'cosy:bg-gradient-to-b cosy:from-pink-200/50 cosy:to-indigo-200/20 dark:cosy:from-pink-500/10 dark:cosy:to-indigo-200/10',
|
222
|
-
'cosy:bg-gradient-to-b cosy:from-red-200/50 cosy:to-orange-200/50 dark:cosy:from-red-500/10 dark:cosy:to-orange-200/10',
|
223
|
-
'cosy:bg-gradient-to-b cosy:from-orange-200/50 cosy:to-yellow-200/50 dark:cosy:from-orange-500/10 dark:cosy:to-yellow-200/10',
|
224
|
-
'cosy:bg-gradient-to-b cosy:from-green-200/50 cosy:to-teal-200/50 dark:cosy:from-green-500/10 dark:cosy:to-teal-200/10',
|
225
|
-
|
226
|
-
// 不透明的背景
|
227
|
-
'cosy:bg-gradient-to-b cosy:from-blue-100 cosy:to-blue-200 dark:cosy:from-blue-500 dark:cosy:to-blue-200',
|
228
|
-
'cosy:bg-gradient-to-b cosy:from-blue-200 cosy:to-purple-200 dark:cosy:from-blue-500 dark:cosy:to-purple-200',
|
229
|
-
'cosy:bg-gradient-to-b cosy:from-yellow-200 cosy:to-green-200 dark:cosy:from-yellow-500 dark:cosy:to-green-200',
|
230
|
-
'cosy:bg-gradient-to-b cosy:from-teal-200 cosy:to-blue-200 dark:cosy:from-teal-500 dark:cosy:to-blue-200',
|
231
|
-
'cosy:bg-gradient-to-b cosy:from-pink-200 cosy:to-red-200 dark:cosy:from-pink-500 dark:cosy:to-red-200',
|
232
|
-
'cosy:bg-gradient-to-b cosy:from-red-200 cosy:to-orange-200 dark:cosy:from-red-500 dark:cosy:to-orange-200',
|
233
|
-
'cosy:bg-gradient-to-b cosy:from-orange-200 cosy:to-yellow-200 dark:cosy:from-orange-500 dark:cosy:to-yellow-200',
|
234
|
-
'cosy:bg-gradient-to-b cosy:from-green-200 cosy:to-teal-200 dark:cosy:from-green-500 dark:cosy:to-teal-200',
|
235
|
-
|
236
|
-
// 不透明的深色背景
|
237
|
-
'cosy:bg-gradient-to-b cosy:from-blue-900 cosy:to-blue-200 dark:cosy:from-blue-900 dark:cosy:to-blue-200',
|
238
|
-
'cosy:bg-gradient-to-b cosy:from-blue-900 cosy:to-purple-200 dark:cosy:from-blue-900 dark:cosy:to-purple-200',
|
239
|
-
'cosy:bg-gradient-to-b cosy:from-yellow-900 cosy:to-green-200 dark:cosy:from-yellow-900 dark:cosy:to-green-200',
|
240
|
-
'cosy:bg-gradient-to-b cosy:from-teal-900 cosy:to-blue-200 dark:cosy:from-teal-900 dark:cosy:to-blue-200',
|
241
|
-
'cosy:bg-gradient-to-b cosy:from-pink-900 cosy:to-red-200 dark:cosy:from-pink-900 dark:cosy:to-red-200',
|
242
|
-
'cosy:bg-gradient-to-b cosy:from-red-900 cosy:to-orange-200 dark:cosy:from-red-900 dark:cosy:to-orange-200',
|
243
|
-
'cosy:bg-gradient-to-b cosy:from-orange-900 cosy:to-yellow-200 dark:cosy:from-orange-900 dark:cosy:to-yellow-200',
|
244
|
-
'cosy:bg-gradient-to-b cosy:from-green-900 cosy:to-teal-900 dark:cosy:from-green-900 dark:cosy:to-teal-900',
|
245
|
-
// 不透明的渐变背景
|
246
|
-
'cosy:bg-gradient-to-br cosy:from-emerald-400 cosy:to-cyan-400 dark:cosy:from-emerald-600 dark:cosy:to-cyan-600',
|
247
|
-
'cosy:bg-gradient-to-br cosy:from-violet-400 cosy:to-fuchsia-400 dark:cosy:from-violet-600 dark:cosy:to-fuchsia-600',
|
248
|
-
'cosy:bg-gradient-to-br cosy:from-amber-400 cosy:to-orange-400 dark:cosy:from-amber-600 dark:cosy:to-orange-600',
|
249
|
-
'cosy:bg-gradient-to-br cosy:from-rose-400 cosy:to-pink-400 dark:cosy:from-rose-600 dark:cosy:to-pink-600',
|
250
|
-
'cosy:bg-gradient-to-br cosy:from-sky-400 cosy:to-indigo-400 dark:cosy:from-sky-600 dark:cosy:to-indigo-600',
|
251
|
-
'cosy:bg-gradient-to-br cosy:from-lime-400 cosy:to-emerald-400 dark:cosy:from-lime-600 dark:cosy:to-emerald-600',
|
252
|
-
'cosy:bg-gradient-to-br cosy:from-purple-400 cosy:to-indigo-400 dark:cosy:from-purple-600 dark:cosy:to-indigo-600',
|
253
|
-
'cosy:bg-gradient-to-br cosy:from-blue-400 cosy:to-violet-400 dark:cosy:from-blue-600 dark:cosy:to-violet-600',
|
254
|
-
|
255
|
-
// 纯色背景
|
256
|
-
'cosy:bg-emerald-400 dark:cosy:bg-emerald-600',
|
257
|
-
'cosy:bg-violet-400 dark:cosy:bg-violet-600',
|
258
|
-
'cosy:bg-amber-400 dark:cosy:bg-amber-600',
|
259
|
-
'cosy:bg-rose-400 dark:cosy:bg-rose-600',
|
260
|
-
'cosy:bg-sky-400 dark:cosy:bg-sky-600',
|
261
|
-
'cosy:bg-lime-400 dark:cosy:bg-lime-600',
|
262
|
-
'cosy:bg-purple-400 dark:cosy:bg-purple-600',
|
263
|
-
'cosy:bg-blue-400 dark:cosy:bg-blue-600'
|
264
|
-
];
|
265
|
-
|
266
195
|
const getBackgroundClass = (): string => {
|
267
196
|
return bgClasses[selectedBgIndex.value % bgClasses.length];
|
268
197
|
}
|
@@ -280,9 +209,6 @@ export default defineComponent({
|
|
280
209
|
window.removeEventListener('bannerBoxSizeChange', handleSizeChange);
|
281
210
|
});
|
282
211
|
|
283
|
-
// 是否显示Banner内容
|
284
|
-
const showBannerContent = computed(() => props.title !== '' || props.description !== '' || props.features.length > 0 || props.customComponent !== null);
|
285
|
-
|
286
212
|
// 计算下载按钮是否显示及其样式类
|
287
213
|
const downloadButtonStyles = computed(() => {
|
288
214
|
switch (props.displayMode) {
|
@@ -320,8 +246,8 @@ export default defineComponent({
|
|
320
246
|
downloadAsImage,
|
321
247
|
getBackgroundClass,
|
322
248
|
clearStoredSize,
|
323
|
-
|
324
|
-
|
249
|
+
downloadButtonStyles,
|
250
|
+
bgClasses
|
325
251
|
};
|
326
252
|
}
|
327
253
|
});
|
@@ -329,88 +255,25 @@ export default defineComponent({
|
|
329
255
|
|
330
256
|
<template>
|
331
257
|
<div class="cosy:relative cosy:w-full cosy:rounded-2xl cosy:max-w-7xl cosy:mx-auto">
|
332
|
-
<!--
|
258
|
+
<!-- Size indicator -->
|
333
259
|
<div v-if="isLoadedFromStorage"
|
334
260
|
class="cosy:absolute cosy:top-4 cosy:right-4 cosy:bg-yellow-500/30 cosy:backdrop-blur-sm cosy:px-3 cosy:py-1 cosy:rounded-lg cosy:text-sm cosy:text-white">
|
335
261
|
{{ selectedSize.name }}
|
336
262
|
</div>
|
337
263
|
|
338
264
|
<!-- Download button with dropdown menu -->
|
339
|
-
<
|
340
|
-
:
|
341
|
-
|
342
|
-
|
343
|
-
class="cosy:bg-yellow-500/30 cosy:backdrop-blur-sm cosy:p-2 cosy:rounded-lg hover:cosy:bg-yellow-500/40"
|
344
|
-
@click="toggleDropdown">
|
345
|
-
<RiDownloadLine class="cosy:w-6 cosy:h-6 cosy:text-white" />
|
346
|
-
</button>
|
347
|
-
<!-- Size selection dropdown -->
|
348
|
-
<div v-if="isDropdownOpen"
|
349
|
-
class="cosy:absolute cosy:left-0 cosy:mt-2 cosy:w-96 cosy:bg-white dark:cosy:bg-gray-800 cosy:rounded-lg cosy:shadow-lg cosy:py-2 cosy:z-50">
|
350
|
-
<!-- Component size presets -->
|
351
|
-
<div class="cosy:px-4 cosy:py-2 cosy:border-b cosy:border-gray-200 dark:cosy:border-gray-700">
|
352
|
-
<div class="cosy:grid cosy:grid-cols-3 cosy:gap-2">
|
353
|
-
<button v-for="preset in sizePresets" :key="preset.name" :class="[
|
354
|
-
'cosy:p-2 cosy:text-left cosy:rounded cosy:text-sm',
|
355
|
-
selectedSize.name === preset.name
|
356
|
-
? 'cosy:bg-yellow-500/30 cosy:text-yellow-900 dark:cosy:text-yellow-100'
|
357
|
-
: 'hover:cosy:bg-gray-100 dark:hover:cosy:bg-gray-700'
|
358
|
-
]" @click="selectedSize = preset">
|
359
|
-
<div class="cosy:flex cosy:flex-col">
|
360
|
-
<span class="cosy:font-medium">{{ preset.name }}</span>
|
361
|
-
<span class="cosy:text-xs cosy:text-gray-500 dark:cosy:text-gray-400">
|
362
|
-
{{ preset.width.replace('cosy:w-[', '').replace(']', '') }}
|
363
|
-
</span>
|
364
|
-
</div>
|
365
|
-
</button>
|
366
|
-
<!-- Clear size button -->
|
367
|
-
<button
|
368
|
-
class="cosy:p-2 cosy:text-left cosy:rounded cosy:text-sm hover:cosy:bg-gray-100 dark:hover:cosy:bg-gray-700"
|
369
|
-
@click="clearStoredSize">
|
370
|
-
<div class="cosy:flex cosy:flex-col">
|
371
|
-
<span
|
372
|
-
class="cosy:font-medium cosy:text-red-600 dark:cosy:text-red-400">清除记住的尺寸</span>
|
373
|
-
<span class="cosy:text-xs cosy:text-gray-500 dark:cosy:text-gray-400">重置为默认尺寸</span>
|
374
|
-
</div>
|
375
|
-
</button>
|
376
|
-
</div>
|
377
|
-
</div>
|
378
|
-
<!-- Background settings -->
|
379
|
-
<div class="cosy:px-4 cosy:py-2 cosy:border-b cosy:border-gray-200 dark:cosy:border-gray-700">
|
380
|
-
<div class="cosy:mt-2">
|
381
|
-
<div class="cosy:grid cosy:grid-cols-8 cosy:gap-2">
|
382
|
-
<button v-for="(_, index) in bgClasses" :key="index" :class="[
|
383
|
-
bgClasses[index],
|
384
|
-
'cosy:w-8 cosy:h-8 cosy:rounded-lg cosy:border-2',
|
385
|
-
selectedBgIndex === index ? 'cosy:border-yellow-500' : 'cosy:border-transparent'
|
386
|
-
]" @click="selectedBgIndex = index" />
|
387
|
-
</div>
|
388
|
-
</div>
|
389
|
-
</div>
|
390
|
-
<!-- Size options -->
|
391
|
-
<div class="cosy:p-4">
|
392
|
-
<button
|
393
|
-
class="cosy:w-full cosy:p-2 cosy:text-center cosy:rounded hover:cosy:bg-gray-100 dark:hover:cosy:bg-gray-700"
|
394
|
-
@click="downloadAsImage()">
|
395
|
-
<div class="cosy:flex cosy:items-center cosy:justify-center cosy:gap-2">
|
396
|
-
<RiDownloadLine class="cosy:w-4 cosy:h-4" />
|
397
|
-
<span class="cosy:font-medium">下载图片</span>
|
398
|
-
</div>
|
399
|
-
</button>
|
400
|
-
</div>
|
401
|
-
</div>
|
402
|
-
</div>
|
403
|
-
</div>
|
265
|
+
<DownloadButton :displayMode="displayMode" :isLoadedFromStorage="isLoadedFromStorage"
|
266
|
+
:selectedSize="selectedSize" :selectedBgIndex="selectedBgIndex" @update:selectedSize="selectedSize = $event"
|
267
|
+
@update:selectedBgIndex="selectedBgIndex = $event" @clear-stored-size="clearStoredSize"
|
268
|
+
@download-image="downloadAsImage" />
|
404
269
|
|
405
270
|
<div ref="componentRef" class="cosy:flex cosy:p-8 cosy:rounded-2xl cosy:shadow" :class="[
|
406
271
|
getBackgroundClass(),
|
407
272
|
selectedSize.width,
|
408
273
|
selectedSize.height
|
409
274
|
]">
|
410
|
-
|
411
|
-
|
412
|
-
data-type="smart-banner">
|
413
|
-
<h2 class="cosy:text-4xl cosy:mb-4">
|
275
|
+
<div class="cosy:py-16 cosy:px-8 cosy:text-center cosy:w-full cosy:rounded-2xl" data-type="smart-banner">
|
276
|
+
<h2 v-if="title.length > 0" class="cosy:text-4xl cosy:mb-4">
|
414
277
|
{{ title }}
|
415
278
|
</h2>
|
416
279
|
|
@@ -418,18 +281,16 @@ export default defineComponent({
|
|
418
281
|
{{ description }}
|
419
282
|
</p>
|
420
283
|
|
421
|
-
<div
|
284
|
+
<div v-if="features.length > 0"
|
285
|
+
class="cosy:flex cosy:flex-row cosy:justify-center cosy:gap-8 cosy:mx-auto cosy:w-full cosy:mt-24">
|
422
286
|
<FeatureCard v-for="feature in features" :key="feature.title" :emoji="feature.emoji"
|
423
287
|
:title="feature.title" :link="feature.link" />
|
424
288
|
</div>
|
425
289
|
|
426
|
-
<div class="cosy:mt-12">
|
427
|
-
<
|
290
|
+
<div :class="{ 'cosy:mt-12': title.length > 0 || description.length > 0 || features.length > 0 }">
|
291
|
+
<slot />
|
428
292
|
</div>
|
429
293
|
</div>
|
430
|
-
|
431
|
-
<!-- Default slot for custom content (when banner prop is not provided) -->
|
432
|
-
<slot v-if="!showBannerContent" />
|
433
294
|
</div>
|
434
295
|
</div>
|
435
296
|
</template>
|
@@ -0,0 +1,202 @@
|
|
1
|
+
<!--
|
2
|
+
@component DownloadButton
|
3
|
+
|
4
|
+
@description
|
5
|
+
DownloadButton 组件提供了一个下载按钮,带有可展开的下拉菜单,用于调整尺寸、背景和下载图片。
|
6
|
+
|
7
|
+
@usage
|
8
|
+
基本用法:
|
9
|
+
```vue
|
10
|
+
<DownloadButton
|
11
|
+
:displayMode="'hover'"
|
12
|
+
:isLoadedFromStorage="true"
|
13
|
+
:selectedSize="selectedSize"
|
14
|
+
:selectedBgIndex="selectedBgIndex"
|
15
|
+
:sizePresets="sizePresets"
|
16
|
+
:bgClasses="bgClasses"
|
17
|
+
@update:selectedSize="selectedSize = $event"
|
18
|
+
@update:selectedBgIndex="selectedBgIndex = $event"
|
19
|
+
@clear-stored-size="clearStoredSize"
|
20
|
+
@download-image="downloadAsImage"
|
21
|
+
/>
|
22
|
+
```
|
23
|
+
|
24
|
+
@props
|
25
|
+
@prop {String} [displayMode='hover'] - 下载按钮显示模式:'always'(总是显示),'hover'(悬停显示),'never'(不显示)
|
26
|
+
@prop {Boolean} [isLoadedFromStorage=false] - 是否已从存储中加载尺寸
|
27
|
+
@prop {Object} [selectedSize] - 当前选中的尺寸预设
|
28
|
+
@prop {Number} [selectedBgIndex=0] - 当前选中的背景样式索引
|
29
|
+
@prop {Array} [sizePresets] - 尺寸预设列表
|
30
|
+
@prop {Array} [bgClasses] - 背景样式类列表
|
31
|
+
|
32
|
+
@emits
|
33
|
+
@emit update:selectedSize - 当选择新的尺寸预设时触发
|
34
|
+
@emit update:selectedBgIndex - 当选择新的背景样式时触发
|
35
|
+
@emit clear-stored-size - 当清除存储的尺寸时触发
|
36
|
+
@emit download-image - 当请求下载图片时触发
|
37
|
+
-->
|
38
|
+
|
39
|
+
<script lang="ts">
|
40
|
+
import { ref, computed, defineComponent } from 'vue';
|
41
|
+
import { RiDownloadLine } from '@remixicon/vue';
|
42
|
+
import { bgClasses } from './bgStyles';
|
43
|
+
import { sizePresets, type SizePreset } from './sizePresets';
|
44
|
+
import '../../style'
|
45
|
+
|
46
|
+
export default defineComponent({
|
47
|
+
name: 'DownloadButton',
|
48
|
+
components: {
|
49
|
+
RiDownloadLine
|
50
|
+
},
|
51
|
+
props: {
|
52
|
+
displayMode: {
|
53
|
+
type: String,
|
54
|
+
default: 'hover',
|
55
|
+
validator: (value: string) => ['always', 'hover', 'never'].includes(value)
|
56
|
+
},
|
57
|
+
isLoadedFromStorage: {
|
58
|
+
type: Boolean,
|
59
|
+
default: false
|
60
|
+
},
|
61
|
+
selectedSize: {
|
62
|
+
type: Object as () => SizePreset,
|
63
|
+
required: true
|
64
|
+
},
|
65
|
+
selectedBgIndex: {
|
66
|
+
type: Number,
|
67
|
+
default: 0
|
68
|
+
}
|
69
|
+
},
|
70
|
+
emits: ['update:selectedSize', 'update:selectedBgIndex', 'clear-stored-size', 'download-image'],
|
71
|
+
setup(props, { emit }) {
|
72
|
+
const isDropdownOpen = ref(false);
|
73
|
+
|
74
|
+
const toggleDropdown = () => {
|
75
|
+
isDropdownOpen.value = !isDropdownOpen.value;
|
76
|
+
};
|
77
|
+
|
78
|
+
// 计算下载按钮是否显示及其样式类
|
79
|
+
const downloadButtonStyles = computed(() => {
|
80
|
+
switch (props.displayMode) {
|
81
|
+
case 'always':
|
82
|
+
return {
|
83
|
+
show: true,
|
84
|
+
classes: 'cosy:opacity-100'
|
85
|
+
};
|
86
|
+
case 'hover':
|
87
|
+
return {
|
88
|
+
show: true,
|
89
|
+
classes: 'cosy:opacity-0 cosy:hover:opacity-100 cosy:transition-opacity'
|
90
|
+
};
|
91
|
+
case 'never':
|
92
|
+
return {
|
93
|
+
show: false,
|
94
|
+
classes: ''
|
95
|
+
};
|
96
|
+
default:
|
97
|
+
return {
|
98
|
+
show: true,
|
99
|
+
classes: 'cosy:opacity-0 cosy:hover:opacity-100 cosy:transition-opacity'
|
100
|
+
};
|
101
|
+
}
|
102
|
+
});
|
103
|
+
|
104
|
+
const selectSize = (size: SizePreset) => {
|
105
|
+
emit('update:selectedSize', size);
|
106
|
+
};
|
107
|
+
|
108
|
+
const selectBackground = (index: number) => {
|
109
|
+
emit('update:selectedBgIndex', index);
|
110
|
+
};
|
111
|
+
|
112
|
+
const clearStoredSize = () => {
|
113
|
+
emit('clear-stored-size');
|
114
|
+
isDropdownOpen.value = false;
|
115
|
+
};
|
116
|
+
|
117
|
+
const downloadImage = () => {
|
118
|
+
emit('download-image');
|
119
|
+
isDropdownOpen.value = false;
|
120
|
+
};
|
121
|
+
|
122
|
+
return {
|
123
|
+
isDropdownOpen,
|
124
|
+
toggleDropdown,
|
125
|
+
downloadButtonStyles,
|
126
|
+
selectSize,
|
127
|
+
selectBackground,
|
128
|
+
clearStoredSize,
|
129
|
+
downloadImage,
|
130
|
+
bgClasses,
|
131
|
+
sizePresets
|
132
|
+
};
|
133
|
+
}
|
134
|
+
});
|
135
|
+
</script>
|
136
|
+
|
137
|
+
<template>
|
138
|
+
<div v-if="downloadButtonStyles.show" class="cosy:absolute cosy:top-4 cosy:left-4"
|
139
|
+
:class="downloadButtonStyles.classes">
|
140
|
+
<div class="cosy:relative" data-dropdown>
|
141
|
+
<button
|
142
|
+
class="cosy:bg-yellow-500/30 cosy:backdrop-blur-sm cosy:p-2 cosy:rounded-lg hover:cosy:bg-yellow-500/40"
|
143
|
+
@click="toggleDropdown">
|
144
|
+
<RiDownloadLine class="cosy:w-6 cosy:h-6 cosy:text-white" />
|
145
|
+
</button>
|
146
|
+
<!-- Size selection dropdown -->
|
147
|
+
<div v-if="isDropdownOpen"
|
148
|
+
class="cosy:absolute cosy:left-0 cosy:mt-2 cosy:w-96 cosy:bg-white dark:cosy:bg-gray-800 cosy:rounded-lg cosy:shadow-lg cosy:py-2 cosy:z-50">
|
149
|
+
<!-- Component size presets -->
|
150
|
+
<div class="cosy:px-4 cosy:py-2 cosy:border-b cosy:border-gray-200 dark:cosy:border-gray-700">
|
151
|
+
<div class="cosy:grid cosy:grid-cols-3 cosy:gap-2">
|
152
|
+
<button v-for="preset in sizePresets" :key="preset.name" :class="[
|
153
|
+
'cosy:p-2 cosy:text-left cosy:rounded cosy:text-sm',
|
154
|
+
selectedSize.name === preset.name
|
155
|
+
? 'cosy:bg-yellow-500/30 cosy:text-yellow-900 dark:cosy:text-yellow-100'
|
156
|
+
: 'hover:cosy:bg-gray-100 dark:hover:cosy:bg-gray-700'
|
157
|
+
]" @click="selectSize(preset)">
|
158
|
+
<div class="cosy:flex cosy:flex-col">
|
159
|
+
<span class="cosy:font-medium">{{ preset.name }}</span>
|
160
|
+
<span class="cosy:text-xs cosy:text-gray-500 dark:cosy:text-gray-400">
|
161
|
+
{{ preset.width.replace('cosy:w-[', '').replace(']', '') }}
|
162
|
+
</span>
|
163
|
+
</div>
|
164
|
+
</button>
|
165
|
+
<!-- Clear size button -->
|
166
|
+
<button
|
167
|
+
class="cosy:p-2 cosy:text-left cosy:rounded cosy:text-sm hover:cosy:bg-gray-100 dark:hover:cosy:bg-gray-700"
|
168
|
+
@click="clearStoredSize">
|
169
|
+
<div class="cosy:flex cosy:flex-col">
|
170
|
+
<span class="cosy:font-medium cosy:text-red-600 dark:cosy:text-red-400">清除记住的尺寸</span>
|
171
|
+
<span class="cosy:text-xs cosy:text-gray-500 dark:cosy:text-gray-400">重置为默认尺寸</span>
|
172
|
+
</div>
|
173
|
+
</button>
|
174
|
+
</div>
|
175
|
+
</div>
|
176
|
+
<!-- Background settings -->
|
177
|
+
<div class="cosy:px-4 cosy:py-2 cosy:border-b cosy:border-gray-200 dark:cosy:border-gray-700">
|
178
|
+
<div class="cosy:mt-2">
|
179
|
+
<div class="cosy:grid cosy:grid-cols-8 cosy:gap-2">
|
180
|
+
<button v-for="(className, index) in bgClasses" :key="index" :class="[
|
181
|
+
className,
|
182
|
+
'cosy:w-8 cosy:h-8 cosy:rounded-lg cosy:border-2',
|
183
|
+
selectedBgIndex === index ? 'cosy:border-yellow-500' : 'cosy:border-transparent'
|
184
|
+
]" @click="selectBackground(index)" />
|
185
|
+
</div>
|
186
|
+
</div>
|
187
|
+
</div>
|
188
|
+
<!-- Size options -->
|
189
|
+
<div class="cosy:p-4">
|
190
|
+
<button
|
191
|
+
class="cosy:w-full cosy:p-2 cosy:text-center cosy:rounded hover:cosy:bg-gray-100 dark:hover:cosy:bg-gray-700"
|
192
|
+
@click="downloadImage">
|
193
|
+
<div class="cosy:flex cosy:items-center cosy:justify-center cosy:gap-2">
|
194
|
+
<RiDownloadLine class="cosy:w-4 cosy:h-4" />
|
195
|
+
<span class="cosy:font-medium">下载图片</span>
|
196
|
+
</div>
|
197
|
+
</button>
|
198
|
+
</div>
|
199
|
+
</div>
|
200
|
+
</div>
|
201
|
+
</div>
|
202
|
+
</template>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
<!--
|
2
|
+
@component BannerBox.DisplayModeAlways
|
3
|
+
|
4
|
+
@description
|
5
|
+
BannerBox 组件的"总是显示下载按钮"模式示例。
|
6
|
+
|
7
|
+
@usage
|
8
|
+
```vue
|
9
|
+
<BannerBoxExamples.DisplayModeAlways />
|
10
|
+
```
|
11
|
+
-->
|
12
|
+
|
13
|
+
<script lang="ts">
|
14
|
+
import '../../app.css'
|
15
|
+
import { defineComponent } from 'vue'
|
16
|
+
import BannerBox from './BannerBox.vue'
|
17
|
+
|
18
|
+
export default defineComponent({
|
19
|
+
name: 'BannerBoxDisplayModeAlwaysExample',
|
20
|
+
components: {
|
21
|
+
BannerBox
|
22
|
+
}
|
23
|
+
})
|
24
|
+
</script>
|
25
|
+
|
26
|
+
<template>
|
27
|
+
<div>
|
28
|
+
<BannerBox displayMode="always" :backgroundClassIndex="1">
|
29
|
+
<div class="cosy:flex cosy:items-center cosy:justify-center cosy:min-h-[150px]">
|
30
|
+
<p>下载按钮始终可见</p>
|
31
|
+
</div>
|
32
|
+
</BannerBox>
|
33
|
+
</div>
|
34
|
+
</template>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
<!--
|
2
|
+
@component BannerBox.DisplayModeHover
|
3
|
+
|
4
|
+
@description
|
5
|
+
BannerBox 组件的"悬停时显示下载按钮"模式示例,这是默认行为。
|
6
|
+
|
7
|
+
@usage
|
8
|
+
```vue
|
9
|
+
<BannerBoxExamples.DisplayModeHover />
|
10
|
+
```
|
11
|
+
-->
|
12
|
+
|
13
|
+
<script lang="ts">
|
14
|
+
import '../../app.css'
|
15
|
+
import { defineComponent } from 'vue'
|
16
|
+
import BannerBox from './BannerBox.vue'
|
17
|
+
|
18
|
+
export default defineComponent({
|
19
|
+
name: 'BannerBoxDisplayModeHoverExample',
|
20
|
+
components: {
|
21
|
+
BannerBox
|
22
|
+
}
|
23
|
+
})
|
24
|
+
</script>
|
25
|
+
|
26
|
+
<template>
|
27
|
+
<div>
|
28
|
+
<BannerBox displayMode="hover" :backgroundClassIndex="2">
|
29
|
+
<div class="cosy:flex cosy:items-center cosy:justify-center cosy:min-h-[150px]">
|
30
|
+
<p>鼠标悬停时显示下载按钮</p>
|
31
|
+
</div>
|
32
|
+
</BannerBox>
|
33
|
+
</div>
|
34
|
+
</template>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
<!--
|
2
|
+
@component BannerBox.DisplayModeNever
|
3
|
+
|
4
|
+
@description
|
5
|
+
BannerBox 组件的"不显示下载按钮"模式示例。
|
6
|
+
|
7
|
+
@usage
|
8
|
+
```vue
|
9
|
+
<BannerBoxExamples.DisplayModeNever />
|
10
|
+
```
|
11
|
+
-->
|
12
|
+
|
13
|
+
<script lang="ts">
|
14
|
+
import '../../app.css'
|
15
|
+
import { defineComponent } from 'vue'
|
16
|
+
import BannerBox from './BannerBox.vue'
|
17
|
+
|
18
|
+
export default defineComponent({
|
19
|
+
name: 'BannerBoxDisplayModeNeverExample',
|
20
|
+
components: {
|
21
|
+
BannerBox
|
22
|
+
}
|
23
|
+
})
|
24
|
+
</script>
|
25
|
+
|
26
|
+
<template>
|
27
|
+
<div>
|
28
|
+
<BannerBox displayMode="never" :backgroundClassIndex="3">
|
29
|
+
<div class="cosy:flex cosy:items-center cosy:justify-center cosy:min-h-[150px]">
|
30
|
+
<p>不显示下载按钮</p>
|
31
|
+
</div>
|
32
|
+
</BannerBox>
|
33
|
+
</div>
|
34
|
+
</template>
|
@@ -1,30 +1,3 @@
|
|
1
|
-
<template>
|
2
|
-
<component :is="link ? 'a' : 'div'" :href="link || undefined" :target="link ? '_blank' : undefined"
|
3
|
-
:rel="link ? 'noopener noreferrer' : undefined" :class="[
|
4
|
-
'card bg-base-100/10 backdrop-blur-lg p-8 transition-all duration-300 hover:-translate-y-1 shadow-lg',
|
5
|
-
{
|
6
|
-
'hover:bg-primary/15 cursor-pointer': link,
|
7
|
-
'cursor-default': !link
|
8
|
-
}
|
9
|
-
]">
|
10
|
-
<div class="card-body p-0">
|
11
|
-
<div class="mb-4">
|
12
|
-
<component :is="icon" v-if="icon" class="text-4xl text-base-content" />
|
13
|
-
<component :is="getPresetIcon" v-else-if="presetIcon" class="text-4xl text-base-content" />
|
14
|
-
<div v-else class="text-4xl text-base-content">
|
15
|
-
{{ emoji }}
|
16
|
-
</div>
|
17
|
-
</div>
|
18
|
-
<h3 class="card-title text-lg font-medium text-base-content">
|
19
|
-
{{ title }}
|
20
|
-
</h3>
|
21
|
-
<p v-if="description" class="text-base-content/70">
|
22
|
-
{{ description }}
|
23
|
-
</p>
|
24
|
-
</div>
|
25
|
-
</component>
|
26
|
-
</template>
|
27
|
-
|
28
1
|
<script setup lang="ts">
|
29
2
|
import { computed, type Component } from 'vue';
|
30
3
|
import {
|
@@ -187,4 +160,31 @@ const getPresetIcon = computed(() => {
|
|
187
160
|
if (!props.presetIcon) return null;
|
188
161
|
return presetIcons[props.presetIcon];
|
189
162
|
});
|
190
|
-
</script>
|
163
|
+
</script>
|
164
|
+
|
165
|
+
<template>
|
166
|
+
<component :is="link ? 'a' : 'div'" :href="link || undefined" :target="link ? '_blank' : undefined"
|
167
|
+
:rel="link ? 'noopener noreferrer' : undefined" :class="[
|
168
|
+
'cosy:card cosy:no-underline cosy:bg-base-100/10 cosy:backdrop-blur-lg cosy:p-8 cosy:transition-all cosy:duration-300 cosy:hover:-translate-y-1 cosy:shadow-lg',
|
169
|
+
{
|
170
|
+
'cosy:hover:bg-primary/15 cosy:cursor-pointer': link,
|
171
|
+
'cosy:cursor-default': !link
|
172
|
+
}
|
173
|
+
]">
|
174
|
+
<div class="card-body cosy:p-0">
|
175
|
+
<div class="cosy:mb-4">
|
176
|
+
<component :is="icon" v-if="icon" class="cosy:text-4xl cosy:text-base-content" />
|
177
|
+
<component :is="getPresetIcon" v-else-if="presetIcon" class="cosy:text-4xl cosy:text-base-content" />
|
178
|
+
<div v-else class="cosy:text-4xl cosy:text-base-content">
|
179
|
+
{{ emoji }}
|
180
|
+
</div>
|
181
|
+
</div>
|
182
|
+
<h3 class="card-title cosy:text-lg cosy:font-medium cosy:text-base-content">
|
183
|
+
{{ title }}
|
184
|
+
</h3>
|
185
|
+
<p v-if="description" class="cosy:text-base-content/70">
|
186
|
+
{{ description }}
|
187
|
+
</p>
|
188
|
+
</div>
|
189
|
+
</component>
|
190
|
+
</template>
|