@kine-design/core 0.0.1-beta.12 → 0.0.1-beta.13
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/components/base/image/__tests__/useImage.test.ts +61 -0
- package/components/base/image/api.ts +2 -2
- package/components/base/image/index.ts +2 -2
- package/components/base/image/props.d.ts +15 -4
- package/components/base/image/useImage.ts +27 -16
- package/dist/components/base/image/index.d.ts +2 -2
- package/dist/components/base/image/useImage.d.ts +7 -0
- package/dist/core.js +25 -8
- package/package.json +1 -1
|
@@ -173,4 +173,65 @@ describe('useImage', () => {
|
|
|
173
173
|
expect(previewScale.value).toBe(1);
|
|
174
174
|
expect(previewRotate.value).toBe(0);
|
|
175
175
|
});
|
|
176
|
+
|
|
177
|
+
// ── 混合媒体预览 ──
|
|
178
|
+
|
|
179
|
+
it('纯字符串列表 currentPreviewItem.type 为 image', () => {
|
|
180
|
+
const emit = createEmit();
|
|
181
|
+
const props = { ...defaultProps, src: 'a.png', previewSrcList: ['a.png', 'b.png'] };
|
|
182
|
+
const { currentPreviewItem, openPreview } = useImage(props, emit);
|
|
183
|
+
|
|
184
|
+
openPreview();
|
|
185
|
+
expect(currentPreviewItem.value).toEqual({ src: 'a.png', type: 'image' });
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('混合列表定位到 PreviewItem 对象', () => {
|
|
189
|
+
const emit = createEmit();
|
|
190
|
+
const props = {
|
|
191
|
+
...defaultProps,
|
|
192
|
+
src: 'video.mp4',
|
|
193
|
+
previewSrcList: [
|
|
194
|
+
'a.png',
|
|
195
|
+
{ src: 'video.mp4', type: 'video' as const },
|
|
196
|
+
'c.png',
|
|
197
|
+
],
|
|
198
|
+
};
|
|
199
|
+
const { currentPreviewItem, currentPreviewIsVideo, previewIndex, openPreview } = useImage(props, emit);
|
|
200
|
+
|
|
201
|
+
openPreview();
|
|
202
|
+
expect(previewIndex.value).toBe(1);
|
|
203
|
+
expect(currentPreviewItem.value).toEqual({ src: 'video.mp4', type: 'video' });
|
|
204
|
+
expect(currentPreviewIsVideo.value).toBe(true);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('切换时 currentPreviewIsVideo 跟随变化', () => {
|
|
208
|
+
const emit = createEmit();
|
|
209
|
+
const props = {
|
|
210
|
+
...defaultProps,
|
|
211
|
+
src: 'a.png',
|
|
212
|
+
previewSrcList: [
|
|
213
|
+
'a.png',
|
|
214
|
+
{ src: 'video.mp4', type: 'video' as const },
|
|
215
|
+
],
|
|
216
|
+
};
|
|
217
|
+
const { currentPreviewIsVideo, openPreview, previewNext } = useImage(props, emit);
|
|
218
|
+
|
|
219
|
+
openPreview();
|
|
220
|
+
expect(currentPreviewIsVideo.value).toBe(false);
|
|
221
|
+
|
|
222
|
+
previewNext();
|
|
223
|
+
expect(currentPreviewIsVideo.value).toBe(true);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('PreviewItem 不指定 type 时默认为 image', () => {
|
|
227
|
+
const emit = createEmit();
|
|
228
|
+
const props = {
|
|
229
|
+
...defaultProps,
|
|
230
|
+
previewSrcList: [{ src: 'photo.jpg' }],
|
|
231
|
+
};
|
|
232
|
+
const { currentPreviewItem, openPreview } = useImage(props, emit);
|
|
233
|
+
|
|
234
|
+
openPreview();
|
|
235
|
+
expect(currentPreviewItem.value.type).toBe('image');
|
|
236
|
+
});
|
|
176
237
|
});
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
8
|
*/
|
|
9
9
|
import { MCOPO, MPropType } from '../../types/props';
|
|
10
|
-
import { ImageProps } from './props';
|
|
10
|
+
import { ImageProps, PreviewSrcListItem } from './props';
|
|
11
11
|
|
|
12
12
|
export const props: MCOPO<ImageProps> = {
|
|
13
13
|
src: { type: String, required: true },
|
|
@@ -20,6 +20,6 @@ export const props: MCOPO<ImageProps> = {
|
|
|
20
20
|
width: { type: [String, Number], default: undefined },
|
|
21
21
|
height: { type: [String, Number], default: undefined },
|
|
22
22
|
lazy: { type: Boolean, default: false },
|
|
23
|
-
previewSrcList: { type: Array as MPropType<
|
|
23
|
+
previewSrcList: { type: Array as MPropType<PreviewSrcListItem[]>, default: () => [] },
|
|
24
24
|
zIndex: { type: Number, default: 2000 },
|
|
25
25
|
};
|
|
@@ -14,5 +14,5 @@ export const ImageCore = {
|
|
|
14
14
|
useImage,
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
export type { ImageProps, ImageEvents } from './props';
|
|
18
|
-
export type { ImageLoadStatus } from './useImage';
|
|
17
|
+
export type { ImageProps, ImageEvents, PreviewItem, PreviewSrcListItem } from './props';
|
|
18
|
+
export type { ImageLoadStatus, NormalizedPreviewItem } from './useImage';
|
|
@@ -6,11 +6,21 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @name m-image
|
|
8
8
|
* @docDescription Image component with lazy loading and preview support.
|
|
9
|
-
*
|
|
9
|
+
* 图片组件,支持懒加载和全屏预览。支持图片与视频混合预览。
|
|
10
10
|
*
|
|
11
11
|
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* @description 预览列表项,支持纯字符串(向后兼容,视为图片)或结构化对象
|
|
16
|
+
*/
|
|
17
|
+
export declare type PreviewItem = {
|
|
18
|
+
src: string,
|
|
19
|
+
type?: 'image' | 'video',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export declare type PreviewSrcListItem = string | PreviewItem;
|
|
23
|
+
|
|
14
24
|
export declare type ImageProps = {
|
|
15
25
|
/**
|
|
16
26
|
* @description 图片地址
|
|
@@ -37,11 +47,12 @@ export declare type ImageProps = {
|
|
|
37
47
|
*/
|
|
38
48
|
lazy?: boolean,
|
|
39
49
|
/**
|
|
40
|
-
* @description
|
|
41
|
-
*
|
|
50
|
+
* @description 开启预览的媒体列表,传入后点击可全屏预览。
|
|
51
|
+
* 支持纯字符串(向后兼容,视为图片)或 { src, type } 对象。
|
|
52
|
+
* @type (string | PreviewItem)[]
|
|
42
53
|
* @default []
|
|
43
54
|
*/
|
|
44
|
-
previewSrcList?:
|
|
55
|
+
previewSrcList?: PreviewSrcListItem[],
|
|
45
56
|
/**
|
|
46
57
|
* @description 图片宽度
|
|
47
58
|
* @type string | number
|
|
@@ -6,27 +6,44 @@
|
|
|
6
6
|
*
|
|
7
7
|
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
8
|
*/
|
|
9
|
-
import { ref, watch, onMounted } from 'vue';
|
|
10
|
-
import { ImageProps } from './props';
|
|
9
|
+
import { ref, computed, watch, onMounted } from 'vue';
|
|
10
|
+
import { ImageProps, PreviewSrcListItem } from './props';
|
|
11
11
|
|
|
12
12
|
/** 图片加载状态 */
|
|
13
13
|
export type ImageLoadStatus = 'loading' | 'loaded' | 'error';
|
|
14
14
|
|
|
15
|
+
/** 正规化后的预览项 */
|
|
16
|
+
export type NormalizedPreviewItem = { src: string; type: 'image' | 'video' };
|
|
17
|
+
|
|
18
|
+
function normalizeItem(item: PreviewSrcListItem): NormalizedPreviewItem {
|
|
19
|
+
if (typeof item === 'string') {
|
|
20
|
+
return { src: item, type: 'image' };
|
|
21
|
+
}
|
|
22
|
+
return { src: item.src, type: item.type ?? 'image' };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getSrc(item: PreviewSrcListItem): string {
|
|
26
|
+
return typeof item === 'string' ? item : item.src;
|
|
27
|
+
}
|
|
28
|
+
|
|
15
29
|
export function useImage(
|
|
16
30
|
props: Required<ImageProps>,
|
|
17
31
|
emit: (event: 'load' | 'error' | 'switch', payload: Event | number) => void,
|
|
18
32
|
) {
|
|
19
33
|
const status = ref<ImageLoadStatus>('loading');
|
|
20
|
-
// 预览弹层是否可见
|
|
21
34
|
const previewVisible = ref(false);
|
|
22
|
-
// 预览当前索引(在 previewSrcList 中的位置)
|
|
23
35
|
const previewIndex = ref(0);
|
|
24
|
-
// 预览缩放比例
|
|
25
36
|
const previewScale = ref(1);
|
|
26
|
-
// 预览旋转角度
|
|
27
37
|
const previewRotate = ref(0);
|
|
28
38
|
|
|
29
|
-
|
|
39
|
+
const currentPreviewItem = computed<NormalizedPreviewItem>(() => {
|
|
40
|
+
const list = props.previewSrcList;
|
|
41
|
+
if (!list || list.length === 0) { return { src: '', type: 'image' }; }
|
|
42
|
+
return normalizeItem(list[previewIndex.value] ?? list[0]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const currentPreviewIsVideo = computed(() => currentPreviewItem.value.type === 'video');
|
|
46
|
+
|
|
30
47
|
const reset = () => {
|
|
31
48
|
status.value = 'loading';
|
|
32
49
|
};
|
|
@@ -41,10 +58,9 @@ export function useImage(
|
|
|
41
58
|
emit('error', e);
|
|
42
59
|
};
|
|
43
60
|
|
|
44
|
-
/** 打开全屏预览,定位到 src 在 previewSrcList 中的位置 */
|
|
45
61
|
const openPreview = () => {
|
|
46
62
|
if (!props.previewSrcList || props.previewSrcList.length === 0) { return; }
|
|
47
|
-
const idx = props.previewSrcList.
|
|
63
|
+
const idx = props.previewSrcList.findIndex(item => getSrc(item) === props.src);
|
|
48
64
|
previewIndex.value = idx >= 0 ? idx : 0;
|
|
49
65
|
previewScale.value = 1;
|
|
50
66
|
previewRotate.value = 0;
|
|
@@ -55,7 +71,6 @@ export function useImage(
|
|
|
55
71
|
previewVisible.value = false;
|
|
56
72
|
};
|
|
57
73
|
|
|
58
|
-
/** 预览切换到上一张 */
|
|
59
74
|
const previewPrev = () => {
|
|
60
75
|
const total = props.previewSrcList.length;
|
|
61
76
|
if (total === 0) { return; }
|
|
@@ -65,7 +80,6 @@ export function useImage(
|
|
|
65
80
|
emit('switch', previewIndex.value);
|
|
66
81
|
};
|
|
67
82
|
|
|
68
|
-
/** 预览切换到下一张 */
|
|
69
83
|
const previewNext = () => {
|
|
70
84
|
const total = props.previewSrcList.length;
|
|
71
85
|
if (total === 0) { return; }
|
|
@@ -75,27 +89,22 @@ export function useImage(
|
|
|
75
89
|
emit('switch', previewIndex.value);
|
|
76
90
|
};
|
|
77
91
|
|
|
78
|
-
/** 预览放大 */
|
|
79
92
|
const zoomIn = () => {
|
|
80
93
|
previewScale.value = Math.min(previewScale.value + 0.25, 5);
|
|
81
94
|
};
|
|
82
95
|
|
|
83
|
-
/** 预览缩小 */
|
|
84
96
|
const zoomOut = () => {
|
|
85
97
|
previewScale.value = Math.max(previewScale.value - 0.25, 0.25);
|
|
86
98
|
};
|
|
87
99
|
|
|
88
|
-
/** 顺时针旋转 90° */
|
|
89
100
|
const rotate = () => {
|
|
90
101
|
previewRotate.value = (previewRotate.value + 90) % 360;
|
|
91
102
|
};
|
|
92
103
|
|
|
93
|
-
// src 变化时重置加载状态
|
|
94
104
|
watch(() => props.src, reset);
|
|
95
105
|
|
|
96
106
|
onMounted(() => {
|
|
97
107
|
if (props.lazy) {
|
|
98
|
-
// 懒加载:交由 img 标签的 loading="lazy" 处理
|
|
99
108
|
status.value = 'loading';
|
|
100
109
|
}
|
|
101
110
|
});
|
|
@@ -106,6 +115,8 @@ export function useImage(
|
|
|
106
115
|
previewIndex,
|
|
107
116
|
previewScale,
|
|
108
117
|
previewRotate,
|
|
118
|
+
currentPreviewItem,
|
|
119
|
+
currentPreviewIsVideo,
|
|
109
120
|
handleLoad,
|
|
110
121
|
handleError,
|
|
111
122
|
openPreview,
|
|
@@ -3,5 +3,5 @@ export declare const ImageCore: {
|
|
|
3
3
|
props: import('../../types/props').MCOPO<import('./props').ImageProps>;
|
|
4
4
|
useImage: typeof useImage;
|
|
5
5
|
};
|
|
6
|
-
export type { ImageProps, ImageEvents } from './props';
|
|
7
|
-
export type { ImageLoadStatus } from './useImage';
|
|
6
|
+
export type { ImageProps, ImageEvents, PreviewItem, PreviewSrcListItem } from './props';
|
|
7
|
+
export type { ImageLoadStatus, NormalizedPreviewItem } from './useImage';
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { ImageProps } from './props';
|
|
2
2
|
/** 图片加载状态 */
|
|
3
3
|
export type ImageLoadStatus = 'loading' | 'loaded' | 'error';
|
|
4
|
+
/** 正规化后的预览项 */
|
|
5
|
+
export type NormalizedPreviewItem = {
|
|
6
|
+
src: string;
|
|
7
|
+
type: 'image' | 'video';
|
|
8
|
+
};
|
|
4
9
|
export declare function useImage(props: Required<ImageProps>, emit: (event: 'load' | 'error' | 'switch', payload: Event | number) => void): {
|
|
5
10
|
status: import('vue').Ref<ImageLoadStatus, ImageLoadStatus>;
|
|
6
11
|
previewVisible: import('vue').Ref<boolean, boolean>;
|
|
7
12
|
previewIndex: import('vue').Ref<number, number>;
|
|
8
13
|
previewScale: import('vue').Ref<number, number>;
|
|
9
14
|
previewRotate: import('vue').Ref<number, number>;
|
|
15
|
+
currentPreviewItem: import('vue').ComputedRef<NormalizedPreviewItem>;
|
|
16
|
+
currentPreviewIsVideo: import('vue').ComputedRef<boolean>;
|
|
10
17
|
handleLoad: (e: Event) => void;
|
|
11
18
|
handleError: (e: Event) => void;
|
|
12
19
|
openPreview: () => void;
|
package/dist/core.js
CHANGED
|
@@ -5389,13 +5389,34 @@ var props$8 = {
|
|
|
5389
5389
|
*
|
|
5390
5390
|
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
5391
5391
|
*/
|
|
5392
|
+
function normalizeItem(item) {
|
|
5393
|
+
if (typeof item === "string") return {
|
|
5394
|
+
src: item,
|
|
5395
|
+
type: "image"
|
|
5396
|
+
};
|
|
5397
|
+
return {
|
|
5398
|
+
src: item.src,
|
|
5399
|
+
type: item.type ?? "image"
|
|
5400
|
+
};
|
|
5401
|
+
}
|
|
5402
|
+
function getSrc(item) {
|
|
5403
|
+
return typeof item === "string" ? item : item.src;
|
|
5404
|
+
}
|
|
5392
5405
|
function useImage(props, emit) {
|
|
5393
5406
|
const status = ref("loading");
|
|
5394
5407
|
const previewVisible = ref(false);
|
|
5395
5408
|
const previewIndex = ref(0);
|
|
5396
5409
|
const previewScale = ref(1);
|
|
5397
5410
|
const previewRotate = ref(0);
|
|
5398
|
-
|
|
5411
|
+
const currentPreviewItem = computed(() => {
|
|
5412
|
+
const list = props.previewSrcList;
|
|
5413
|
+
if (!list || list.length === 0) return {
|
|
5414
|
+
src: "",
|
|
5415
|
+
type: "image"
|
|
5416
|
+
};
|
|
5417
|
+
return normalizeItem(list[previewIndex.value] ?? list[0]);
|
|
5418
|
+
});
|
|
5419
|
+
const currentPreviewIsVideo = computed(() => currentPreviewItem.value.type === "video");
|
|
5399
5420
|
const reset = () => {
|
|
5400
5421
|
status.value = "loading";
|
|
5401
5422
|
};
|
|
@@ -5407,10 +5428,9 @@ function useImage(props, emit) {
|
|
|
5407
5428
|
status.value = "error";
|
|
5408
5429
|
emit("error", e);
|
|
5409
5430
|
};
|
|
5410
|
-
/** 打开全屏预览,定位到 src 在 previewSrcList 中的位置 */
|
|
5411
5431
|
const openPreview = () => {
|
|
5412
5432
|
if (!props.previewSrcList || props.previewSrcList.length === 0) return;
|
|
5413
|
-
const idx = props.previewSrcList.
|
|
5433
|
+
const idx = props.previewSrcList.findIndex((item) => getSrc(item) === props.src);
|
|
5414
5434
|
previewIndex.value = idx >= 0 ? idx : 0;
|
|
5415
5435
|
previewScale.value = 1;
|
|
5416
5436
|
previewRotate.value = 0;
|
|
@@ -5419,7 +5439,6 @@ function useImage(props, emit) {
|
|
|
5419
5439
|
const closePreview = () => {
|
|
5420
5440
|
previewVisible.value = false;
|
|
5421
5441
|
};
|
|
5422
|
-
/** 预览切换到上一张 */
|
|
5423
5442
|
const previewPrev = () => {
|
|
5424
5443
|
const total = props.previewSrcList.length;
|
|
5425
5444
|
if (total === 0) return;
|
|
@@ -5428,7 +5447,6 @@ function useImage(props, emit) {
|
|
|
5428
5447
|
previewRotate.value = 0;
|
|
5429
5448
|
emit("switch", previewIndex.value);
|
|
5430
5449
|
};
|
|
5431
|
-
/** 预览切换到下一张 */
|
|
5432
5450
|
const previewNext = () => {
|
|
5433
5451
|
const total = props.previewSrcList.length;
|
|
5434
5452
|
if (total === 0) return;
|
|
@@ -5437,15 +5455,12 @@ function useImage(props, emit) {
|
|
|
5437
5455
|
previewRotate.value = 0;
|
|
5438
5456
|
emit("switch", previewIndex.value);
|
|
5439
5457
|
};
|
|
5440
|
-
/** 预览放大 */
|
|
5441
5458
|
const zoomIn = () => {
|
|
5442
5459
|
previewScale.value = Math.min(previewScale.value + .25, 5);
|
|
5443
5460
|
};
|
|
5444
|
-
/** 预览缩小 */
|
|
5445
5461
|
const zoomOut = () => {
|
|
5446
5462
|
previewScale.value = Math.max(previewScale.value - .25, .25);
|
|
5447
5463
|
};
|
|
5448
|
-
/** 顺时针旋转 90° */
|
|
5449
5464
|
const rotate = () => {
|
|
5450
5465
|
previewRotate.value = (previewRotate.value + 90) % 360;
|
|
5451
5466
|
};
|
|
@@ -5459,6 +5474,8 @@ function useImage(props, emit) {
|
|
|
5459
5474
|
previewIndex,
|
|
5460
5475
|
previewScale,
|
|
5461
5476
|
previewRotate,
|
|
5477
|
+
currentPreviewItem,
|
|
5478
|
+
currentPreviewIsVideo,
|
|
5462
5479
|
handleLoad,
|
|
5463
5480
|
handleError,
|
|
5464
5481
|
openPreview,
|