feat: 实现前端图片压缩10M->50k;处理网络请求超时时间过小;

This commit is contained in:
mixtan 2025-06-19 20:26:59 +08:00
parent 7b8c3179fb
commit 9b8a92f93b
6 changed files with 262 additions and 58 deletions

1
components.d.ts vendored
View File

@ -34,6 +34,7 @@ declare module 'vue' {
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSelectV2: typeof import('element-plus/es')['ElSelectV2']
ElStatistic: typeof import('element-plus/es')['ElStatistic']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElUpload: typeof import('element-plus/es')['ElUpload']
FloatingMenu: typeof import('./src/components/floatingMenu.vue')['default']
Login: typeof import('./src/components/login.vue')['default']

87
src/utils/file.js Normal file
View File

@ -0,0 +1,87 @@
export async function compressImage(file, options = {}) {
// 提取选项参数,设置默认值
const {
quality = 0.75,
maxWidth = 800,
maxHeight = 600
} = options;
// 验证输入是否为有效的File对象
if (!(file instanceof File)) {
throw new Error('输入参数必须是一个File对象');
}
// 检查文件类型是否为图片
if (!file.type.startsWith('image/')) {
throw new Error('输入文件必须是图片类型');
}
// 创建一个Promise来处理异步操作
return new Promise((resolve, reject) => {
// 创建Image对象用于加载图片
const img = new Image();
// 监听图片加载完成事件
img.onload = () => {
// 创建Canvas元素
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 获取原始图片尺寸
let width = img.width;
let height = img.height;
// 计算调整后的尺寸(保持原始比例)
if (width > maxWidth) {
height = height * (maxWidth / width);
width = maxWidth;
}
if (height > maxHeight) {
width = width * (maxHeight / height);
height = maxHeight;
}
// 设置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, lastModified: Date.now() }
);
resolve(compressedFile);
},
file.type, // 使用原始图片类型
quality // 压缩质量
);
};
// 监听图片加载错误事件
img.onerror = () => reject(new Error('图片加载失败'));
// 读取图片数据
const reader = new FileReader();
reader.onload = () => {
img.src = reader.result;
};
reader.onerror = () => reject(new Error('文件读取失败'));
// 开始读取文件
reader.readAsDataURL(file);
});
}

View File

@ -10,7 +10,7 @@ const service = axios.create({
// "X-Requested-With": "XMLHttpRequest",
// "Content-Type": "application/json",
},
timeout: 5000, // request timeout
timeout: 1000 * 60 * 5, // request timeout
});
// 拦截前
// request interceptor

View File

@ -33,7 +33,7 @@
<div class="avatar" v-if="isLoggedIn">
<div class="icon_avatar">
<el-icon size="20"><Avatar /></el-icon>
<el-icon size="20" color="#ccc"><Avatar /></el-icon>
</div>
<el-button
link

View File

@ -181,6 +181,7 @@ import { Plus,Search } from "@element-plus/icons-vue";
import cityData from "../../stores/cityData";
import type { CityDataStructure } from "../../stores/cityData";
import { useUserStore } from "@/stores/userStore";
import { compressImage } from "@/utils/file";
import {
GetStoreCategories,
@ -803,6 +804,7 @@ const handlePictureCardPreview = (uploadFile) => {
const getBatchNo = async (file, type) => {
loading.value = true;
file = await compressImage(file);
const rsp = await batchNoApi(file, type).then((res) => {
return res;
});
@ -854,7 +856,7 @@ const handleOcrText = async (batchNo, imgType) => {
applyFormData.biz_license_content = res.bizLicenseScope;
break;
case "legal_person_id_images":
var res = (await getOcrText(batchNo, orcImgTypeConf.ID_CARD_FRONT)) as any;
var res = (await getOcrText(batchNo, orcImgTypeConf.FR_ID_CARD_FRONT)) as any;
console.log("legal_person_id_images", res);
applyFormData.legal_person_id_number = res.idNumber;
@ -863,7 +865,7 @@ const handleOcrText = async (batchNo, imgType) => {
case "legal_person_id_images2":
var res = (await getOcrText(
batchNo,
orcImgTypeConf.ID_CARD_BEHIND
orcImgTypeConf.FR_ID_CARD_BEHIND
)) as any;
var validity = res.validity.split('-')
@ -879,7 +881,7 @@ const handleOcrText = async (batchNo, imgType) => {
case "individual_id_images":
var res = (await getOcrText(
batchNo,
orcImgTypeConf.FR_ID_CARD_FRONT
orcImgTypeConf.ID_CARD_FRONT
)) as any;
console.log("individual_id_images", res);
@ -936,17 +938,17 @@ const handleUploadSuccess = async (response, file, fileList, field) => {
handleOcrText(res.batchNo, field)
break;
case "legal_person_id_images":
var res = await getBatchNo(file.raw, orcImgTypeConf.ID_CARD_FRONT);
var res = await getBatchNo(file.raw, orcImgTypeConf.FR_ID_CARD_FRONT);
currentBbatchNo.value = res.batchNo
handleOcrText(res.batchNo, field)
break;
case "legal_person_id_images2":
var res = await getBatchNo(file.raw, orcImgTypeConf.ID_CARD_BEHIND);
var res = await getBatchNo(file.raw, orcImgTypeConf.FR_ID_CARD_BEHIND);
currentBbatchNo.value = res.batchNo;
handleOcrText(res.batchNo, field);
break;
case "individual_id_images":
var res = await getBatchNo(file.raw, orcImgTypeConf.FR_ID_CARD_FRONT);
var res = await getBatchNo(file.raw, orcImgTypeConf.ID_CARD_FRONT);
currentBbatchNo.value = res.batchNo;
handleOcrText(res.batchNo, field);
break;

View File

@ -186,7 +186,11 @@
prop="biz_license_image"
>
<el-upload
:ref="(el)=>{ el && (uploadRefs.biz_license_image = el) }"
:ref="
(el) => {
el && (uploadRefs.biz_license_image = el);
}
"
v-model:file-list="uploadFiles.biz_license_image"
multiple
:limit="1"
@ -226,7 +230,7 @@
plain
v-if="
applyFormData.biz_license_image &&
!applyFormData.biz_license_company &&
!applyFormData.biz_license_company &&
currentBbatchNo
"
@click="handleOcrText(currentBbatchNo, 'biz_license_image')"
@ -362,7 +366,11 @@
prop="legal_person_id_images"
>
<el-upload
:ref="(el)=>{ el && (uploadRefs.legal_person_id_images = el) }"
:ref="
(el) => {
el && (uploadRefs.legal_person_id_images = el);
}
"
v-model:file-list="uploadFiles.legal_person_id_images"
:limit="1"
list-type="picture-card"
@ -400,7 +408,7 @@
plain
v-if="
applyFormData.legal_person_id_images &&
!applyFormData.legal_person_id_number &&
!applyFormData.legal_person_id_number &&
currentBbatchNo
"
@click="
@ -414,7 +422,11 @@
prop="legal_person_id_images2"
>
<el-upload
:ref="(el)=>{ el && (uploadRefs.legal_person_id_images2 = el) }"
:ref="
(el) => {
el && (uploadRefs.legal_person_id_images2 = el);
}
"
v-model:file-list="uploadFiles.legal_person_id_images2"
:limit="1"
list-type="picture-card"
@ -503,7 +515,11 @@
prop="individual_id_images"
>
<el-upload
:ref="(el)=>{ el && (uploadRefs.individual_id_images = el) }"
:ref="
(el) => {
el && (uploadRefs.individual_id_images = el);
}
"
v-model:file-list="uploadFiles.individual_id_images"
:limit="1"
list-type="picture-card"
@ -543,7 +559,11 @@
prop="individual_id_images2"
>
<el-upload
:ref="(el)=>{ el && (uploadRefs.individual_id_images2 = el) }"
:ref="
(el) => {
el && (uploadRefs.individual_id_images2 = el);
}
"
v-model:file-list="uploadFiles.individual_id_images2"
:limit="1"
list-type="picture-card"
@ -641,7 +661,11 @@
<div class="tit">结算信息</div>
<el-form-item label="银行卡" prop="bank_image">
<el-upload
:ref="(el)=>{ el && (uploadRefs.bank_image = el) }"
:ref="
(el) => {
el && (uploadRefs.bank_image = el);
}
"
v-model:file-list="uploadFiles.bank_image"
:limit="1"
list-type="picture-card"
@ -671,7 +695,11 @@
<el-button
type="info"
plain
v-if="applyFormData.bank_image && !applyFormData.account_number && currentBbatchNo"
v-if="
applyFormData.bank_image &&
!applyFormData.account_number &&
currentBbatchNo
"
@click="handleOcrText(currentBbatchNo, 'bank_image')"
>点击免填卡号</el-button
>
@ -680,10 +708,16 @@
v-if="applyFormData.entity_type == 1 || applyFormData.bank_image"
>
<el-form-item label="开户名称" prop="account_holder_name">
<el-input v-model="applyFormData.account_holder_name" />
<el-input
v-model="applyFormData.account_holder_name"
placeholder="请输入开户名称"
/>
</el-form-item>
<el-form-item label="银行卡号" prop="account_number">
<el-input v-model="applyFormData.account_number" />
<el-input
v-model="applyFormData.account_number"
placeholder="请输入银行卡号"
/>
</el-form-item>
<el-form-item label="开户银行" prop="bank_branch_name">
<el-select-v2
@ -704,6 +738,34 @@
</el-icon>
</template>
</el-select-v2>
<div class="bank_name_tip">
<el-tooltip placement="top" effect="light">
<template #content>
<div class="bank_name_cont">
<h3>开店时搜不到我的开户银行怎么处理?</h3>
<p>
1.
目前小店的结算账户仅支持下拉框中包含的银行无法搜索到的银行在后续经营中可能无法正常结算提现等功能因此暂不支持
</p>
<p>
2.
若遇到这类问题请商户使用下拉框选项中包含的银行账户进行入驻如开户地为县级市或乡镇区无法搜索到可选择相同银行的市级网点否则将无法进行身份验证
</p>
<p>
3.
为了顺利注册开店与保障账号资产安全推荐优先使用四大国有银行开户注册
</p>
</div>
</template>
<el-button type="info" link>
<el-icon color="#e6a23c" size="14"
><WarningFilled /></el-icon
>搜不到我的开户银行怎么处理</el-button
>
</el-tooltip>
</div>
</el-form-item>
</template>
</div>
@ -711,9 +773,8 @@
</div>
<div class="form-button">
<el-button type="primary" @click="merchToApply" class="custom-button"
>提交审核</el-button
>
<div class="myui_check_text">检查并确认店铺信息和证件信息无误</div>
<el-button type="primary" @click="merchToApply">提交审核</el-button>
</div>
<el-dialog v-model="dialogVisible" width="max-content">
@ -726,7 +787,7 @@
import { ref, reactive, onMounted, watch } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { Plus, Search } from "@element-plus/icons-vue";
import { Plus, Search, WarningFilled } from "@element-plus/icons-vue";
import cityData from "../../stores/cityData";
import type { CityDataStructure } from "../../stores/cityData";
@ -741,6 +802,7 @@ import {
GetAppDistrict,
} from "@/api/login";
import { batchNoApi, imgOcrResultApi } from "@/api/upload";
import { compressImage } from "@/utils/file";
interface Bank {
id: number;
@ -1307,10 +1369,10 @@ const handleSelect = (item) => {
(formRef.value as any).validate();
};
const beforeUpload = (file) => {
const beforeUpload = async (file) => {
const isJPG = file.type === "image/jpeg";
const isPNG = file.type === "image/png";
const isLt2M = file.size / 1024 / 1024 < 10;
const isLarge = file.size / 1024 / 1024 < 10;
loading.value = true;
@ -1319,11 +1381,13 @@ const beforeUpload = (file) => {
ElMessage.error("上传的图片必须是JPG或PNG格式");
return false;
}
if (!isLt2M) {
if (!isLarge) {
loading.value = false;
ElMessage.error("上传的图片大小不能超过10MB");
return false;
}
return true;
};
@ -1365,21 +1429,24 @@ const handlePictureCardPreview = (uploadFile) => {
const getBatchNo = async (file, type, field) => {
loading.value = true;
currentBbatchNo.value = "";
const rsp = await batchNoApi(file, type).then((res) => {
loading.value = false;
return res;
}).catch(()=> {
loading.value = false;
const _uploadRef = uploadRefs.value[field]
if(_uploadRef){
_uploadRef.clearFiles()
uploadFiles[field] = []
cleanFile(field)
}
ElMessage.error('网络异常,请重试!')
});
file = await compressImage(file);
const rsp = await batchNoApi(file, type)
.then((res) => {
loading.value = false;
return res;
})
.catch(() => {
loading.value = false;
const _uploadRef = uploadRefs.value[field];
if (_uploadRef) {
_uploadRef.clearFiles();
uploadFiles[field] = [];
cleanFile(field);
}
ElMessage.error("网络异常,请重试!");
});
if (rsp?.code==0 && rsp?.status == 200) {
if (rsp?.code == 0 && rsp?.status == 200) {
return rsp?.data;
} else {
return null;
@ -1396,7 +1463,7 @@ const getOcrText = async (batchNo, type) => {
clearTimeout(orcTimeout);
orcTimeout = setTimeout(async () => {
const imgOcrRes = await imgOcrResultApi(formData).finally(()=> {
const imgOcrRes = await imgOcrResultApi(formData).finally(() => {
loading.value = false;
});
@ -1434,7 +1501,7 @@ const handleOcrText = async (batchNo, imgType) => {
case "legal_person_id_images":
var res = (await getOcrText(
batchNo,
orcImgTypeConf.ID_CARD_FRONT
orcImgTypeConf.FR_ID_CARD_FRONT
)) as any;
console.log("legal_person_id_images", res);
@ -1444,7 +1511,7 @@ const handleOcrText = async (batchNo, imgType) => {
case "legal_person_id_images2":
var res = (await getOcrText(
batchNo,
orcImgTypeConf.ID_CARD_BEHIND
orcImgTypeConf.FR_ID_CARD_BEHIND
)) as any;
var validity = res.validity.split("-");
@ -1461,7 +1528,7 @@ const handleOcrText = async (batchNo, imgType) => {
case "individual_id_images":
var res = (await getOcrText(
batchNo,
orcImgTypeConf.FR_ID_CARD_FRONT
orcImgTypeConf.ID_CARD_FRONT
)) as any;
console.log("individual_id_images", res);
@ -1499,14 +1566,14 @@ const handleOcrText = async (batchNo, imgType) => {
(formRef.value as any).validate();
};
const cleanFile = (field)=>{
const cleanFile = (field) => {
if (field === "license_image") {
applyFormData[field]=[];
} else {
applyFormData[field] = "";
}
currentFile.value = null;
}
applyFormData[field] = [];
} else {
applyFormData[field] = "";
}
currentFile.value = null;
};
const handleUploadSuccess = async (response, file, fileList, field) => {
if (response && response.status === 200 && response.code === 0) {
@ -1524,27 +1591,47 @@ const handleUploadSuccess = async (response, file, fileList, field) => {
switch (field) {
case "biz_license_image":
var res = await getBatchNo(file.raw, orcImgTypeConf.BUSINESS_LICENCE, field);
var res = await getBatchNo(
file.raw,
orcImgTypeConf.BUSINESS_LICENCE,
field
);
currentBbatchNo.value = res.batchNo;
handleOcrText(res.batchNo, field);
break;
case "legal_person_id_images":
var res = await getBatchNo(file.raw, orcImgTypeConf.ID_CARD_FRONT, field);
var res = await getBatchNo(
file.raw,
orcImgTypeConf.FR_ID_CARD_FRONT,
field
);
currentBbatchNo.value = res.batchNo;
handleOcrText(res.batchNo, field);
break;
case "legal_person_id_images2":
var res = await getBatchNo(file.raw, orcImgTypeConf.ID_CARD_BEHIND, field);
var res = await getBatchNo(
file.raw,
orcImgTypeConf.FR_ID_CARD_BEHIND,
field
);
currentBbatchNo.value = res.batchNo;
handleOcrText(res.batchNo, field);
break;
case "individual_id_images":
var res = await getBatchNo(file.raw, orcImgTypeConf.FR_ID_CARD_FRONT, field);
var res = await getBatchNo(
file.raw,
orcImgTypeConf.ID_CARD_FRONT,
field
);
currentBbatchNo.value = res.batchNo;
handleOcrText(res.batchNo, field);
break;
case "individual_id_images2":
var res = await getBatchNo(file.raw, orcImgTypeConf.ID_CARD_BEHIND, field);
var res = await getBatchNo(
file.raw,
orcImgTypeConf.ID_CARD_BEHIND,
field
);
currentBbatchNo.value = res.batchNo;
handleOcrText(res.batchNo, field);
break;
@ -1598,9 +1685,9 @@ const clearOtherFields = () => {
};
onMounted(() => {
if(!isLoggedIn.value){
if (!isLoggedIn.value) {
router.push({ name: "index" });
return
return;
}
GetStoreCategories()
.then((res) => {
@ -1688,6 +1775,29 @@ onMounted(() => {
}
}
.bank_name_tip {
display: flex;
align-items: center;
margin-left: 10px;
}
.bank_name_cont {
width: 360px;
h3{
margin-bottom: 12px;
}
p {
font-size: 14px;
margin-bottom: 12px;
}
}
.myui_check_text {
color: #999;
padding-bottom: 10px;
text-align: center;
font-size: 14px;
}
.auto-item {
p {
font-size: 15px;
@ -1714,5 +1824,9 @@ onMounted(() => {
width: 100%;
}
}
.bank_name_tip {
margin-left: 0px;
}
}
</style>