Files
pasd_app/pages/work/inspection/scanSign/index.vue
2025-09-04 20:26:46 +08:00

637 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="container">
<view class="example">
<uni-forms ref="dynamicForm" :model="form" label-width="80px">
<uni-forms-item label="巡检点" name="inspectionPoint">
<uni-easyinput disabled :value="form.inspectionPoint" placeholder="请输入巡检点"></uni-easyinput>
</uni-forms-item>
<uni-forms-item label="巡检要求" name="inspectionRequirements">
<uni-easyinput type="textarea" disabled :value="form.inspectionRequirements" placeholder="请输入巡检要求"></uni-easyinput>
</uni-forms-item>
<uni-forms-item label="图片上传">
<view class="example-body">
<view class="custom-image-picker">
<!-- 图片预览区域 -->
<view class="image-preview-container">
<view v-for="(item, index) in img" :key="index" class="image-preview-item">
<image :src="item.url" class="preview-image" mode="aspectFill"></image>
<view class="delete-btn" @click="deleteCustomImg(index)">
<text class="delete-icon">×</text>
</view>
</view>
<!-- 添加图片按钮 -->
<view v-if="img.length < 3" class="add-image-btn" @click="chooseImage">
<text class="add-icon">+</text>
<text class="add-text">添加图片</text>
</view>
</view>
<view class="image-tip">
<text>最多选择3张图片支持自动压缩</text>
</view>
</view>
</view>
</uni-forms-item>
<!-- 备注 -->
<uni-forms-item label="备注1" name="remark">
<uni-easyinput type="textarea" v-model="form.remark" placeholder="请输入备注"></uni-easyinput>
</uni-forms-item>
</uni-forms>
<view class="button-group">
<button class="btn btn-primary" @click="submit()">提交</button>
<button class="btn btn-cancel" @click="cancel()">取消</button>
</view>
</view>
<!-- 消息提示框 -->
<view>
<uni-popup ref="alertDialog" type="dialog">
<uni-popup-dialog :type="msgType" title="消息" :content="messageText" @confirm="dialogConfirm"
showClose="false"></uni-popup-dialog>
</uni-popup>
</view>
</view>
</template>
<script>
import {
uploadImg
} from "@/api/system/user"
import {
addRecord
} from "@/api/inspection/record.js"
import {
addWatermarkToImage
} from "@/utils/watermark.js"
export default {
data() {
return {
form: {
inspectionType: 0,
inspectorId: this.$store.state.user.nickName,
inspectionPoint: "",
inspectionRequirements: "",
inspectionImg: "",
ImgUrl: [],
remark: ""
},
img: [],
imgFiles: "",
msgType: '',
messageText: '',
isUploading: false,
isMobileBrowser: false // 改为更通用的移动浏览器检测标志
}
},
methods: {
submit() {
if (this.isUploading) {
this.showMessage('warning', '图片正在上传中,请稍等');
return;
}
if (!Array.isArray(this.form.ImgUrl) || this.form.ImgUrl.length === 0) {
this.showMessage('error', '请选择要上传的图片');
return;
}
this.form.inspectionImg = this.joinList();
addRecord(this.form).then(res => {
if (res.code === 200) {
this.showMessage('success', '打卡成功');
} else {
this.showMessage('error', '打卡失败');
}
}).catch(err => {
this.showMessage('error', `提交失败: ${err.message}`);
});
},
// 自定义图片选择方法
chooseImage() {
if (this.isUploading) {
this.showMessage('warning', '图片正在上传中,请稍等');
return;
}
uni.chooseImage({
count: 3 - this.img.length, // 最多选择剩余可选数量
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['camera', 'album'], // 可以指定来源是相册还是相机,默认二者都有
success: (res) => {
// 处理选择的图片
res.tempFilePaths.forEach((filePath, index) => {
this.handleCustomImageUpload(filePath, res.tempFiles[index]);
});
},
fail: (err) => {
console.error('选择图片失败:', err);
this.showMessage('error', '选择图片失败');
}
});
},
// 删除自定义图片
deleteCustomImg(index) {
if (index >= 0 && index < this.img.length) {
// 释放URL对象
if (this.img[index].url && this.img[index].url.startsWith('blob:')) {
URL.revokeObjectURL(this.img[index].url);
}
this.form.ImgUrl.splice(index, 1);
this.img.splice(index, 1);
}
},
// 兼容旧的删除方法
deleteImg(e) {
const index = this.img.findIndex(f => f.path === e.tempFile.path);
if (index !== -1) {
this.deleteCustomImg(index);
}
},
// 兼容旧的上传方法
upload(e) {
if (e.tempFiles && e.tempFiles.length > 0) {
this.handleImageUpload(e.tempFiles[0]);
}
},
// 处理自定义图片上传
async handleCustomImageUpload(filePath, fileInfo) {
this.isUploading = true;
try {
// 将uni.chooseImage返回的文件转换为File对象
const file = await this.convertToFile(filePath, fileInfo);
// 生成水印文字
const watermarkText = `${this.form.inspectionPoint || '未命名地点'} \n${this.getCurrentDate()}`;
// 添加水印
let processedFile = await addWatermarkToImage(file, watermarkText);
// 自动压缩图片(所有图片都进行压缩优化)
processedFile = await this.compressImage(processedFile);
// 上传处理后的文件
const fileUrl = await this.uploadFile(processedFile);
// 更新UI显示
this.img.push({
raw: processedFile,
name: processedFile.name,
url: fileUrl,
path: filePath
});
} catch (error) {
console.error("图片处理失败:", error);
this.showMessage('error', `图片处理失败: ${error.message}`);
} finally {
this.isUploading = false;
}
},
// 将uni.chooseImage的结果转换为File对象
async convertToFile(filePath, fileInfo) {
return new Promise((resolve, reject) => {
// 在uni-app中可以直接使用filePath创建File对象
// 对于H5平台需要特殊处理
// #ifdef H5
const xhr = new XMLHttpRequest();
xhr.open('GET', filePath, true);
xhr.responseType = 'blob';
xhr.onload = function() {
if (xhr.status === 200) {
const blob = xhr.response;
const file = new File([blob], fileInfo.name || 'image.jpg', {
type: fileInfo.type || 'image/jpeg'
});
resolve(file);
} else {
reject(new Error('文件读取失败'));
}
};
xhr.onerror = () => reject(new Error('文件读取失败'));
xhr.send();
// #endif
// #ifndef H5
// 对于非H5平台创建一个模拟的File对象
const file = {
name: fileInfo.name || 'image.jpg',
type: fileInfo.type || 'image/jpeg',
size: fileInfo.size || 0,
path: filePath,
lastModified: Date.now()
};
resolve(file);
// #endif
});
},
cancel() {
uni.reLaunch({
url: '/pages/work/index'
});
},
joinList() {
return this.form.ImgUrl.join(',');
},
// 处理原有的图片上传兼容uni-file-picker
async handleImageUpload(file) {
this.isUploading = true;
try {
if (!file || !file.file) {
throw new Error('无效的文件对象');
}
// 生成水印文字
const watermarkText = `${this.form.inspectionPoint || '未命名地点'} \n${this.getCurrentDate()}`;
// 添加水印
let processedFile = await addWatermarkToImage(file.file, watermarkText);
// 使用新的压缩方法
processedFile = await this.compressImage(processedFile);
// 上传处理后的文件
const fileUrl = await this.uploadFile(processedFile);
// 更新UI显示
this.img.push({
raw: processedFile,
name: processedFile.name,
url: fileUrl
});
} catch (error) {
console.error("图片处理失败:", error);
this.showMessage('error', `图片处理失败: ${error.message}`);
} finally {
this.isUploading = false;
}
},
// 通用图片压缩方法
compressImage(file, quality = 0.7, maxWidth = 1200, maxHeight = 1200) {
return new Promise((resolve, reject) => {
// 如果文件小于200KB直接返回
if (file.size < 200 * 1024) {
console.log('文件较小,无需压缩');
resolve(file);
return;
}
console.log('原始文件大小:', (file.size / 1024).toFixed(2), 'KB');
const img = new Image();
const reader = new FileReader();
reader.onload = (event) => {
img.onload = () => {
// 计算压缩比例
let width = img.width;
let height = img.height;
// 限制最大尺寸
if (width > maxWidth || height > maxHeight) {
const ratio = Math.min(maxWidth / width, maxHeight / height);
width = Math.floor(width * ratio);
height = Math.floor(height * ratio);
}
// 创建canvas
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// 设置更高的压缩质量
ctx.imageSmoothingQuality = 'high';
ctx.drawImage(img, 0, 0, width, height);
// 使用toBlob进行压缩
if (canvas.toBlob) {
canvas.toBlob(
(blob) => {
if (!blob) {
console.warn('压缩失败,返回原始文件');
resolve(file);
return;
}
// 创建压缩后的文件对象
const compressedFile = new File([blob], file.name, {
type: 'image/jpeg',
lastModified: Date.now()
});
console.log('压缩后文件大小:', (compressedFile.size / 1024).toFixed(2), 'KB');
resolve(compressedFile);
},
'image/jpeg',
quality
);
} else {
// 不支持toBlob的浏览器直接返回原文件
console.warn('浏览器不支持toBlob方法无法压缩');
resolve(file);
}
};
img.onerror = () => {
console.warn('图片加载失败,返回原始文件');
resolve(file);
};
img.src = event.target.result;
};
reader.onerror = () => {
console.warn('文件读取失败,返回原始文件');
resolve(file);
};
reader.readAsDataURL(file);
});
},
// 专门为移动浏览器优化的压缩方法(保留兼容性)
compressImageForMobile(file, quality = 0.6, maxWidth = 1200, maxHeight = 1200) {
return new Promise((resolve, reject) => {
const img = new Image();
const reader = new FileReader();
reader.onload = (event) => {
img.onload = () => {
// 计算压缩比例
let width = img.width;
let height = img.height;
// 限制最大尺寸
if (width > maxWidth || height > maxHeight) {
const ratio = Math.min(maxWidth / width, maxHeight / height);
width = Math.floor(width * ratio);
height = Math.floor(height * ratio);
}
// 创建canvas
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// 设置更高的压缩质量
ctx.imageSmoothingQuality = 'high';
ctx.drawImage(img, 0, 0, width, height);
// 使用toBlob的polyfill保证兼容性
if (canvas.toBlob) {
canvas.toBlob(
(blob) => {
if (!blob) {
console.warn('压缩失败,返回原始文件');
resolve(file);
return;
}
// 创建压缩后的文件对象
const compressedFile = new File([blob], file.name, {
type: 'image/jpeg',
lastModified: Date.now()
});
resolve(compressedFile);
},
'image/jpeg',
quality
);
} else {
// 不支持toBlob的浏览器直接返回原文件
console.warn('浏览器不支持toBlob方法无法压缩');
resolve(file);
}
};
img.onerror = () => {
console.warn('图片加载失败,返回原始文件');
resolve(file);
};
img.src = event.target.result;
};
reader.onerror = () => {
console.warn('文件读取失败,返回原始文件');
resolve(file);
};
reader.readAsDataURL(file);
});
},
async uploadFile(file) {
return new Promise((resolve, reject) => {
// 创建预览URL用于显示
const previewUrl = URL.createObjectURL(file);
// 实际上传
uploadImg({
filePath: previewUrl
}).then(res => {
this.form.ImgUrl.push(res.fileName);
resolve(previewUrl);
}).catch(err => {
reject(err);
});
});
},
getCurrentDate() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
},
dialogConfirm() {
if (this.msgType == 'success') {
uni.reLaunch({
url: '/pages/work/index'
});
}
},
showMessage(type, text) {
this.msgType = type;
this.messageText = text;
this.$refs.alertDialog.open();
},
// 检测移动浏览器环境
checkMobileBrowser() {
// 更全面的移动设备检测
const userAgent = navigator.userAgent.toLowerCase();
const isMobile = /iphone|ipod|android|windows phone|mobile|blackberry/.test(userAgent);
// 如果是通过uni-app的web-view打开也认为是移动环境
this.isMobileBrowser = isMobile || window.__uniAppWebview;
}
},
onLoad(option) {
this.form.inspectionPoint = option.inspectionPoint || '';
this.form.inspectionRequirements = option.inspectionRequirements || '';
this.form.inspectionPointId = option.inspectionPointId || '';
},
mounted() {
this.checkMobileBrowser();
}
}
</script>
<style lang="scss">
page {
display: flex;
flex-direction: column;
box-sizing: border-box;
background-color: #fff;
height: 90vh;
/* 使页面高度占满整个视口 */
}
.container {
flex: 1;
/* 让 container 占满剩余空间 */
display: flex;
flex-direction: column;
/* 设置为列方向 */
}
.example {
flex: 1;
/* 让 example 占满 container 的剩余空间 */
display: flex;
flex-direction: column;
/* 设置为列方向,确保子元素垂直排列 */
padding: 15px;
background-color: #fff;
}
// 样式沉底
.button-group {
position: fixed;
bottom: 20px;
left: 0;
/* 使用 margin-top: auto 来将按钮组推到 example 容器的底部 */
display: flex;
width: 100%;
justify-content: space-around;
}
.button-group button {
flex: 1;
background: #fff;
color: #000;
/* 使按钮平分可用空间 */
/* 可能还需要设置一些其他的样式来确保按钮看起来正确,比如 text-align, padding 等 */
}
/* 自定义图片选择器样式 */
.custom-image-picker {
width: 100%;
}
.image-preview-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 10px;
}
.image-preview-item {
position: relative;
width: 80px;
height: 80px;
border-radius: 8px;
overflow: hidden;
border: 1px solid #e0e0e0;
}
.preview-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.delete-btn {
position: absolute;
top: -5px;
right: -5px;
width: 20px;
height: 20px;
background-color: #ff4757;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.delete-icon {
color: white;
font-size: 14px;
font-weight: bold;
line-height: 1;
}
.add-image-btn {
width: 80px;
height: 80px;
border: 2px dashed #c0c0c0;
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
background-color: #fafafa;
transition: all 0.3s ease;
}
.add-image-btn:hover {
border-color: #007aff;
background-color: #f0f8ff;
}
.add-icon {
font-size: 24px;
color: #c0c0c0;
margin-bottom: 4px;
}
.add-text {
font-size: 12px;
color: #999;
}
.image-tip {
font-size: 12px;
color: #666;
text-align: center;
margin-top: 5px;
}
</style>