mirror of
https://github.com/kuaifan/dootask.git
synced 2026-01-02 17:28:17 +00:00
perf: 优化图片预览,优化与弹窗esc按键冲突的问题
This commit is contained in:
parent
6115828dfe
commit
868b8e7206
@ -7,16 +7,18 @@
|
||||
</transition>
|
||||
<Spinner/>
|
||||
<RightBottom/>
|
||||
<PreviewImage/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Spinner from "./components/Spinner";
|
||||
import RightBottom from "./components/RightBottom";
|
||||
import PreviewImage from "./components/PreviewImage";
|
||||
import {mapState} from "vuex";
|
||||
|
||||
export default {
|
||||
components: {RightBottom, Spinner},
|
||||
components: {PreviewImage, RightBottom, Spinner},
|
||||
|
||||
data() {
|
||||
return {
|
||||
|
||||
2
resources/assets/js/app.js
vendored
2
resources/assets/js/app.js
vendored
@ -45,7 +45,6 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownItem,
|
||||
} from 'element-ui';
|
||||
import ImageViewer from 'element-ui/packages/image/src/image-viewer';
|
||||
|
||||
Vue.component('EAvatar', Avatar);
|
||||
Vue.component('ETooltip', Tooltip);
|
||||
@ -53,7 +52,6 @@ Vue.component('EPopover', Popover);
|
||||
Vue.component('EDropdown', Dropdown);
|
||||
Vue.component('EDropdownMenu', DropdownMenu);
|
||||
Vue.component('EDropdownItem', DropdownItem);
|
||||
Vue.component('EImageViewer', ImageViewer);
|
||||
|
||||
const originalPush = VueRouter.prototype.push
|
||||
VueRouter.prototype.push = function push(location) {
|
||||
|
||||
85
resources/assets/js/components/PreviewImage/index.vue
Normal file
85
resources/assets/js/components/PreviewImage/index.vue
Normal file
@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<Modal
|
||||
:value="previewImageList.length > 0"
|
||||
:mask-closable="false"
|
||||
:footer-hide="true"
|
||||
:transition-names="['fade', 'fade']"
|
||||
fullscreen
|
||||
@on-visible-change="visibleChange"
|
||||
class-name="common-preview-image">
|
||||
<PreviewImageView v-if="previewImageList.length > 0" :initial-index="previewImageIndex" :url-list="previewImageList" infinite/>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
body {
|
||||
.ivu-modal-wrap {
|
||||
&.common-preview-image {
|
||||
.ivu-modal {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
.ivu-modal-content {
|
||||
background: transparent;
|
||||
.ivu-modal-close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background-color: #606266;
|
||||
font-size: 24px;
|
||||
height: 40px;
|
||||
right: 40px;
|
||||
top: 40px;
|
||||
width: 40px;
|
||||
.ivu-icon-ios-close {
|
||||
top: 0;
|
||||
right: 0;
|
||||
font-size: 40px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
.ivu-modal-body {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import PreviewImageView from "./view";
|
||||
import {mapState} from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'PreviewImage',
|
||||
components: {PreviewImageView},
|
||||
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'previewImageIndex',
|
||||
'previewImageList',
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
visibleChange(val) {
|
||||
if (!val) {
|
||||
this.close()
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.$store.state.previewImageIndex = 0;
|
||||
this.$store.state.previewImageList = [];
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
363
resources/assets/js/components/PreviewImage/view.vue
Normal file
363
resources/assets/js/components/PreviewImage/view.vue
Normal file
@ -0,0 +1,363 @@
|
||||
<template>
|
||||
<div ref="view" class="common-preview-view">
|
||||
<template v-if="!isSingle">
|
||||
<div class="preview-view-prev" :class="{ 'is-disabled': !infinite && isFirst }">
|
||||
<i class="taskfont"></i>
|
||||
</div>
|
||||
<div class="preview-view-next" :class="{ 'is-disabled': !infinite && isLast }">
|
||||
<i class="taskfont"></i>
|
||||
</div>
|
||||
</template>
|
||||
<div class="preview-view-actions">
|
||||
<div class="actions-inner">
|
||||
<i class="taskfont" @click="handleActions('zoomOut')"></i>
|
||||
<i class="taskfont" @click="handleActions('zoomIn')"></i>
|
||||
<i class="actions-divider"></i>
|
||||
<i class="taskfont" @click="toggleMode" v-html="mode.icon"></i>
|
||||
<i class="actions-divider"></i>
|
||||
<i class="taskfont" @click="handleActions('anticlocelise')"></i>
|
||||
<i class="taskfont" @click="handleActions('clocelise')"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview-view-canvas">
|
||||
<img
|
||||
v-for="(url, i) in urlList"
|
||||
v-if="i === index"
|
||||
ref="img"
|
||||
class="preview-view-img"
|
||||
:key="url"
|
||||
:src="currentImg"
|
||||
:style="imgStyle"
|
||||
@load="handleImgLoad"
|
||||
@error="handleImgError"
|
||||
@mousedown="handleMouseDown">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.common-preview-view {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
|
||||
.preview-view-prev,
|
||||
.preview-view-next {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
opacity: .8;
|
||||
cursor: pointer;
|
||||
background-color: #606266;
|
||||
border-color: #fff;
|
||||
height: 44px;
|
||||
width: 44px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
z-index: 2;
|
||||
transform: translateY(-50%);
|
||||
|
||||
&.is-disabled {
|
||||
cursor: no-drop;
|
||||
|
||||
> i {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
> i {
|
||||
color: #fff;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-view-prev {
|
||||
left: 40px;
|
||||
}
|
||||
|
||||
.preview-view-next {
|
||||
right: 40px;
|
||||
}
|
||||
|
||||
.preview-view-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: .8;
|
||||
background-color: #606266;
|
||||
border-color: #fff;
|
||||
border-radius: 22px;
|
||||
bottom: 30px;
|
||||
height: 44px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
z-index: 2;
|
||||
padding: 0 23px;
|
||||
transform: translateX(-50%);
|
||||
width: 282px;
|
||||
|
||||
.actions-inner {
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
cursor: default;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: space-around;
|
||||
text-align: justify;
|
||||
width: 100%;
|
||||
|
||||
> i {
|
||||
cursor: pointer;
|
||||
font-size: 23px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.preview-view-canvas {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import {isFirefox, rafThrottle} from "element-ui/src/utils/util";
|
||||
import {off, on} from "element-ui/src/utils/dom";
|
||||
|
||||
const Mode = {
|
||||
CONTAIN: {
|
||||
name: 'contain',
|
||||
icon: ''
|
||||
},
|
||||
ORIGINAL: {
|
||||
name: 'original',
|
||||
icon: ''
|
||||
}
|
||||
};
|
||||
const mousewheelEventName = isFirefox() ? 'DOMMouseScroll' : 'mousewheel';
|
||||
|
||||
|
||||
export default {
|
||||
props: {
|
||||
urlList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
initialIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
infinite: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
onSwitch: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
index: this.initialIndex,
|
||||
loading: false,
|
||||
mode: Mode.CONTAIN,
|
||||
transform: {
|
||||
scale: 1,
|
||||
deg: 0,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
enableTransition: false
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.deviceSupportInstall();
|
||||
this.$refs.view.focus();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.deviceSupportUninstall();
|
||||
},
|
||||
computed: {
|
||||
isSingle() {
|
||||
return this.urlList.length <= 1;
|
||||
},
|
||||
isFirst() {
|
||||
return this.index === 0;
|
||||
},
|
||||
isLast() {
|
||||
return this.index === this.urlList.length - 1;
|
||||
},
|
||||
currentImg() {
|
||||
return this.urlList[this.index];
|
||||
},
|
||||
imgStyle() {
|
||||
const {scale, deg, offsetX, offsetY, enableTransition} = this.transform;
|
||||
const style = {
|
||||
transform: `scale(${scale}) rotate(${deg}deg)`,
|
||||
transition: enableTransition ? 'transform .3s' : '',
|
||||
'margin-left': `${offsetX}px`,
|
||||
'margin-top': `${offsetY}px`
|
||||
};
|
||||
if (this.mode === Mode.CONTAIN) {
|
||||
style.maxWidth = style.maxHeight = '100%';
|
||||
}
|
||||
return style;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
index: {
|
||||
handler: function (val) {
|
||||
this.reset();
|
||||
this.onSwitch(val);
|
||||
}
|
||||
},
|
||||
currentImg(val) {
|
||||
this.$nextTick(_ => {
|
||||
const $img = this.$refs.img[0];
|
||||
if (!$img.complete) {
|
||||
this.loading = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
deviceSupportInstall() {
|
||||
this._keyDownHandler = e => {
|
||||
e.stopPropagation();
|
||||
const keyCode = e.keyCode;
|
||||
switch (keyCode) {
|
||||
// SPACE
|
||||
case 32:
|
||||
this.toggleMode();
|
||||
break;
|
||||
// LEFT_ARROW
|
||||
case 37:
|
||||
this.prev();
|
||||
break;
|
||||
// UP_ARROW
|
||||
case 38:
|
||||
this.handleActions('zoomIn');
|
||||
break;
|
||||
// RIGHT_ARROW
|
||||
case 39:
|
||||
this.next();
|
||||
break;
|
||||
// DOWN_ARROW
|
||||
case 40:
|
||||
this.handleActions('zoomOut');
|
||||
break;
|
||||
}
|
||||
};
|
||||
this._mouseWheelHandler = rafThrottle(e => {
|
||||
const delta = e.wheelDelta ? e.wheelDelta : -e.detail;
|
||||
if (delta > 0) {
|
||||
this.handleActions('zoomIn', {
|
||||
zoomRate: 0.015,
|
||||
enableTransition: false
|
||||
});
|
||||
} else {
|
||||
this.handleActions('zoomOut', {
|
||||
zoomRate: 0.015,
|
||||
enableTransition: false
|
||||
});
|
||||
}
|
||||
});
|
||||
on(document, 'keydown', this._keyDownHandler);
|
||||
on(document, mousewheelEventName, this._mouseWheelHandler);
|
||||
},
|
||||
deviceSupportUninstall() {
|
||||
off(document, 'keydown', this._keyDownHandler);
|
||||
off(document, mousewheelEventName, this._mouseWheelHandler);
|
||||
this._keyDownHandler = null;
|
||||
this._mouseWheelHandler = null;
|
||||
},
|
||||
handleImgLoad(e) {
|
||||
this.loading = false;
|
||||
},
|
||||
handleImgError(e) {
|
||||
this.loading = false;
|
||||
e.target.alt = '加载失败';
|
||||
},
|
||||
handleMouseDown(e) {
|
||||
if (this.loading || e.button !== 0) return;
|
||||
|
||||
const {offsetX, offsetY} = this.transform;
|
||||
const startX = e.pageX;
|
||||
const startY = e.pageY;
|
||||
this._dragHandler = rafThrottle(ev => {
|
||||
this.transform.offsetX = offsetX + ev.pageX - startX;
|
||||
this.transform.offsetY = offsetY + ev.pageY - startY;
|
||||
});
|
||||
on(document, 'mousemove', this._dragHandler);
|
||||
on(document, 'mouseup', ev => {
|
||||
off(document, 'mousemove', this._dragHandler);
|
||||
});
|
||||
|
||||
e.preventDefault();
|
||||
},
|
||||
reset() {
|
||||
this.transform = {
|
||||
scale: 1,
|
||||
deg: 0,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
enableTransition: false
|
||||
};
|
||||
},
|
||||
toggleMode() {
|
||||
if (this.loading) return;
|
||||
|
||||
const modeNames = Object.keys(Mode);
|
||||
const modeValues = Object.values(Mode);
|
||||
const index = modeValues.indexOf(this.mode);
|
||||
const nextIndex = (index + 1) % modeNames.length;
|
||||
this.mode = Mode[modeNames[nextIndex]];
|
||||
this.reset();
|
||||
},
|
||||
prev() {
|
||||
if (this.isFirst && !this.infinite) return;
|
||||
const len = this.urlList.length;
|
||||
this.index = (this.index - 1 + len) % len;
|
||||
},
|
||||
next() {
|
||||
if (this.isLast && !this.infinite) return;
|
||||
const len = this.urlList.length;
|
||||
this.index = (this.index + 1) % len;
|
||||
},
|
||||
handleActions(action, options = {}) {
|
||||
if (this.loading) return;
|
||||
const {zoomRate, rotateDeg, enableTransition} = {
|
||||
zoomRate: 0.2,
|
||||
rotateDeg: 90,
|
||||
enableTransition: true,
|
||||
...options
|
||||
};
|
||||
const {transform} = this;
|
||||
switch (action) {
|
||||
case 'zoomOut':
|
||||
if (transform.scale > 0.2) {
|
||||
transform.scale = parseFloat((transform.scale - zoomRate).toFixed(3));
|
||||
}
|
||||
break;
|
||||
case 'zoomIn':
|
||||
transform.scale = parseFloat((transform.scale + zoomRate).toFixed(3));
|
||||
break;
|
||||
case 'clocelise':
|
||||
transform.deg += rotateDeg;
|
||||
break;
|
||||
case 'anticlocelise':
|
||||
transform.deg -= rotateDeg;
|
||||
break;
|
||||
}
|
||||
transform.enableTransition = enableTransition;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -342,13 +342,6 @@
|
||||
<Badge :count="unreadTotal"/>
|
||||
</div>
|
||||
</DragBallComponent>
|
||||
|
||||
<!--预览图片-->
|
||||
<EImageViewer
|
||||
v-if="previewImageList.length > 0"
|
||||
:urlList="previewImageList"
|
||||
:initialIndex="previewImageIndex"
|
||||
:on-close="closePreviewImage"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -491,9 +484,6 @@ export default {
|
||||
|
||||
'clientNewVersion',
|
||||
'cacheTaskBrowse',
|
||||
|
||||
'previewImageList',
|
||||
'previewImageIndex',
|
||||
]),
|
||||
|
||||
...mapGetters(['taskData', 'dashboardTask']),
|
||||
@ -980,11 +970,6 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
closePreviewImage() {
|
||||
this.$store.state.previewImageIndex = 0;
|
||||
this.$store.state.previewImageList = [];
|
||||
},
|
||||
|
||||
notificationInit() {
|
||||
this.notificationClass = new notificationKoro(this.$L("打开通知成功"));
|
||||
if (this.notificationClass.support) {
|
||||
|
||||
1
resources/assets/sass/element.scss
vendored
1
resources/assets/sass/element.scss
vendored
@ -14,7 +14,6 @@ $--dropdown-menuItem-hover-color: #606266;
|
||||
@import "~element-ui/packages/theme-chalk/src/dropdown-menu";
|
||||
@import "~element-ui/packages/theme-chalk/src/dropdown-item";
|
||||
@import "~element-ui/packages/theme-chalk/src/notification";
|
||||
@import "~element-ui/packages/theme-chalk/src/image";
|
||||
|
||||
|
||||
.el-dropdown-menu__item {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user