Commit e23bd2696da945291a9b652f1af39ad1936f376b

Authored by 无木
1 parent 98749ec6

feat(preview): add more features

为Preview组件添加新的属性及事件
CHANGELOG.zh_CN.md
  1 +### ✨ Features
  2 +
  3 +- **Preview** 添加新的属性及事件
  4 +
1 5 ### 🐛 Bug Fixes
2 6  
3 7 - **ApiTreeSelect** 修复未能正确监听`params`变化的问题
... ...
src/components/Preview/src/Functional.vue
... ... @@ -38,13 +38,33 @@
38 38 type: Number as PropType<number>,
39 39 default: 0,
40 40 },
  41 + scaleStep: {
  42 + type: Number as PropType<number>,
  43 + },
  44 + defaultWidth: {
  45 + type: Number as PropType<number>,
  46 + },
  47 + maskClosable: {
  48 + type: Boolean as PropType<boolean>,
  49 + },
  50 + rememberState: {
  51 + type: Boolean as PropType<boolean>,
  52 + },
41 53 };
42 54  
43 55 const prefixCls = 'img-preview';
44 56 export default defineComponent({
45 57 name: 'ImagePreview',
46 58 props,
47   - setup(props: Props) {
  59 + emits: ['img-load', 'img-error'],
  60 + setup(props: Props, { expose, emit }) {
  61 + interface stateInfo {
  62 + scale: number;
  63 + rotate: number;
  64 + top: number;
  65 + left: number;
  66 + }
  67 + const stateMap = new Map<string, stateInfo>();
48 68 const imgState = reactive<ImgState>({
49 69 currentUrl: '',
50 70 imgScale: 1,
... ... @@ -96,6 +116,14 @@
96 116 };
97 117 }
98 118  
  119 + const getScaleStep = computed(() => {
  120 + if (props.scaleStep > 0 && props.scaleStep < 100) {
  121 + return props.scaleStep / 100;
  122 + } else {
  123 + return imgState.imgScale / 10;
  124 + }
  125 + });
  126 +
99 127 // 监听鼠标滚轮
100 128 function scrollFunc(e: any) {
101 129 e = e || window.event;
... ... @@ -104,11 +132,11 @@
104 132 e.preventDefault();
105 133 if (e.delta > 0) {
106 134 // 滑轮向上滚动
107   - scaleFunc(0.015);
  135 + scaleFunc(getScaleStep.value);
108 136 }
109 137 if (e.delta < 0) {
110 138 // 滑轮向下滚动
111   - scaleFunc(-0.015);
  139 + scaleFunc(-getScaleStep.value);
112 140 }
113 141 }
114 142 // 缩放函数
... ... @@ -134,11 +162,54 @@
134 162 imgState.status = StatueEnum.LOADING;
135 163 const img = new Image();
136 164 img.src = url;
137   - img.onload = () => {
  165 + img.onload = (e: Event) => {
  166 + if (imgState.currentUrl !== url) {
  167 + const ele: HTMLElement[] = e.composedPath();
  168 + if (props.rememberState) {
  169 + // 保存当前图片的缩放信息
  170 + stateMap.set(imgState.currentUrl, {
  171 + scale: imgState.imgScale,
  172 + top: imgState.imgTop,
  173 + left: imgState.imgLeft,
  174 + rotate: imgState.imgRotate,
  175 + });
  176 + // 如果之前已存储缩放信息,就应用
  177 + const stateInfo = stateMap.get(url);
  178 + if (stateInfo) {
  179 + imgState.imgScale = stateInfo.scale;
  180 + imgState.imgTop = stateInfo.top;
  181 + imgState.imgRotate = stateInfo.rotate;
  182 + imgState.imgLeft = stateInfo.left;
  183 + } else {
  184 + initState();
  185 + if (props.defaultWidth) {
  186 + imgState.imgScale = props.defaultWidth / ele[0].naturalWidth;
  187 + }
  188 + }
  189 + } else {
  190 + if (props.defaultWidth) {
  191 + imgState.imgScale = props.defaultWidth / ele[0].naturalWidth;
  192 + }
  193 + }
  194 +
  195 + ele &&
  196 + emit('img-load', {
  197 + index: imgState.currentIndex,
  198 + dom: ele[0] as HTMLImageElement,
  199 + url,
  200 + });
  201 + }
138 202 imgState.currentUrl = url;
139 203 imgState.status = StatueEnum.DONE;
140 204 };
141   - img.onerror = () => {
  205 + img.onerror = (e: Event) => {
  206 + const ele: EventTarget[] = e.composedPath();
  207 + ele &&
  208 + emit('img-error', {
  209 + index: imgState.currentIndex,
  210 + dom: ele[0] as HTMLImageElement,
  211 + url,
  212 + });
142 213 imgState.status = StatueEnum.FAIL;
143 214 };
144 215 }
... ... @@ -146,6 +217,10 @@
146 217 // 关闭
147 218 function handleClose(e: MouseEvent) {
148 219 e && e.stopPropagation();
  220 + close();
  221 + }
  222 +
  223 + function close() {
149 224 imgState.show = false;
150 225 // 移除火狐浏览器下的鼠标滚动事件
151 226 document.body.removeEventListener('DOMMouseScroll', scrollFunc);
... ... @@ -158,6 +233,19 @@
158 233 initState();
159 234 }
160 235  
  236 + expose({
  237 + resume,
  238 + close,
  239 + prev: handleChange.bind(null, 'left'),
  240 + next: handleChange.bind(null, 'right'),
  241 + setScale: (scale: number) => {
  242 + if (scale > 0 && scale <= 10) imgState.imgScale = scale;
  243 + },
  244 + setRotate: (rotate: number) => {
  245 + imgState.imgRotate = rotate;
  246 + },
  247 + } as PreviewActions);
  248 +
161 249 // 上一页下一页
162 250 function handleChange(direction: 'left' | 'right') {
163 251 const { currentIndex } = imgState;
... ... @@ -205,6 +293,7 @@
205 293 transform: `scale(${imgScale}) rotate(${imgRotate}deg)`,
206 294 marginTop: `${imgTop}px`,
207 295 marginLeft: `${imgLeft}px`,
  296 + maxWidth: props.defaultWidth ? 'unset' : '100%',
208 297 };
209 298 });
210 299  
... ... @@ -222,6 +311,16 @@
222 311 }
223 312 });
224 313  
  314 + const handleMaskClick = (e: MouseEvent) => {
  315 + if (
  316 + props.maskClosable &&
  317 + e.target &&
  318 + (e.target as HTMLDivElement).classList.contains(`${prefixCls}-content`)
  319 + ) {
  320 + handleClose(e);
  321 + }
  322 + };
  323 +
225 324 const renderClose = () => {
226 325 return (
227 326 <div class={`${prefixCls}__close`} onClick={handleClose}>
... ... @@ -246,10 +345,16 @@
246 345 const renderController = () => {
247 346 return (
248 347 <div class={`${prefixCls}__controller`}>
249   - <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(-0.15)}>
  348 + <div
  349 + class={`${prefixCls}__controller-item`}
  350 + onClick={() => scaleFunc(-getScaleStep.value)}
  351 + >
250 352 <img src={unScaleSvg} />
251 353 </div>
252   - <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(0.15)}>
  354 + <div
  355 + class={`${prefixCls}__controller-item`}
  356 + onClick={() => scaleFunc(getScaleStep.value)}
  357 + >
253 358 <img src={scaleSvg} />
254 359 </div>
255 360 <div class={`${prefixCls}__controller-item`} onClick={resume}>
... ... @@ -279,7 +384,12 @@
279 384 return () => {
280 385 return (
281 386 imgState.show && (
282   - <div class={prefixCls} ref={wrapElRef} onMouseup={handleMouseUp}>
  387 + <div
  388 + class={prefixCls}
  389 + ref={wrapElRef}
  390 + onMouseup={handleMouseUp}
  391 + onClick={handleMaskClick}
  392 + >
283 393 <div class={`${prefixCls}-content`}>
284 394 {/*<Spin*/}
285 395 {/* indicator={<LoadingOutlined style="font-size: 24px" spin />}*/}
... ...
src/components/Preview/src/functional.ts
... ... @@ -6,15 +6,12 @@ import { createVNode, render } from &#39;vue&#39;;
6 6 let instance: ReturnType<typeof createVNode> | null = null;
7 7 export function createImgPreview(options: Options) {
8 8 if (!isClient) return;
9   - const { imageList, show = true, index = 0 } = options;
10   -
11 9 const propsData: Partial<Props> = {};
12 10 const container = document.createElement('div');
13   - propsData.imageList = imageList;
14   - propsData.show = show;
15   - propsData.index = index;
  11 + Object.assign(propsData, { show: true, index: 0, scaleStep: 100 }, options);
16 12  
17 13 instance = createVNode(ImgPreview, propsData);
18 14 render(instance, container);
19 15 document.body.appendChild(container);
  16 + return instance.component?.exposed;
20 17 }
... ...
src/components/Preview/src/typing.ts
... ... @@ -2,6 +2,12 @@ export interface Options {
2 2 show?: boolean;
3 3 imageList: string[];
4 4 index?: number;
  5 + scaleStep?: number;
  6 + defaultWidth?: number;
  7 + maskClosable?: boolean;
  8 + rememberState?: boolean;
  9 + onImgLoad?: (img: HTMLImageElement) => void;
  10 + onImgError?: (img: HTMLImageElement) => void;
5 11 }
6 12  
7 13 export interface Props {
... ... @@ -9,6 +15,19 @@ export interface Props {
9 15 instance: Props;
10 16 imageList: string[];
11 17 index: number;
  18 + scaleStep: number;
  19 + defaultWidth: number;
  20 + maskClosable: boolean;
  21 + rememberState: boolean;
  22 +}
  23 +
  24 +export interface PreviewActions {
  25 + resume: () => void;
  26 + close: () => void;
  27 + prev: () => void;
  28 + next: () => void;
  29 + setScale: (scale: number) => void;
  30 + setRotate: (rotate: number) => void;
12 31 }
13 32  
14 33 export interface ImageProps {
... ...
src/views/demo/feat/img-preview/index.vue
... ... @@ -8,6 +8,7 @@
8 8 import { defineComponent } from 'vue';
9 9 import { createImgPreview, ImagePreview } from '/@/components/Preview/index';
10 10 import { PageWrapper } from '/@/components/Page';
  11 + // import { PreviewActions } from '/@/components/Preview/src/typing';
11 12  
12 13 const imgList: string[] = [
13 14 'https://picsum.photos/id/66/346/216',
... ... @@ -18,7 +19,11 @@
18 19 components: { PageWrapper, ImagePreview },
19 20 setup() {
20 21 function openImg() {
21   - createImgPreview({ imageList: imgList });
  22 + const onImgLoad = ({ index, url, dom }) => {
  23 + console.log(`第${index + 1}张图片已加载,URL为:${url}`, dom);
  24 + };
  25 + // 可以使用createImgPreview返回的 PreviewActions 来控制预览逻辑,实现类似幻灯片、自动旋转之类的骚操作
  26 + createImgPreview({ imageList: imgList, defaultWidth: 700, rememberState: true, onImgLoad });
22 27 }
23 28 return { imgList, openImg };
24 29 },
... ...