mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-17 06:32:51 +00:00
perf: 优化缩略图
This commit is contained in:
parent
50a7950ccd
commit
d799c06017
@ -3,9 +3,11 @@
|
|||||||
namespace App\Exceptions;
|
namespace App\Exceptions;
|
||||||
|
|
||||||
use App\Module\Base;
|
use App\Module\Base;
|
||||||
|
use App\Module\Image;
|
||||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
class Handler extends ExceptionHandler
|
class Handler extends ExceptionHandler
|
||||||
@ -51,6 +53,11 @@ class Handler extends ExceptionHandler
|
|||||||
*/
|
*/
|
||||||
public function render($request, Throwable $e)
|
public function render($request, Throwable $e)
|
||||||
{
|
{
|
||||||
|
if ($e instanceof NotFoundHttpException) {
|
||||||
|
if ($result = $this->ImagePathHandler($request)) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
if ($e instanceof ApiException) {
|
if ($e instanceof ApiException) {
|
||||||
return response()->json(Base::retError($e->getMessage(), $e->getData(), $e->getCode()));
|
return response()->json(Base::retError($e->getMessage(), $e->getData(), $e->getCode()));
|
||||||
} elseif ($e instanceof ModelNotFoundException) {
|
} elseif ($e instanceof ModelNotFoundException) {
|
||||||
@ -78,4 +85,133 @@ class Handler extends ExceptionHandler
|
|||||||
parent::report($e);
|
parent::report($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片路径处理
|
||||||
|
* @param $request
|
||||||
|
* @return \Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse|null
|
||||||
|
*/
|
||||||
|
private function ImagePathHandler($request)
|
||||||
|
{
|
||||||
|
$path = $request->path();
|
||||||
|
|
||||||
|
// 处理图片
|
||||||
|
$pattern = '/^(uploads\/.*\.(png|jpg|jpeg))\/crop\/([^\/]+)$/';
|
||||||
|
if (preg_match($pattern, $path, $matches)) {
|
||||||
|
// 获取参数
|
||||||
|
$file = $matches[1];
|
||||||
|
$ext = $matches[2];
|
||||||
|
$rules = preg_replace('/\s+/', '', $matches[3]);
|
||||||
|
$rules = str_replace(['=', '&'], [':', ','], $rules);
|
||||||
|
$rules = explode(',', $rules);
|
||||||
|
if (empty($rules)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取年月
|
||||||
|
$Ym = date("Ym");
|
||||||
|
if (preg_match('/\/(\d{6})\//', $file, $ms)) {
|
||||||
|
$Ym = $ms[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件存在直接返回
|
||||||
|
$dirName = str_replace(['/', '.'], '_', $file);
|
||||||
|
$fileName = str_replace([':', ','], ['-', '_'], implode(',', $rules)) . '.' . $ext;
|
||||||
|
$savePath = public_path('uploads/tmp/crop/' . $Ym . '/' . $dirName . '/' . $fileName);
|
||||||
|
if (file_exists($savePath)) {
|
||||||
|
// 设置头部声明图片缓存
|
||||||
|
return response()->file($savePath, [
|
||||||
|
'Pragma' => 'public',
|
||||||
|
'Cache-Control' => 'max-age=1814400',
|
||||||
|
'Expires' => gmdate('D, d M Y H:i:s', time() + 1814400) . ' GMT',
|
||||||
|
'Last-Modified' => gmdate('D, d M Y H:i:s', filemtime($savePath)) . ' GMT',
|
||||||
|
'ETag' => md5_file($savePath)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件不存在处理
|
||||||
|
$sourcePath = public_path($file);
|
||||||
|
if (!file_exists($sourcePath)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断删除多余文件
|
||||||
|
$saveDir = dirname($savePath);
|
||||||
|
if (is_dir($saveDir)) {
|
||||||
|
$items = glob($saveDir . '/*');
|
||||||
|
if (count($items) > 5) {
|
||||||
|
usort($items, function ($a, $b) {
|
||||||
|
return filemtime($b) - filemtime($a);
|
||||||
|
});
|
||||||
|
$itemsToDelete = array_slice($items, 5);
|
||||||
|
foreach ($itemsToDelete as $item) {
|
||||||
|
if (is_file($item)) {
|
||||||
|
unlink($item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Base::makeDir($saveDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理图片
|
||||||
|
try {
|
||||||
|
$handle = 0;
|
||||||
|
$image = new Image($sourcePath);
|
||||||
|
foreach ($rules as $rule) {
|
||||||
|
if (!str_contains($rule, ':')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
[$type, $value] = explode(':', $rule);
|
||||||
|
if (!in_array($type, ['ratio', 'size', 'percentage', 'cover', 'contain'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch ($type) {
|
||||||
|
// 按比例裁剪
|
||||||
|
case 'ratio':
|
||||||
|
if (is_numeric($value)) {
|
||||||
|
$image->ratioCrop($value);
|
||||||
|
$handle++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 按尺寸缩放
|
||||||
|
case 'size':
|
||||||
|
$size = Base::newIntval(explode('x', $value));
|
||||||
|
if (count($size) === 2) {
|
||||||
|
$image->resize($size[0], $size[1]);
|
||||||
|
$handle++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 按尺寸缩放
|
||||||
|
case 'percentage':
|
||||||
|
case 'cover':
|
||||||
|
case 'contain':
|
||||||
|
$size = Base::newIntval(explode('x', $value));
|
||||||
|
if (count($size) === 2) {
|
||||||
|
$image->thumb($size[0], $size[1], $type);
|
||||||
|
$handle++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($handle > 0) {
|
||||||
|
$image->saveTo($savePath);
|
||||||
|
Image::compressImage($savePath, null, 80);
|
||||||
|
return response()->file($savePath, [
|
||||||
|
'Pragma' => 'public',
|
||||||
|
'Cache-Control' => 'max-age=1814400',
|
||||||
|
'Expires' => gmdate('D, d M Y H:i:s', time() + 1814400) . ' GMT',
|
||||||
|
'Last-Modified' => gmdate('D, d M Y H:i:s', filemtime($savePath)) . ' GMT',
|
||||||
|
'ETag' => md5_file($savePath)
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$image->destroy();
|
||||||
|
}
|
||||||
|
} catch (\ImagickException) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -245,7 +245,7 @@ class File extends AbstractModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
$path = 'uploads/tmp/' . date("Ym") . '/';
|
$path = 'uploads/tmp/file/' . date("Ym") . '/';
|
||||||
$data = Base::upload([
|
$data = Base::upload([
|
||||||
"file" => Request::file('files'),
|
"file" => Request::file('files'),
|
||||||
"type" => 'more',
|
"type" => 'more',
|
||||||
|
|||||||
@ -2507,7 +2507,7 @@ class Base
|
|||||||
*/
|
*/
|
||||||
public static function isThumb($file): bool
|
public static function isThumb($file): bool
|
||||||
{
|
{
|
||||||
return preg_match('/_thumb\.(jpg|jpeg|png)$/', $file);
|
return preg_match('/_thumb\.(png|jpg|jpeg)$/', $file);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2535,7 +2535,8 @@ class Base
|
|||||||
*/
|
*/
|
||||||
public static function thumbRestore($file): string
|
public static function thumbRestore($file): string
|
||||||
{
|
{
|
||||||
return preg_replace('/_thumb\.(jpg|jpeg|png)$/', '', $file);
|
$file = preg_replace('/_thumb\.(png|jpg|jpeg)$/', '', $file);
|
||||||
|
return preg_replace('/\/crop\/([^\/]+)$/', '', $file);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -58,11 +58,11 @@ class Image
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 按比例裁剪
|
* 按比例裁剪
|
||||||
* @param int $ratio
|
* @param float $ratio
|
||||||
* @return $this
|
* @return $this
|
||||||
* @throws \ImagickException
|
* @throws \ImagickException
|
||||||
*/
|
*/
|
||||||
public function ratioCrop(int $ratio = 0): static
|
public function ratioCrop(float $ratio = 0): static
|
||||||
{
|
{
|
||||||
if ($ratio === 0) {
|
if ($ratio === 0) {
|
||||||
return $this;
|
return $this;
|
||||||
@ -77,7 +77,7 @@ class Image
|
|||||||
$newHeight = $height;
|
$newHeight = $height;
|
||||||
} elseif ($height > $width * $ratio) {
|
} elseif ($height > $width * $ratio) {
|
||||||
$newWidth = $width;
|
$newWidth = $width;
|
||||||
$newHeight = $width * 3;
|
$newHeight = $width * $ratio;
|
||||||
} else {
|
} else {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -201,6 +201,15 @@ class Image
|
|||||||
$this->image->destroy();
|
$this->image->destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁对象
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function destroy()
|
||||||
|
{
|
||||||
|
$this->image->destroy();
|
||||||
|
}
|
||||||
|
|
||||||
/** ******************************************************************************/
|
/** ******************************************************************************/
|
||||||
/** ******************************************************************************/
|
/** ******************************************************************************/
|
||||||
/** ******************************************************************************/
|
/** ******************************************************************************/
|
||||||
|
|||||||
8
resources/assets/js/functions/common.js
vendored
8
resources/assets/js/functions/common.js
vendored
@ -1067,11 +1067,13 @@ const timezone = require("dayjs/plugin/timezone");
|
|||||||
* 等比缩放尺寸
|
* 等比缩放尺寸
|
||||||
* @param width
|
* @param width
|
||||||
* @param height
|
* @param height
|
||||||
* @param maxWidth
|
* @param maxW
|
||||||
* @param maxHeight
|
* @param maxH
|
||||||
* @returns {{width, height}|{width: number, height: number}}
|
* @returns {{width, height}|{width: number, height: number}}
|
||||||
*/
|
*/
|
||||||
scaleToScale(width, height, maxWidth, maxHeight) {
|
scaleToScale(width, height, maxW, maxH = undefined) {
|
||||||
|
const maxWidth = maxW;
|
||||||
|
const maxHeight = typeof maxH === "undefined" ? maxW : maxH;
|
||||||
let tempWidth;
|
let tempWidth;
|
||||||
let tempHeight;
|
let tempHeight;
|
||||||
if (width > 0 && height > 0) {
|
if (width > 0 && height > 0) {
|
||||||
|
|||||||
97
resources/assets/js/functions/web.js
vendored
97
resources/assets/js/functions/web.js
vendored
@ -252,10 +252,22 @@ import {MarkdownPreview} from "../store/markdown";
|
|||||||
const widthMatch = res.match("width=\"(\\d+)\""),
|
const widthMatch = res.match("width=\"(\\d+)\""),
|
||||||
heightMatch = res.match("height=\"(\\d+)\"");
|
heightMatch = res.match("height=\"(\\d+)\"");
|
||||||
if (widthMatch && heightMatch) {
|
if (widthMatch && heightMatch) {
|
||||||
const width = parseInt(widthMatch[1]),
|
const data = {
|
||||||
height = parseInt(heightMatch[1]),
|
width: parseInt(widthMatch[1]),
|
||||||
maxSize = 40;
|
height: parseInt(heightMatch[1]),
|
||||||
const scale = $A.scaleToScale(width, height, maxSize, maxSize);
|
maxSize: 40,
|
||||||
|
src
|
||||||
|
}
|
||||||
|
const ratioExceed = $A.imageRatioExceed(data.width, data.height, 2)
|
||||||
|
if (ratioExceed > 0 && /\.(png|jpg|jpeg)$/.test(data.src)) {
|
||||||
|
src = $A.thumbRestore(data.src) + `/crop/ratio:${ratioExceed},percentage:80x0`
|
||||||
|
if (data.width > data.height) {
|
||||||
|
data.width = data.height * ratioExceed;
|
||||||
|
} else {
|
||||||
|
data.height = data.width * ratioExceed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const scale = $A.scaleToScale(data.width, data.height, data.maxSize);
|
||||||
imgClassName = `${imgClassName}" style="width:${scale.width}px;height:${scale.height}px`
|
imgClassName = `${imgClassName}" style="width:${scale.width}px;height:${scale.height}px`
|
||||||
}
|
}
|
||||||
return `[image:${src}]`
|
return `[image:${src}]`
|
||||||
@ -274,11 +286,12 @@ import {MarkdownPreview} from "../store/markdown";
|
|||||||
if (imgClassName) {
|
if (imgClassName) {
|
||||||
text = text.replace(/\[image:(.*?)\]/g, `<img class="${imgClassName}" src="$1">`)
|
text = text.replace(/\[image:(.*?)\]/g, `<img class="${imgClassName}" src="$1">`)
|
||||||
text = text.replace(/\{\{RemoteURL\}\}/g, this.apiUrl('../'))
|
text = text.replace(/\{\{RemoteURL\}\}/g, this.apiUrl('../'))
|
||||||
}
|
} else {
|
||||||
const tmpText = text.substring(0, 30)
|
const tmpText = text.substring(0, 30)
|
||||||
if (tmpText.length < text.length) {
|
if (tmpText.length < text.length) {
|
||||||
text = tmpText + '...'
|
text = tmpText + '...'
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return text
|
return text
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -339,13 +352,30 @@ import {MarkdownPreview} from "../store/markdown";
|
|||||||
const widthMatch = res.match(widthReg),
|
const widthMatch = res.match(widthReg),
|
||||||
heightMatch = res.match(heightReg);
|
heightMatch = res.match(heightReg);
|
||||||
if (widthMatch && heightMatch) {
|
if (widthMatch && heightMatch) {
|
||||||
const width = parseInt(widthMatch[1]),
|
const data = {
|
||||||
height = parseInt(heightMatch[1]),
|
res,
|
||||||
maxSize = res.indexOf("emoticon") > -1 ? 150 : 220; // 跟css中的设置一致
|
width: parseInt(widthMatch[1]),
|
||||||
const scale = $A.scaleToScale(width, height, maxSize, maxSize);
|
height: parseInt(heightMatch[1]),
|
||||||
const value = res
|
maxSize: res.indexOf("emoticon") > -1 ? 150 : 220, // 跟css中的设置一致
|
||||||
.replace(widthReg, `original-width="${width}"`)
|
}
|
||||||
.replace(heightReg, `original-height="${height}" style="width:${scale.width}px;height:${scale.height}px"`)
|
if (data.maxSize === 220) {
|
||||||
|
const ratioExceed = $A.imageRatioExceed(data.width, data.height)
|
||||||
|
if (ratioExceed > 0) {
|
||||||
|
const srcMatch = res.match(/src=(["'])(([^'"]*)\.(png|jpg|jpeg))\1/);
|
||||||
|
if (srcMatch) {
|
||||||
|
data.res = data.res.replace(srcMatch[2], $A.thumbRestore(srcMatch[2]) + `/crop/ratio:${ratioExceed},percentage:320x0`)
|
||||||
|
if (data.width > data.height) {
|
||||||
|
data.width = data.height * ratioExceed;
|
||||||
|
} else {
|
||||||
|
data.height = data.width * ratioExceed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const scale = $A.scaleToScale(data.width, data.height, data.maxSize);
|
||||||
|
const value = data.res
|
||||||
|
.replace(widthReg, `original-width="${data.width}"`)
|
||||||
|
.replace(heightReg, `original-height="${data.height}" style="width:${scale.width}px;height:${scale.height}px"`)
|
||||||
text = text.replace(res, value)
|
text = text.replace(res, value)
|
||||||
} else {
|
} else {
|
||||||
text = text.replace(res, `<div class="no-size-image-box">${res}</div>`);
|
text = text.replace(res, `<div class="no-size-image-box">${res}</div>`);
|
||||||
@ -432,11 +462,23 @@ import {MarkdownPreview} from "../store/markdown";
|
|||||||
if (msg.type == 'img') {
|
if (msg.type == 'img') {
|
||||||
if (imgClassName) {
|
if (imgClassName) {
|
||||||
// 缩略图,主要用于回复消息预览
|
// 缩略图,主要用于回复消息预览
|
||||||
const width = parseInt(msg.width),
|
const data = {
|
||||||
height = parseInt(msg.height),
|
width: parseInt(msg.width),
|
||||||
maxSize = 40;
|
height: parseInt(msg.height),
|
||||||
const scale = $A.scaleToScale(width, height, maxSize, maxSize);
|
maxSize: 40,
|
||||||
return `<img class="${imgClassName}" style="width:${scale.width}px;height:${scale.height}px" src="${msg.thumb}">`
|
thumb: msg.thumb
|
||||||
|
}
|
||||||
|
const ratioExceed = $A.imageRatioExceed(data.width, data.height, 2)
|
||||||
|
if (ratioExceed > 0 && /\.(png|jpg|jpeg)$/.test(data.thumb)) {
|
||||||
|
data.thumb = $A.thumbRestore(data.thumb) + `/crop/ratio:${ratioExceed},percentage:80x0`
|
||||||
|
if (data.width > data.height) {
|
||||||
|
data.width = data.height * ratioExceed;
|
||||||
|
} else {
|
||||||
|
data.height = data.width * ratioExceed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const scale = $A.scaleToScale(data.width, data.height, data.maxSize);
|
||||||
|
return `<img class="${imgClassName}" style="width:${scale.width}px;height:${scale.height}px" src="${data.thumb}">`
|
||||||
}
|
}
|
||||||
return `[${$A.L('图片')}]`
|
return `[${$A.L('图片')}]`
|
||||||
} else if (msg.ext == 'mp4') {
|
} else if (msg.ext == 'mp4') {
|
||||||
@ -500,7 +542,9 @@ import {MarkdownPreview} from "../store/markdown";
|
|||||||
* @returns {*|string}
|
* @returns {*|string}
|
||||||
*/
|
*/
|
||||||
thumbRestore(url) {
|
thumbRestore(url) {
|
||||||
return `${url}`.replace(/_thumb\.(jpg|jpeg|png)$/, '')
|
return `${url}`
|
||||||
|
.replace(/_thumb\.(png|jpg|jpeg)$/, '')
|
||||||
|
.replace(/\/crop\/([^\/]+)$/, '')
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -520,6 +564,23 @@ import {MarkdownPreview} from "../store/markdown";
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片尺寸比例超出
|
||||||
|
* @param width
|
||||||
|
* @param height
|
||||||
|
* @param ratio
|
||||||
|
* @param float
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
imageRatioExceed(width, height, ratio = 3, float = 0.5) {
|
||||||
|
if (width && height) {
|
||||||
|
if (width / height > (ratio + float) || height / width > (ratio + float)) {
|
||||||
|
return ratio
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载 VConsole 日志组件
|
* 加载 VConsole 日志组件
|
||||||
* @param key
|
* @param key
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="`content-file ${msg.type}`">
|
<div :class="`content-file ${msg.type}`">
|
||||||
<div class="dialog-file">
|
<div class="dialog-file">
|
||||||
<img v-if="msg.type === 'img'" class="file-img" :style="imageStyle(msg)" :src="msg.thumb" @click="viewFile"/>
|
<img v-if="msg.type === 'img'" class="file-img" :style="imageStyle(msg)" :src="imageSrc(msg)" @click="viewFile"/>
|
||||||
<div v-else-if="isVideoFile(msg)" class="file-video" :style="imageStyle(msg)" @click="viewFile">
|
<div v-else-if="isVideoFile(msg)" class="file-video" :style="imageStyle(msg)" @click="viewFile">
|
||||||
<img v-if="msg.thumb" :src="msg.thumb">
|
<img v-if="msg.thumb" :src="msg.thumb">
|
||||||
<video v-else :width="imageStyle(msg, 'width')" :height="imageStyle(msg, 'height')">
|
<video v-else :width="imageStyle(msg, 'width')" :height="imageStyle(msg, 'height')">
|
||||||
@ -49,9 +49,16 @@ export default {
|
|||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
|
|
||||||
imageStyle(info, type = 'style') {
|
imageStyle({width, height, ext}, type = 'style') {
|
||||||
const {width, height} = info;
|
|
||||||
if (width && height) {
|
if (width && height) {
|
||||||
|
const ratioExceed = $A.imageRatioExceed(width, height)
|
||||||
|
if (['png', 'jpg', 'jpeg'].includes(ext) && ratioExceed > 0) {
|
||||||
|
if (width > height) {
|
||||||
|
width = height * ratioExceed;
|
||||||
|
} else {
|
||||||
|
height = width * ratioExceed;
|
||||||
|
}
|
||||||
|
}
|
||||||
let maxW = 220,
|
let maxW = 220,
|
||||||
maxH = 220,
|
maxH = 220,
|
||||||
tempW = width,
|
tempW = width,
|
||||||
@ -82,6 +89,14 @@ export default {
|
|||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
imageSrc({width, height, ext, thumb}) {
|
||||||
|
const ratioExceed = $A.imageRatioExceed(width, height)
|
||||||
|
if (['png', 'jpg', 'jpeg'].includes(ext) && ratioExceed > 0) {
|
||||||
|
thumb = $A.thumbRestore(thumb) + `/crop/ratio:${ratioExceed},percentage:320x0`;
|
||||||
|
}
|
||||||
|
return thumb;
|
||||||
|
},
|
||||||
|
|
||||||
isVideoFile(msg) {
|
isVideoFile(msg) {
|
||||||
return msg.type === 'file'
|
return msg.type === 'file'
|
||||||
&& msg.ext === 'mp4'
|
&& msg.ext === 'mp4'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user