1
This commit is contained in:
parent
71ec511819
commit
48aaeb8ac2
@ -21,10 +21,12 @@ import com.ruoyi.common.core.domain.AjaxResult;
|
|||||||
import com.ruoyi.common.core.domain.R;
|
import com.ruoyi.common.core.domain.R;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import com.ruoyi.system.service.ISysDictDataService;
|
import com.ruoyi.system.service.ISysDictDataService;
|
||||||
|
import com.ruoyi.utils.OssUtil;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiImplicitParam;
|
import io.swagger.annotations.ApiImplicitParam;
|
||||||
import io.swagger.annotations.ApiImplicitParams;
|
import io.swagger.annotations.ApiImplicitParams;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import springfox.documentation.annotations.ApiIgnore;
|
import springfox.documentation.annotations.ApiIgnore;
|
||||||
@ -75,6 +77,8 @@ public class WebController extends BaseController {
|
|||||||
private IBaseAppService appService;
|
private IBaseAppService appService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private GoogleConfig googleConfig;
|
private GoogleConfig googleConfig;
|
||||||
|
@Autowired
|
||||||
|
private OssUtil ossUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导航栏接口--所有分类
|
* 导航栏接口--所有分类
|
||||||
@ -474,46 +478,17 @@ public class WebController extends BaseController {
|
|||||||
return R.ok(appService.selectNewApp());
|
return R.ok(appService.selectNewApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
@ApiOperation("下载APK文件")
|
@ApiOperation("下载APK文件")
|
||||||
@GetMapping("/downloadApk")
|
@GetMapping("/downloadApk")
|
||||||
public void downloadApk(HttpServletResponse response) {
|
public void downloadApk(HttpServletResponse response,@RequestParam(required = false) String id) {
|
||||||
try {
|
BaseApp appVersion;
|
||||||
// APK文件路径
|
if(StringUtils.isNotEmpty(id)){
|
||||||
File file = new File(googleConfig.getAppDownload());
|
appVersion = appService.getById(id);
|
||||||
|
}else{
|
||||||
// 检查文件是否存在
|
appVersion = appService.selectNewApp();
|
||||||
if (!file.exists()) {
|
|
||||||
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
|
||||||
response.getWriter().write("文件不存在");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置响应头
|
|
||||||
response.setContentType("application/vnd.android.package-archive");
|
|
||||||
response.setHeader("Content-Disposition", "attachment; filename=truck.apk");
|
|
||||||
response.setHeader("Content-Length", String.valueOf(file.length()));
|
|
||||||
|
|
||||||
// 写入响应流
|
|
||||||
FileInputStream fis = new FileInputStream(file);
|
|
||||||
OutputStream os = response.getOutputStream();
|
|
||||||
|
|
||||||
byte[] buffer = new byte[1024];
|
|
||||||
int bytesRead;
|
|
||||||
while ((bytesRead = fis.read(buffer)) != -1) {
|
|
||||||
os.write(buffer, 0, bytesRead);
|
|
||||||
}
|
|
||||||
|
|
||||||
fis.close();
|
|
||||||
os.flush();
|
|
||||||
os.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
try {
|
|
||||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
|
||||||
response.getWriter().write("文件下载失败: " + e.getMessage());
|
|
||||||
} catch (Exception ex) {
|
|
||||||
logger.error("下载APK文件时发生错误", ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
ossUtil.downloadFile(response, appVersion.getApkUrl(), appVersion.getVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,6 +33,10 @@ public class BaseApp extends DlBaseEntity
|
|||||||
@Excel(name = "版本")
|
@Excel(name = "版本")
|
||||||
private String version;
|
private String version;
|
||||||
|
|
||||||
|
/** app包下载地址 */
|
||||||
|
@Excel(name = "app包下载地址")
|
||||||
|
private String apkUrl;
|
||||||
|
|
||||||
/** 本次升级描述 */
|
/** 本次升级描述 */
|
||||||
@Excel(name = "本次升级描述")
|
@Excel(name = "本次升级描述")
|
||||||
private String content;
|
private String content;
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
package com.ruoyi.utils;
|
||||||
|
|
||||||
|
import com.aliyun.oss.ClientBuilderConfiguration;
|
||||||
|
import com.aliyun.oss.OSS;
|
||||||
|
import com.aliyun.oss.OSSClientBuilder;
|
||||||
|
import com.aliyuncs.utils.IOUtils;
|
||||||
|
import com.ruoyi.common.config.OssConfig;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class OssUtil {
|
||||||
|
@Autowired
|
||||||
|
private OssConfig ossConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件(fileKey-InputStream)
|
||||||
|
*/
|
||||||
|
public void uploadFile(String fileKey, InputStream stream) throws IOException {
|
||||||
|
ClientBuilderConfiguration conf = new ClientBuilderConfiguration();
|
||||||
|
OSS client = new OSSClientBuilder().build(ossConfig.getEndPoint(), ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret(), conf);
|
||||||
|
|
||||||
|
try {
|
||||||
|
client.putObject(ossConfig.getBucketName(), fileKey, stream);
|
||||||
|
} finally {
|
||||||
|
stream.close();
|
||||||
|
if (client != null) {
|
||||||
|
client.shutdown();
|
||||||
|
}
|
||||||
|
IOUtils.closeQuietly(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件下载
|
||||||
|
*/
|
||||||
|
public void downloadFile(HttpServletResponse response, String fileKey, String fileName) throws IOException {
|
||||||
|
fileKey = fileKey.replace("https://"+ossConfig.getBucketName()+"."+ossConfig.getEndPoint()+"/","");
|
||||||
|
InputStream stream = null;
|
||||||
|
BufferedInputStream bufferedInputStream = null;
|
||||||
|
ClientBuilderConfiguration conf = new ClientBuilderConfiguration();
|
||||||
|
OSS client = new OSSClientBuilder().build(ossConfig.getEndPoint(), ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret(), conf);
|
||||||
|
try {
|
||||||
|
stream = client.getObject(ossConfig.getBucketName(), fileKey).getObjectContent();
|
||||||
|
response.reset();
|
||||||
|
response.setContentType("application/octet-stream");
|
||||||
|
response.addHeader("Content-Disposition", "attachment;filename=CDbay-" + URLEncoder.encode(fileName, "UTF-8")
|
||||||
|
.replace("+", "%20")+".apk");
|
||||||
|
OutputStream outputStream = response.getOutputStream();
|
||||||
|
bufferedInputStream = new BufferedInputStream(stream);
|
||||||
|
byte[] buf = new byte[16384];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = stream.read(buf, 0, buf.length)) >= 0) {
|
||||||
|
outputStream.write(buf, 0, bytesRead);
|
||||||
|
outputStream.flush();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (client != null) {
|
||||||
|
client.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bufferedInputStream != null || stream != null) {
|
||||||
|
try {
|
||||||
|
bufferedInputStream.close();
|
||||||
|
stream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Oss Download IOException,fileKey:" + fileKey, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<resultMap type="BaseApp" id="BaseAppResult">
|
<resultMap type="BaseApp" id="BaseAppResult">
|
||||||
<result property="id" column="id" />
|
<result property="id" column="id" />
|
||||||
<result property="version" column="version" />
|
<result property="version" column="version" />
|
||||||
|
<result property="apkUrl" column="apk_url" />
|
||||||
<result property="content" column="content" />
|
<result property="content" column="content" />
|
||||||
<result property="creator" column="creator" />
|
<result property="creator" column="creator" />
|
||||||
<result property="createTime" column="create_time" />
|
<result property="createTime" column="create_time" />
|
||||||
@ -16,7 +17,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<sql id="selectBaseAppVo">
|
<sql id="selectBaseAppVo">
|
||||||
select id, version, content, creator, create_time, updater, update_time, del_flag from dl_base_app
|
select id, version, apk_url, content, creator, create_time, updater, update_time, del_flag from dl_base_app
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<select id="queryListPage" parameterType="BaseApp" resultMap="BaseAppResult">
|
<select id="queryListPage" parameterType="BaseApp" resultMap="BaseAppResult">
|
||||||
@ -24,6 +25,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<where>
|
<where>
|
||||||
<if test="entity.version != null "> and version like concat('%', #{entity.version}, '%')</if>
|
<if test="entity.version != null "> and version like concat('%', #{entity.version}, '%')</if>
|
||||||
</where>
|
</where>
|
||||||
order by version DESC,create_time desc
|
order by create_time desc
|
||||||
</select>
|
</select>
|
||||||
</mapper>
|
</mapper>
|
@ -1,46 +1,184 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
<!-- 压缩比例设置 -->
|
||||||
|
<div class="compression-setting">
|
||||||
|
<div class="compression-info">
|
||||||
|
图片压缩比例: {{ compressionRatio.toFixed(1) }}x
|
||||||
|
<el-tooltip class="item" effect="dark" content="值越小压缩率越高,图片质量越低"
|
||||||
|
placement="bottom"
|
||||||
|
>
|
||||||
|
<i class="el-icon-question"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<el-slider
|
||||||
|
v-model="compressionRatio"
|
||||||
|
:min="0.1"
|
||||||
|
:max="1.0"
|
||||||
|
:step="0.1"
|
||||||
|
:show-stops="true"
|
||||||
|
:tooltip-format="formatCompressionRatio"
|
||||||
|
></el-slider>
|
||||||
|
</div>
|
||||||
<vue-ueditor-wrap v-model="value"
|
<vue-ueditor-wrap v-model="value"
|
||||||
:destroy="true"
|
:destroy="true"
|
||||||
:config="editorConfig"
|
:config="editorConfig"
|
||||||
|
:before-upload="handleBeforeUpload"
|
||||||
:editorDependencies="['ueditor.config.js','ueditor.all.js']"
|
:editorDependencies="['ueditor.config.js','ueditor.all.js']"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import VueUeditorWrap from 'vue-ueditor-wrap';
|
import VueUeditorWrap from 'vue-ueditor-wrap'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components:{
|
components: {
|
||||||
VueUeditorWrap
|
VueUeditorWrap
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
/* 编辑器的内容 */
|
/* 编辑器的内容 */
|
||||||
value: {
|
value: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
value(newVaue,oldValue){
|
value(newVaue, oldValue) {
|
||||||
this.$emit('input', newVaue);
|
this.$emit('input', newVaue)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted(){
|
mounted() {
|
||||||
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
editorConfig: {
|
editorConfig: {
|
||||||
// 编辑器后端服务接口,参考后端规范 https://open-doc.modstart.com/ueditor-plus/backend.html
|
// 编辑器后端服务接口,参考后端规范 https://open-doc.modstart.com/ueditor-plus/backend.html
|
||||||
serverUrl: process.env.VUE_APP_BASE_API+'/sys/ueditor/exec',
|
serverUrl: process.env.VUE_APP_BASE_API + '/sys/ueditor/exec',
|
||||||
UEDITOR_HOME_URL: '/UEditor/',
|
UEDITOR_HOME_URL: '/UEditor/',
|
||||||
//**代码补充231218**
|
//**代码补充231218**
|
||||||
UEDITOR_CORS_URL: '/UEditor/',
|
UEDITOR_CORS_URL: '/UEditor/',
|
||||||
zIndex: 999,//设置z轴顺序值,避免工具栏下拉组件被遮挡
|
zIndex: 999,//设置z轴顺序值,避免工具栏下拉组件被遮挡
|
||||||
|
// 图片上传相关配置
|
||||||
|
imageMaxSize: 5 * 1024 * 1024, // 5MB,这里只是限制,实际压缩还需处理
|
||||||
|
imageAllowFiles: ['.png', '.jpg', '.jpeg', '.gif', '.bmp']
|
||||||
|
},
|
||||||
|
// 压缩比例 (0.1-1.0)
|
||||||
|
compressionRatio: 0.9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 格式化压缩比例显示
|
||||||
|
formatCompressionRatio(value) {
|
||||||
|
return `${value.toFixed(1)}x`
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 上传前处理图片压缩
|
||||||
|
* @param {File} file 原始文件
|
||||||
|
* @param {Function} callback 回调函数
|
||||||
|
*/
|
||||||
|
handleBeforeUpload(file, callback) {
|
||||||
|
debugger
|
||||||
|
// 只处理图片文件
|
||||||
|
if (!file.type.match(/image\/\w+/)) {
|
||||||
|
return callback(file, true) // 非图片直接上传
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 小于100KB的图片不压缩
|
||||||
|
if (file.size < 100 * 1024) {
|
||||||
|
return callback(file, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 进行图片压缩
|
||||||
|
this.compressImage(file).then(compressedFile => {
|
||||||
|
callback(compressedFile, true)
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('图片压缩失败:', err)
|
||||||
|
callback(file, true) // 压缩失败则上传原图
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片压缩处理
|
||||||
|
* @param {File} file 原始图片文件
|
||||||
|
* @returns {Promise<File>} 压缩后的文件
|
||||||
|
*/
|
||||||
|
compressImage(file) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const img = new Image()
|
||||||
|
const reader = new FileReader()
|
||||||
|
|
||||||
|
reader.onload = (e) => {
|
||||||
|
img.src = e.target.result
|
||||||
|
}
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
// 创建canvas元素
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
const ctx = canvas.getContext('2d')
|
||||||
|
|
||||||
|
// 计算压缩后的尺寸
|
||||||
|
let width = img.width
|
||||||
|
let height = img.height
|
||||||
|
|
||||||
|
// 设置canvas尺寸
|
||||||
|
canvas.width = width
|
||||||
|
canvas.height = height
|
||||||
|
|
||||||
|
// 绘制图片到canvas
|
||||||
|
ctx.drawImage(img, 0, 0, width, height)
|
||||||
|
|
||||||
|
// 将canvas内容转为Blob
|
||||||
|
canvas.toBlob(
|
||||||
|
(blob) => {
|
||||||
|
if (!blob) {
|
||||||
|
reject(new Error('压缩失败'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建新的File对象
|
||||||
|
const compressedFile = new File(
|
||||||
|
[blob],
|
||||||
|
file.name,
|
||||||
|
{
|
||||||
|
type: blob.type || file.type,
|
||||||
|
lastModified: Date.now()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
resolve(compressedFile)
|
||||||
|
},
|
||||||
|
file.type || 'image/jpeg',
|
||||||
|
this.compressionRatio
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
img.onerror = (err) => {
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.readAsDataURL(file)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
// 压缩设置样式
|
||||||
|
.compression-setting {
|
||||||
|
width: 300px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding: 0 10px 8px 10px;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
.compression-info {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .el-slider__runway {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -1,5 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="component-upload-image">
|
<div class="component-upload-image">
|
||||||
|
<!-- 压缩比例设置 -->
|
||||||
|
<div class="compression-setting" v-if="showCompressionSetting">
|
||||||
|
<div class="compression-info">
|
||||||
|
压缩比例: {{ compressionRatio.toFixed(1) }}x
|
||||||
|
<el-tooltip class="item" effect="dark" content="值越小压缩率越高,图片质量越低"
|
||||||
|
placement="bottom"
|
||||||
|
>
|
||||||
|
<i class="el-icon-question"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<el-slider
|
||||||
|
v-model="compressionRatio"
|
||||||
|
:min="0.1"
|
||||||
|
:max="1.0"
|
||||||
|
:step="0.1"
|
||||||
|
:show-stops="true"
|
||||||
|
:tooltip-format="formatCompressionRatio"
|
||||||
|
></el-slider>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-upload
|
<el-upload
|
||||||
multiple
|
multiple
|
||||||
:action="uploadImgUrl"
|
:action="uploadImgUrl"
|
||||||
@ -17,8 +37,9 @@
|
|||||||
:on-preview="handlePictureCardPreview"
|
:on-preview="handlePictureCardPreview"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:class="{hide: this.fileList.length >= this.limit}"
|
:class="{hide: this.fileList.length >= this.limit}"
|
||||||
|
:http-request="customUpload"
|
||||||
>
|
>
|
||||||
<i class="el-icon-plus"></i>
|
<i class="el-icon-plus"></i>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
|
|
||||||
<!-- 上传提示 -->
|
<!-- 上传提示 -->
|
||||||
@ -64,7 +85,7 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
// 文件类型, 例如['png', 'jpg', 'jpeg']
|
// 文件类型, 例如['png', 'jpeg', 'jpg']
|
||||||
fileType: {
|
fileType: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => ["png", "jpg", "jpeg"],
|
default: () => ["png", "jpg", "jpeg"],
|
||||||
@ -73,6 +94,16 @@ export default {
|
|||||||
isShowTip: {
|
isShowTip: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
|
},
|
||||||
|
// 是否显示压缩设置
|
||||||
|
showCompression: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
// 默认压缩比例
|
||||||
|
defaultCompressionRatio: {
|
||||||
|
type: Number,
|
||||||
|
default: 0.9
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@ -87,9 +118,21 @@ export default {
|
|||||||
headers: {
|
headers: {
|
||||||
Authorization: "Bearer " + getToken(),
|
Authorization: "Bearer " + getToken(),
|
||||||
},
|
},
|
||||||
fileList: []
|
fileList: [],
|
||||||
|
// 压缩比例 (0.1-1.0)
|
||||||
|
compressionRatio: this.defaultCompressionRatio
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
// 是否显示提示
|
||||||
|
showTip() {
|
||||||
|
return this.isShowTip && (this.fileType || this.fileSize);
|
||||||
|
},
|
||||||
|
// 是否显示压缩设置
|
||||||
|
showCompressionSetting() {
|
||||||
|
return this.showCompression && !this.disabled && this.fileList.length < this.limit;
|
||||||
|
}
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
value: {
|
value: {
|
||||||
handler(val) {
|
handler(val) {
|
||||||
@ -114,26 +157,32 @@ export default {
|
|||||||
},
|
},
|
||||||
deep: true,
|
deep: true,
|
||||||
immediate: true
|
immediate: true
|
||||||
|
},
|
||||||
|
// 监听默认压缩比例变化
|
||||||
|
defaultCompressionRatio: {
|
||||||
|
handler(val) {
|
||||||
|
this.compressionRatio = val;
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
// 是否显示提示
|
|
||||||
showTip() {
|
|
||||||
return this.isShowTip && (this.fileType || this.fileSize);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
// 上传前loading加载
|
// 格式化压缩比例显示
|
||||||
handleBeforeUpload(file) {
|
formatCompressionRatio(value) {
|
||||||
|
return `${value.toFixed(1)}x`;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 上传前验证
|
||||||
|
async handleBeforeUpload(file) {
|
||||||
let isImg = false;
|
let isImg = false;
|
||||||
if (this.fileType.length) {
|
if (this.fileType.length) {
|
||||||
let fileExtension = "";
|
let fileExtension = "";
|
||||||
if (file.name.lastIndexOf(".") > -1) {
|
if (file.name.lastIndexOf(".") > -1) {
|
||||||
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
|
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1).toLowerCase();
|
||||||
}
|
}
|
||||||
isImg = this.fileType.some(type => {
|
isImg = this.fileType.some(type => {
|
||||||
if (file.type.indexOf(type) > -1) return true;
|
if (file.type.indexOf(type) > -1) return true;
|
||||||
if (fileExtension && fileExtension.indexOf(type) > -1) return true;
|
if (fileExtension && fileExtension === type.toLowerCase()) return true;
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -144,28 +193,151 @@ export default {
|
|||||||
this.$modal.msgError(`文件格式不正确,请上传${this.fileType.join("/")}图片格式文件!`);
|
this.$modal.msgError(`文件格式不正确,请上传${this.fileType.join("/")}图片格式文件!`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.name.includes(',')) {
|
if (file.name.includes(',')) {
|
||||||
this.$modal.msgError('文件名不正确,不能包含英文逗号!');
|
this.$modal.msgError('文件名不正确,不能包含英文逗号!');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.fileSize) {
|
|
||||||
const isLt = file.size / 1024 / 1024 < this.fileSize;
|
// 这里只做验证,不限制大小,因为用户可以通过压缩来减小文件大小
|
||||||
if (!isLt) {
|
this.$modal.loading("正在处理图片,请稍候...");
|
||||||
this.$modal.msgError(`上传头像图片大小不能超过 ${this.fileSize} MB!`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.$modal.loading("正在上传图片,请稍候...");
|
|
||||||
this.number++;
|
this.number++;
|
||||||
|
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 自定义上传方法,包含压缩逻辑
|
||||||
|
customUpload(options) {
|
||||||
|
const file = options.file;
|
||||||
|
|
||||||
|
// 调用图片压缩方法
|
||||||
|
this.compressImage(file, this.compressionRatio)
|
||||||
|
.then(compressedFile => {
|
||||||
|
// 创建FormData对象,模拟表单提交
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', compressedFile, file.name);
|
||||||
|
|
||||||
|
// 创建XMLHttpRequest对象
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('post', this.uploadImgUrl, true);
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
Object.keys(this.headers).forEach(key => {
|
||||||
|
xhr.setRequestHeader(key, this.headers[key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 上传进度处理
|
||||||
|
xhr.upload.addEventListener('progress', (e) => {
|
||||||
|
if (e.total > 0) {
|
||||||
|
e.percent = e.loaded / e.total * 100;
|
||||||
|
}
|
||||||
|
options.onProgress(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 上传成功处理
|
||||||
|
xhr.addEventListener('load', () => {
|
||||||
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
|
const response = JSON.parse(xhr.responseText);
|
||||||
|
options.onSuccess(response);
|
||||||
|
} else {
|
||||||
|
options.onError(xhr.responseText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 上传错误处理
|
||||||
|
xhr.addEventListener('error', () => {
|
||||||
|
options.onError(xhr.responseText);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
xhr.send(formData);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.$modal.msgError('图片压缩失败: ' + error.message);
|
||||||
|
this.$modal.closeLoading();
|
||||||
|
this.number--;
|
||||||
|
options.onError(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 图片压缩核心方法
|
||||||
|
compressImage(file, quality = 0.8) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// 如果不是图片,直接返回原文件
|
||||||
|
if (!file.type.match(/image.*/)) {
|
||||||
|
resolve(file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = (e) => {
|
||||||
|
const img = new Image();
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
// 创建canvas元素
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
// 设置canvas尺寸,保持原图比例
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
|
||||||
|
// 在canvas上绘制图片
|
||||||
|
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// 获取图片类型
|
||||||
|
const mimeType = file.type || 'image/jpeg';
|
||||||
|
|
||||||
|
// 将canvas内容转为Blob对象
|
||||||
|
canvas.toBlob((blob) => {
|
||||||
|
if (!blob) {
|
||||||
|
reject(new Error('无法压缩图片'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将Blob转为File对象
|
||||||
|
const compressedFile = new File([blob], file.name, {
|
||||||
|
type: mimeType,
|
||||||
|
lastModified: Date.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve(compressedFile);
|
||||||
|
}, mimeType, quality);
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onerror = (error) => {
|
||||||
|
reject(new Error('无法加载图片进行压缩'));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载图片
|
||||||
|
img.src = e.target.result;
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.onerror = (error) => {
|
||||||
|
reject(new Error('无法读取图片文件'));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 读取文件
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// 文件个数超出
|
// 文件个数超出
|
||||||
handleExceed() {
|
handleExceed() {
|
||||||
this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`);
|
this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`);
|
||||||
},
|
},
|
||||||
|
|
||||||
// 上传成功回调
|
// 上传成功回调
|
||||||
handleUploadSuccess(res, file) {
|
handleUploadSuccess(res, file) {
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
this.uploadList.push({ name: res.name, url: res.fileName,size:res.size,width:res.width,height:res.height });
|
this.uploadList.push({
|
||||||
|
name: res.name,
|
||||||
|
url: res.fileName,
|
||||||
|
size: res.size,
|
||||||
|
width: res.width,
|
||||||
|
height: res.height
|
||||||
|
});
|
||||||
this.uploadedSuccessfully();
|
this.uploadedSuccessfully();
|
||||||
} else {
|
} else {
|
||||||
this.number--;
|
this.number--;
|
||||||
@ -175,6 +347,7 @@ export default {
|
|||||||
this.uploadedSuccessfully();
|
this.uploadedSuccessfully();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 删除图片
|
// 删除图片
|
||||||
handleDelete(file) {
|
handleDelete(file) {
|
||||||
const findex = this.fileList.map(f => f.name).indexOf(file.name);
|
const findex = this.fileList.map(f => f.name).indexOf(file.name);
|
||||||
@ -183,27 +356,31 @@ export default {
|
|||||||
this.$emit("input", this.listToString(this.fileList));
|
this.$emit("input", this.listToString(this.fileList));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 上传失败
|
// 上传失败
|
||||||
handleUploadError() {
|
handleUploadError() {
|
||||||
this.$modal.msgError("上传图片失败,请重试");
|
this.$modal.msgError("上传图片失败,请重试");
|
||||||
this.$modal.closeLoading();
|
this.$modal.closeLoading();
|
||||||
},
|
},
|
||||||
|
|
||||||
// 上传结束处理
|
// 上传结束处理
|
||||||
uploadedSuccessfully() {
|
uploadedSuccessfully() {
|
||||||
if (this.number > 0 && this.uploadList.length === this.number) {
|
if (this.number > 0 && this.uploadList.length === this.number) {
|
||||||
this.fileList = this.fileList.concat(this.uploadList);
|
this.fileList = this.fileList.concat(this.uploadList);
|
||||||
this.$emit('uploadedImg',this.fileList)
|
this.$emit('uploadedImg', this.fileList);
|
||||||
this.uploadList = [];
|
this.uploadList = [];
|
||||||
this.number = 0;
|
this.number = 0;
|
||||||
this.$emit("input", this.listToString(this.fileList));
|
this.$emit("input", this.listToString(this.fileList));
|
||||||
this.$modal.closeLoading();
|
this.$modal.closeLoading();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 预览
|
// 预览
|
||||||
handlePictureCardPreview(file) {
|
handlePictureCardPreview(file) {
|
||||||
this.dialogImageUrl = file.url;
|
this.dialogImageUrl = file.url;
|
||||||
this.dialogVisible = true;
|
this.dialogVisible = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
// 对象转成指定字符串分隔
|
// 对象转成指定字符串分隔
|
||||||
listToString(list, separator) {
|
listToString(list, separator) {
|
||||||
let strs = "";
|
let strs = "";
|
||||||
@ -213,7 +390,7 @@ export default {
|
|||||||
strs += list[i].url.replace(this.baseUrl, "") + separator;
|
strs += list[i].url.replace(this.baseUrl, "") + separator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return strs != '' ? strs.substr(0, strs.length - 1) : '';
|
return strs !== '' ? strs.substr(0, strs.length - 1) : '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -223,6 +400,7 @@ export default {
|
|||||||
::v-deep.hide .el-upload--picture-card {
|
::v-deep.hide .el-upload--picture-card {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 去掉动画效果
|
// 去掉动画效果
|
||||||
::v-deep .el-list-enter-active,
|
::v-deep .el-list-enter-active,
|
||||||
::v-deep .el-list-leave-active {
|
::v-deep .el-list-leave-active {
|
||||||
@ -233,6 +411,21 @@ export default {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 压缩设置样式
|
||||||
|
.compression-setting {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding: 0 10px 8px 10px;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
.compression-info {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
::v-deep .el-slider__runway{
|
||||||
|
margin:0 !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
icon="el-icon-edit"
|
icon="el-icon-edit"
|
||||||
@click="handleUpdate(scope.row)"
|
@click="handleUpdate(scope.row)"
|
||||||
v-hasPermi="['base:app:edit']"
|
v-hasPermi="['base:app:edit']"
|
||||||
>修改</el-button>
|
>下载</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
size="mini"
|
size="mini"
|
||||||
type="text"
|
type="text"
|
||||||
@ -69,7 +69,10 @@
|
|||||||
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
|
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
|
||||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||||
<el-form-item label="版本" prop="version">
|
<el-form-item label="版本" prop="version">
|
||||||
<el-input v-model="form.version" placeholder="请输入版本" />
|
<el-input type="input" v-model="form.version" placeholder="请输入版本" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="app包" prop="apkUrl">
|
||||||
|
<file-upload v-model="form.apkUrl" :limit="1" :fileType="fileType" :fileSize="50"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="本次升级描述" prop="content">
|
<el-form-item label="本次升级描述" prop="content">
|
||||||
<el-input v-model="form.content" type="textarea" placeholder="请输入内容" />
|
<el-input v-model="form.content" type="textarea" placeholder="请输入内容" />
|
||||||
@ -122,7 +125,11 @@ export default {
|
|||||||
version: [
|
version: [
|
||||||
{ required: true, message: '请输入版本号', trigger: 'blur' }
|
{ required: true, message: '请输入版本号', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
}
|
apkUrl: [
|
||||||
|
{ required: true, message: '请上传程序包', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
},
|
||||||
|
baseUrl: process.env.VUE_APP_BASE_API,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@ -148,6 +155,7 @@ export default {
|
|||||||
this.form = {
|
this.form = {
|
||||||
id: null,
|
id: null,
|
||||||
version: null,
|
version: null,
|
||||||
|
apkUrl: null,
|
||||||
content: null,
|
content: null,
|
||||||
creator: null,
|
creator: null,
|
||||||
createTime: null,
|
createTime: null,
|
||||||
@ -179,15 +187,18 @@ export default {
|
|||||||
this.open = true;
|
this.open = true;
|
||||||
this.title = "添加app版本管理";
|
this.title = "添加app版本管理";
|
||||||
},
|
},
|
||||||
/** 修改按钮操作 */
|
/** 下载 */
|
||||||
handleUpdate(row) {
|
handleUpdate(row) {
|
||||||
this.reset();
|
let a = document.createElement("a"); // 创建a标签
|
||||||
const id = row.id || this.ids
|
a.style.display = "none";
|
||||||
getApp(id).then(response => {
|
a.href = this.baseUrl + "/web/downloadApk?id="+row.id;
|
||||||
this.form = response.data;
|
a.setAttribute(
|
||||||
this.open = true;
|
"download",
|
||||||
this.title = "修改app版本管理";
|
a.href.split("/")[a.href.split("/").length - 1]
|
||||||
});
|
); // 给a标签设置download属性值,为从接口中获取到的需要下载的文件地址
|
||||||
|
document.body.appendChild(a); // 将标签添加到页面
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
},
|
},
|
||||||
/** 提交按钮 */
|
/** 提交按钮 */
|
||||||
submitForm() {
|
submitForm() {
|
||||||
|
@ -105,7 +105,7 @@
|
|||||||
<el-form-item label="所属分类" prop="catgId">
|
<el-form-item label="所属分类" prop="catgId">
|
||||||
<div class="dl-flex-column">
|
<div class="dl-flex-column">
|
||||||
<treeselect style="width: 200px" v-model="form.catgId" :options="catgOptions" :normalizer="normalizer"
|
<treeselect style="width: 200px" v-model="form.catgId" :options="catgOptions" :normalizer="normalizer"
|
||||||
:noResultsText="'暂无数据'" placeholder="请选择新闻分类"
|
:noResultsText="'暂无数据'" placeholder="请选择新闻分类" :disable-branch-nodes="true"
|
||||||
/>
|
/>
|
||||||
<div class="dl-add-catg" @click="goCatgView">添加新闻分类</div>
|
<div class="dl-add-catg" @click="goCatgView">添加新闻分类</div>
|
||||||
</div>
|
</div>
|
||||||
@ -184,7 +184,7 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="8">
|
<el-col :span="9">
|
||||||
<el-form-item label="新闻图" prop="mainPic">
|
<el-form-item label="新闻图" prop="mainPic">
|
||||||
<el-tag v-if="!form.mainPic" style="cursor: pointer" @click="choosePic('mainPic',1)">图片库选择</el-tag>
|
<el-tag v-if="!form.mainPic" style="cursor: pointer" @click="choosePic('mainPic',1)">图片库选择</el-tag>
|
||||||
<image-upload @uploadedImg="uploadedImg" v-model="form.mainPic" :limit="1"/>
|
<image-upload @uploadedImg="uploadedImg" v-model="form.mainPic" :limit="1"/>
|
||||||
|
@ -102,7 +102,7 @@
|
|||||||
<el-form-item label="所属分类" prop="catgId">
|
<el-form-item label="所属分类" prop="catgId">
|
||||||
<div class="dl-flex-column">
|
<div class="dl-flex-column">
|
||||||
<treeselect style="width: 200px" v-model="form.catgId" :options="catgOptions" :normalizer="normalizer"
|
<treeselect style="width: 200px" v-model="form.catgId" :options="catgOptions" :normalizer="normalizer"
|
||||||
:noResultsText="'暂无数据'" placeholder="请选择产品分类"
|
:noResultsText="'暂无数据'" placeholder="请选择产品分类" :disable-branch-nodes="true"
|
||||||
/>
|
/>
|
||||||
<div class="dl-add-catg" @click="goCatgView">添加产品分类</div>
|
<div class="dl-add-catg" @click="goCatgView">添加产品分类</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user