perf: 优化移动端图片预览

This commit is contained in:
kuaifan 2022-05-31 15:15:54 +08:00
parent 022499300e
commit 1f8198b36b
10 changed files with 190 additions and 34 deletions

View File

@ -758,14 +758,19 @@ class FileController extends AbstractController
$file->saveBeforePids(); $file->saveBeforePids();
// //
$data = Base::uploadMove($data, "uploads/file/" . $file->type . "/" . date("Ym") . "/" . $file->id . "/"); $data = Base::uploadMove($data, "uploads/file/" . $file->type . "/" . date("Ym") . "/" . $file->id . "/");
$content = [
'from' => '',
'type' => $type,
'ext' => $data['ext'],
'url' => $data['path'],
];
if (isset($data['width'])) {
$content['width'] = $data['width'];
$content['height'] = $data['height'];
}
$content = FileContent::createInstance([ $content = FileContent::createInstance([
'fid' => $file->id, 'fid' => $file->id,
'content' => [ 'content' => $content,
'from' => '',
'type' => $type,
'ext' => $data['ext'],
'url' => $data['path']
],
'text' => '', 'text' => '',
'size' => $file->size, 'size' => $file->size,
'userid' => $user->userid, 'userid' => $user->userid,

View File

@ -341,6 +341,8 @@ class File extends AbstractModel
$content = Base::json2array(FileContent::whereFid($item['id'])->orderByDesc('id')->value('content')); $content = Base::json2array(FileContent::whereFid($item['id'])->orderByDesc('id')->value('content'));
if ($content) { if ($content) {
$item['image_url'] = Base::fillUrl($content['url']); $item['image_url'] = Base::fillUrl($content['url']);
$item['image_width'] = intval($content['width']);
$item['image_height'] = intval($content['height']);
} }
} }
return $item; return $item;

View File

@ -35,6 +35,7 @@
"moment": "^2.29.1", "moment": "^2.29.1",
"node-sass": "^6.0.1", "node-sass": "^6.0.1",
"notification-koro1": "^1.1.1", "notification-koro1": "^1.1.1",
"photoswipe": "^5.2.7",
"postcss": "^8.4.5", "postcss": "^8.4.5",
"quill": "^1.3.7", "quill": "^1.3.7",
"quill-mention-hi": "^3.1.0-1", "quill-mention-hi": "^3.1.0-1",

View File

@ -0,0 +1,82 @@
<template>
</template>
<style lang="scss">
body {
.preview-image-swipe {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
> img {
max-width: 100%;
max-height: 100%;
}
}
}
</style>
<script>
import PhotoSwipeLightbox from 'photoswipe/lightbox';
import 'photoswipe/style.css';
export default {
props: {
urlList: {
type: Array,
default: () => []
},
initialIndex: {
type: Number,
default: 0
},
},
data() {
return {
lightbox: null,
};
},
beforeDestroy() {
this.lightbox?.destroy();
},
watch: {
urlList: {
handler(array) {
this.lightbox?.destroy();
const dataSource = array.map(item => {
if ($A.isJson(item)) {
if (item.src) {
item.src = $A.rightDelete(item.src, "_thumb.jpg");
}
return item
}
return {
html: `<div class="preview-image-swipe"><img src="${$A.rightDelete(item, "_thumb.jpg")}"/></div>`,
}
})
this.lightbox = new PhotoSwipeLightbox({
dataSource,
escKey: false,
showHideAnimationType: 'none',
pswpModule: () => import('photoswipe'),
});
this.lightbox.on('close', () => {
this.$emit("on-close")
});
this.lightbox.on('destroy', () => {
this.$emit("on-destroy")
});
this.lightbox.init();
this.lightbox.loadAndOpen(this.initialIndex);
},
immediate: true
},
initialIndex(index) {
this.lightbox?.loadAndOpen(index);
}
},
};
</script>

View File

@ -72,6 +72,10 @@
z-index: 2; z-index: 2;
transform: translateY(-50%); transform: translateY(-50%);
@media (max-width: 640px) {
display: none;
}
&.is-disabled { &.is-disabled {
cursor: no-drop; cursor: no-drop;
@ -208,7 +212,11 @@ export default {
return this.index === this.urlList.length - 1; return this.index === this.urlList.length - 1;
}, },
currentImg() { currentImg() {
return $A.rightDelete(this.urlList[this.index], "_thumb.jpg"); let item = this.urlList[this.index];
if ($A.isJson(item)) {
item = item.src;
}
return $A.rightDelete(item, "_thumb.jpg");
}, },
imgStyle() { imgStyle() {
const {scale, deg, offsetX, offsetY, enableTransition} = this.transform; const {scale, deg, offsetX, offsetY, enableTransition} = this.transform;

View File

@ -5,16 +5,19 @@
:mask-closable="false" :mask-closable="false"
:footer-hide="true" :footer-hide="true"
:transition-names="['', '']" :transition-names="['', '']"
fullscreen :class-name="mode === 'desktop' ? 'common-preview-image-view' : 'common-preview-image-swipe'"
class-name="common-preview-image"> fullscreen>
<PreviewImageView v-if="list.length > 0" :initial-index="index" :url-list="list" infinite/> <template v-if="list.length > 0">
<PreviewImageView v-if="mode === 'desktop'" :initial-index="index" :url-list="list" infinite/>
<PreviewImageSwipe v-if="mode === 'mobile'" :initial-index="index" :url-list="list" @on-destroy="show=false"/>
</template>
</Modal> </Modal>
</template> </template>
<style lang="scss"> <style lang="scss">
body { body {
.ivu-modal-wrap { .ivu-modal-wrap {
&.common-preview-image { &.common-preview-image-view {
.ivu-modal { .ivu-modal {
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -34,6 +37,11 @@ body {
top: 40px; top: 40px;
width: 40px; width: 40px;
@media (max-width: 640px) {
right: 24px;
top: 24px;
}
.ivu-icon-ios-close { .ivu-icon-ios-close {
top: 0; top: 0;
right: 0; right: 0;
@ -48,16 +56,20 @@ body {
} }
} }
} }
&.common-preview-image-swipe {
display: none;
}
} }
} }
</style> </style>
<script> <script>
import PreviewImageView from "./view"; const PreviewImageView = () => import('./components/view');
const PreviewImageSwipe = () => import('./components/swipe');
export default { export default {
name: 'PreviewImage', name: 'PreviewImage',
components: {PreviewImageView}, components: {PreviewImageSwipe, PreviewImageView},
props: { props: {
value: { value: {
type: Boolean, type: Boolean,
@ -72,6 +84,12 @@ export default {
default: () => { default: () => {
return []; return [];
} }
},
mode: {
type: String,
default: () => {
return $A.isDesktop ? 'desktop' : 'mobile';
}
} }
}, },
data() { data() {

View File

@ -375,7 +375,7 @@ export default {
// //
} else { } else {
this.timerScroll = setInterval(() => { this.timerScroll = setInterval(() => {
if (this.quill.hasFocus()) { if (this.quill?.hasFocus()) {
$A.scrollToView(this.$refs.editor, true) $A.scrollToView(this.$refs.editor, true)
} else { } else {
clearInterval(this.timerScroll); clearInterval(this.timerScroll);

View File

@ -152,7 +152,7 @@ export default {
contentClass() { contentClass() {
const {type, msg} = this.msgData; const {type, msg} = this.msgData;
let classArray = []; const classArray = [];
if (type === 'text') { if (type === 'text') {
if (/^<img\s+class="emoticon"[^>]*?>$/.test(msg.text)) { if (/^<img\s+class="emoticon"[^>]*?>$/.test(msg.text)) {
classArray.push('an-emoticon') classArray.push('an-emoticon')
@ -243,15 +243,19 @@ export default {
// //
const array = text.match(/<img\s+[^>]*?>/g); const array = text.match(/<img\s+[^>]*?>/g);
if (array) { if (array) {
const widthReg = new RegExp("width=\"(\\d+)\"") const widthReg = new RegExp("width=\"(\\d+)\""),
const heightReg = new RegExp("height=\"(\\d+)\"") heightReg = new RegExp("height=\"(\\d+)\"")
array.some(res => { array.some(res => {
if (widthReg.test(res) && heightReg.test(res)) { const widthMatch = res.match(widthReg),
let width = parseInt(res.match(widthReg)[1]), heightMatch = res.match(heightReg);
height = parseInt(res.match(heightReg)[1]), if (widthMatch && heightMatch) {
const width = parseInt(widthMatch[1]),
height = parseInt(heightMatch[1]),
maxSize = res.indexOf("emoticon") > -1 ? 150 : 220; maxSize = res.indexOf("emoticon") > -1 ? 150 : 220;
let scale = $A.scaleToScale(width, height, maxSize, maxSize); const scale = $A.scaleToScale(width, height, maxSize, maxSize);
let value = res.replace(widthReg, `width=${scale.width}`).replace(heightReg, `height=${scale.height}`) const value = res
.replace(widthReg, `original-width="${width}" width="${scale.width}"`)
.replace(heightReg, `original-height="${height}" height="${scale.height}"`)
text = text.replace(res, value) text = text.replace(res, value)
} }
}) })
@ -261,14 +265,14 @@ export default {
recordStyle(info) { recordStyle(info) {
const {duration} = info; const {duration} = info;
let width = 50 + Math.min(180, Math.floor(duration / 150)); const width = 50 + Math.min(180, Math.floor(duration / 150));
return { return {
width: width + 'px', width: width + 'px',
}; };
}, },
recordDuration(duration) { recordDuration(duration) {
let minute = Math.floor(duration / 60000), const minute = Math.floor(duration / 60000),
seconds = Math.floor(duration / 1000) % 60; seconds = Math.floor(duration / 1000) % 60;
if (minute > 0) { if (minute > 0) {
return `${minute}:${seconds}` return `${minute}:${seconds}`
@ -340,7 +344,7 @@ export default {
this.viewPicture(target.currentSrc); this.viewPicture(target.currentSrc);
} else { } else {
this.$store.state.previewImageIndex = 0; this.$store.state.previewImageIndex = 0;
this.$store.state.previewImageList = [target.currentSrc]; this.$store.state.previewImageList = this.getTextImageInfos(target.outerHTML);
} }
break; break;
@ -400,20 +404,20 @@ export default {
return a.id - b.id; return a.id - b.id;
}); });
// //
let list = []; const list = [];
data.some(({type, msg}) => { data.some(({type, msg}) => {
if (type === 'file') { if (type === 'file') {
list.push(msg.path) list.push({
} else if (type === 'text') { src: msg.path,
const baseUrl = $A.apiUrl('../'); width: msg.width,
const array = msg.text.match(/<img\s+class="browse"[^>]*?src="(.*?)"[^>]*?>/g); height: msg.height,
array && array.some(res => {
list.push(res.match(/<img\s+class="browse"[^>]*?src="(.*?)"[^>]*?>/)[1].replace(/\{\{RemoteURL\}\}/g, baseUrl))
}) })
} else if (type === 'text') {
list.push(...this.getTextImageInfos(msg.text))
} }
}) })
// //
let index = list.findIndex(item => item === currentUrl); const index = list.findIndex(({src}) => src === currentUrl);
if (index > -1) { if (index > -1) {
this.$store.state.previewImageIndex = index; this.$store.state.previewImageIndex = index;
this.$store.state.previewImageList = list; this.$store.state.previewImageList = list;
@ -423,6 +427,30 @@ export default {
} }
}, },
getTextImageInfos(text) {
const baseUrl = $A.apiUrl('../');
const array = text.match(new RegExp(`<img[^>]*?>`, "g"));
const list = [];
if (array) {
const srcReg = new RegExp("src=\"(.*?)\""),
widthReg = new RegExp("(original-)?width=\"(\\d+)\""),
heightReg = new RegExp("(original-)?height=\"(\\d+)\"")
array.some(res => {
const srcMatch = res.match(srcReg),
widthMatch = res.match(widthReg),
heightMatch = res.match(heightReg);
if (srcMatch && widthMatch && heightMatch) {
list.push({
src: srcMatch[1].replace(/\{\{RemoteURL\}\}/g, baseUrl),
width: widthMatch[2],
height: heightMatch[2],
})
}
})
}
return list;
},
downFile() { downFile() {
$A.modalConfirm({ $A.modalConfirm({
title: '下载文件', title: '下载文件',

View File

@ -951,7 +951,16 @@ export default {
const list = this.fileList.filter(({image_url}) => !!image_url) const list = this.fileList.filter(({image_url}) => !!image_url)
if (list.length > 0) { if (list.length > 0) {
this.imageIndex = list.findIndex(({id}) => item.id === id) this.imageIndex = list.findIndex(({id}) => item.id === id)
this.imageList = list.map(item => item.image_url) this.imageList = list.map(item => {
if (item.image_width) {
return {
src: item.image_url,
width: item.image_width,
height: item.image_height,
}
}
return item.image_url;
})
this.imageShow = true this.imageShow = true
return; return;
} }

View File

@ -676,5 +676,8 @@
} }
} }
} }
.file-upload-list {
bottom: 86px
}
} }
} }