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'] ElSelect: typeof import('element-plus/es')['ElSelect']
ElSelectV2: typeof import('element-plus/es')['ElSelectV2'] ElSelectV2: typeof import('element-plus/es')['ElSelectV2']
ElStatistic: typeof import('element-plus/es')['ElStatistic'] ElStatistic: typeof import('element-plus/es')['ElStatistic']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElUpload: typeof import('element-plus/es')['ElUpload'] ElUpload: typeof import('element-plus/es')['ElUpload']
FloatingMenu: typeof import('./src/components/floatingMenu.vue')['default'] FloatingMenu: typeof import('./src/components/floatingMenu.vue')['default']
Login: typeof import('./src/components/login.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", // "X-Requested-With": "XMLHttpRequest",
// "Content-Type": "application/json", // "Content-Type": "application/json",
}, },
timeout: 5000, // request timeout timeout: 1000 * 60 * 5, // request timeout
}); });
// 拦截前 // 拦截前
// request interceptor // request interceptor

View File

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

View File

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

View File

@ -186,7 +186,11 @@
prop="biz_license_image" prop="biz_license_image"
> >
<el-upload <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" v-model:file-list="uploadFiles.biz_license_image"
multiple multiple
:limit="1" :limit="1"
@ -362,7 +366,11 @@
prop="legal_person_id_images" prop="legal_person_id_images"
> >
<el-upload <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" v-model:file-list="uploadFiles.legal_person_id_images"
:limit="1" :limit="1"
list-type="picture-card" list-type="picture-card"
@ -400,7 +408,7 @@
plain plain
v-if=" v-if="
applyFormData.legal_person_id_images && applyFormData.legal_person_id_images &&
!applyFormData.legal_person_id_number && !applyFormData.legal_person_id_number &&
currentBbatchNo currentBbatchNo
" "
@click=" @click="
@ -414,7 +422,11 @@
prop="legal_person_id_images2" prop="legal_person_id_images2"
> >
<el-upload <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" v-model:file-list="uploadFiles.legal_person_id_images2"
:limit="1" :limit="1"
list-type="picture-card" list-type="picture-card"
@ -503,7 +515,11 @@
prop="individual_id_images" prop="individual_id_images"
> >
<el-upload <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" v-model:file-list="uploadFiles.individual_id_images"
:limit="1" :limit="1"
list-type="picture-card" list-type="picture-card"
@ -543,7 +559,11 @@
prop="individual_id_images2" prop="individual_id_images2"
> >
<el-upload <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" v-model:file-list="uploadFiles.individual_id_images2"
:limit="1" :limit="1"
list-type="picture-card" list-type="picture-card"
@ -641,7 +661,11 @@
<div class="tit">结算信息</div> <div class="tit">结算信息</div>
<el-form-item label="银行卡" prop="bank_image"> <el-form-item label="银行卡" prop="bank_image">
<el-upload <el-upload
:ref="(el)=>{ el && (uploadRefs.bank_image = el) }" :ref="
(el) => {
el && (uploadRefs.bank_image = el);
}
"
v-model:file-list="uploadFiles.bank_image" v-model:file-list="uploadFiles.bank_image"
:limit="1" :limit="1"
list-type="picture-card" list-type="picture-card"
@ -671,7 +695,11 @@
<el-button <el-button
type="info" type="info"
plain plain
v-if="applyFormData.bank_image && !applyFormData.account_number && currentBbatchNo" v-if="
applyFormData.bank_image &&
!applyFormData.account_number &&
currentBbatchNo
"
@click="handleOcrText(currentBbatchNo, 'bank_image')" @click="handleOcrText(currentBbatchNo, 'bank_image')"
>点击免填卡号</el-button >点击免填卡号</el-button
> >
@ -680,10 +708,16 @@
v-if="applyFormData.entity_type == 1 || applyFormData.bank_image" v-if="applyFormData.entity_type == 1 || applyFormData.bank_image"
> >
<el-form-item label="开户名称" prop="account_holder_name"> <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>
<el-form-item label="银行卡号" prop="account_number"> <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>
<el-form-item label="开户银行" prop="bank_branch_name"> <el-form-item label="开户银行" prop="bank_branch_name">
<el-select-v2 <el-select-v2
@ -704,6 +738,34 @@
</el-icon> </el-icon>
</template> </template>
</el-select-v2> </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> </el-form-item>
</template> </template>
</div> </div>
@ -711,9 +773,8 @@
</div> </div>
<div class="form-button"> <div class="form-button">
<el-button type="primary" @click="merchToApply" class="custom-button" <div class="myui_check_text">检查并确认店铺信息和证件信息无误</div>
>提交审核</el-button <el-button type="primary" @click="merchToApply">提交审核</el-button>
>
</div> </div>
<el-dialog v-model="dialogVisible" width="max-content"> <el-dialog v-model="dialogVisible" width="max-content">
@ -726,7 +787,7 @@
import { ref, reactive, onMounted, watch } from "vue"; import { ref, reactive, onMounted, watch } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { ElMessage } from "element-plus"; 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 cityData from "../../stores/cityData";
import type { CityDataStructure } from "../../stores/cityData"; import type { CityDataStructure } from "../../stores/cityData";
@ -741,6 +802,7 @@ import {
GetAppDistrict, GetAppDistrict,
} from "@/api/login"; } from "@/api/login";
import { batchNoApi, imgOcrResultApi } from "@/api/upload"; import { batchNoApi, imgOcrResultApi } from "@/api/upload";
import { compressImage } from "@/utils/file";
interface Bank { interface Bank {
id: number; id: number;
@ -1307,10 +1369,10 @@ const handleSelect = (item) => {
(formRef.value as any).validate(); (formRef.value as any).validate();
}; };
const beforeUpload = (file) => { const beforeUpload = async (file) => {
const isJPG = file.type === "image/jpeg"; const isJPG = file.type === "image/jpeg";
const isPNG = file.type === "image/png"; const isPNG = file.type === "image/png";
const isLt2M = file.size / 1024 / 1024 < 10; const isLarge = file.size / 1024 / 1024 < 10;
loading.value = true; loading.value = true;
@ -1319,11 +1381,13 @@ const beforeUpload = (file) => {
ElMessage.error("上传的图片必须是JPG或PNG格式"); ElMessage.error("上传的图片必须是JPG或PNG格式");
return false; return false;
} }
if (!isLt2M) {
if (!isLarge) {
loading.value = false; loading.value = false;
ElMessage.error("上传的图片大小不能超过10MB"); ElMessage.error("上传的图片大小不能超过10MB");
return false; return false;
} }
return true; return true;
}; };
@ -1365,21 +1429,24 @@ const handlePictureCardPreview = (uploadFile) => {
const getBatchNo = async (file, type, field) => { const getBatchNo = async (file, type, field) => {
loading.value = true; loading.value = true;
currentBbatchNo.value = ""; currentBbatchNo.value = "";
const rsp = await batchNoApi(file, type).then((res) => { file = await compressImage(file);
loading.value = false; const rsp = await batchNoApi(file, type)
return res; .then((res) => {
}).catch(()=> { loading.value = false;
loading.value = false; return res;
const _uploadRef = uploadRefs.value[field] })
if(_uploadRef){ .catch(() => {
_uploadRef.clearFiles() loading.value = false;
uploadFiles[field] = [] const _uploadRef = uploadRefs.value[field];
cleanFile(field) if (_uploadRef) {
} _uploadRef.clearFiles();
ElMessage.error('网络异常,请重试!') uploadFiles[field] = [];
}); cleanFile(field);
}
ElMessage.error("网络异常,请重试!");
});
if (rsp?.code==0 && rsp?.status == 200) { if (rsp?.code == 0 && rsp?.status == 200) {
return rsp?.data; return rsp?.data;
} else { } else {
return null; return null;
@ -1396,7 +1463,7 @@ const getOcrText = async (batchNo, type) => {
clearTimeout(orcTimeout); clearTimeout(orcTimeout);
orcTimeout = setTimeout(async () => { orcTimeout = setTimeout(async () => {
const imgOcrRes = await imgOcrResultApi(formData).finally(()=> { const imgOcrRes = await imgOcrResultApi(formData).finally(() => {
loading.value = false; loading.value = false;
}); });
@ -1434,7 +1501,7 @@ const handleOcrText = async (batchNo, imgType) => {
case "legal_person_id_images": case "legal_person_id_images":
var res = (await getOcrText( var res = (await getOcrText(
batchNo, batchNo,
orcImgTypeConf.ID_CARD_FRONT orcImgTypeConf.FR_ID_CARD_FRONT
)) as any; )) as any;
console.log("legal_person_id_images", res); console.log("legal_person_id_images", res);
@ -1444,7 +1511,7 @@ const handleOcrText = async (batchNo, imgType) => {
case "legal_person_id_images2": case "legal_person_id_images2":
var res = (await getOcrText( var res = (await getOcrText(
batchNo, batchNo,
orcImgTypeConf.ID_CARD_BEHIND orcImgTypeConf.FR_ID_CARD_BEHIND
)) as any; )) as any;
var validity = res.validity.split("-"); var validity = res.validity.split("-");
@ -1461,7 +1528,7 @@ const handleOcrText = async (batchNo, imgType) => {
case "individual_id_images": case "individual_id_images":
var res = (await getOcrText( var res = (await getOcrText(
batchNo, batchNo,
orcImgTypeConf.FR_ID_CARD_FRONT orcImgTypeConf.ID_CARD_FRONT
)) as any; )) as any;
console.log("individual_id_images", res); console.log("individual_id_images", res);
@ -1499,14 +1566,14 @@ const handleOcrText = async (batchNo, imgType) => {
(formRef.value as any).validate(); (formRef.value as any).validate();
}; };
const cleanFile = (field)=>{ const cleanFile = (field) => {
if (field === "license_image") { if (field === "license_image") {
applyFormData[field]=[]; applyFormData[field] = [];
} else { } else {
applyFormData[field] = ""; applyFormData[field] = "";
} }
currentFile.value = null; currentFile.value = null;
} };
const handleUploadSuccess = async (response, file, fileList, field) => { const handleUploadSuccess = async (response, file, fileList, field) => {
if (response && response.status === 200 && response.code === 0) { if (response && response.status === 200 && response.code === 0) {
@ -1524,27 +1591,47 @@ const handleUploadSuccess = async (response, file, fileList, field) => {
switch (field) { switch (field) {
case "biz_license_image": 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; currentBbatchNo.value = res.batchNo;
handleOcrText(res.batchNo, field); handleOcrText(res.batchNo, field);
break; break;
case "legal_person_id_images": 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; currentBbatchNo.value = res.batchNo;
handleOcrText(res.batchNo, field); handleOcrText(res.batchNo, field);
break; break;
case "legal_person_id_images2": 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; currentBbatchNo.value = res.batchNo;
handleOcrText(res.batchNo, field); handleOcrText(res.batchNo, field);
break; break;
case "individual_id_images": 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; currentBbatchNo.value = res.batchNo;
handleOcrText(res.batchNo, field); handleOcrText(res.batchNo, field);
break; break;
case "individual_id_images2": 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; currentBbatchNo.value = res.batchNo;
handleOcrText(res.batchNo, field); handleOcrText(res.batchNo, field);
break; break;
@ -1598,9 +1685,9 @@ const clearOtherFields = () => {
}; };
onMounted(() => { onMounted(() => {
if(!isLoggedIn.value){ if (!isLoggedIn.value) {
router.push({ name: "index" }); router.push({ name: "index" });
return return;
} }
GetStoreCategories() GetStoreCategories()
.then((res) => { .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 { .auto-item {
p { p {
font-size: 15px; font-size: 15px;
@ -1714,5 +1824,9 @@ onMounted(() => {
width: 100%; width: 100%;
} }
} }
.bank_name_tip {
margin-left: 0px;
}
} }
</style> </style>