WorkCamera/uniapp/WorkCameraf/pages/index/index.vue
2026-01-06 21:44:50 +08:00

1538 lines
37 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>
<!-- 主页面内容 -->
<view>
<image class="logo" src="/static/logo.jpg"></image>
<text class="title">{{ title }}</text>
<view class="btn-view">
<button type="primary" class="btn" @click="handleStartCapture">
开始拍摄
</button>
</view>
</view>
<!-- 隐藏的canvas用于水印处理仅App/小程序端使用 -->
<!-- #ifndef H5 -->
<canvas
canvas-id="watermarkCanvas"
style="
position: fixed;
top: -9999px;
left: -9999px;
width: 2000px;
height: 2000px;
"
></canvas>
<!-- #endif -->
<!-- 弹窗内容 -->
<uni-popup
ref="popup"
:is-mask-click="false"
type="bottom"
border-radius="0 0 0 0"
>
<view class="container">
<view style="height: 80rpx"></view>
<!-- 图片列表区域 -->
<view class="preview-box">
<text class="preview-title">图片列表 ({{ imageList.length }}/{{ MAX_IMAGES }})</text>
<!-- 多图预览列表 -->
<view class="multi-preview-container" v-if="imageList.length > 0">
<view
class="preview-item"
:class="{ 'preview-item-selected': currentImageIndex === index }"
v-for="(img, index) in imageList"
:key="index"
@click="handleSelectImage(index)"
>
<image :src="img.watermark" mode="aspectFill" class="preview-thumb"></image>
<view class="delete-btn" @click.stop="removeImage(index)">
<text class="delete-icon">×</text>
</view>
<text class="image-index">{{ index + 1 }}</text>
</view>
<!-- 添加更多图片按钮 -->
<view
class="add-more-btn"
v-if="imageList.length < MAX_IMAGES"
@click="handleAddMorePhoto"
>
<text class="add-icon">+</text>
<text class="add-text">添加</text>
</view>
</view>
</view>
<!-- 当前图片预览区域 -->
<view class="current-preview-box" v-if="imageSrc">
<text class="preview-title">当前图片预览</text>
<!-- 单图预览(显示当前选中的图片) -->
<view class="preview-img">
<image
:src="imageSrc"
@click="handlePreviewCurrentImage"
mode="aspectFit"
class="img"
></image>
</view>
<!-- 图片尺寸和文件大小信息 -->
<view
class="size-info"
v-if="imageSizeInfo.original.width > 0 && false"
>
<text class="size-text">
原图尺寸: {{ imageSizeInfo.original.width }} x
{{ imageSizeInfo.original.height }}
</text>
<text class="size-text" v-if="imageSizeInfo.original.fileSize">
原图大小: {{ imageSizeInfo.original.fileSize.sizeKB }} KB
</text>
<text class="size-text">
水印图片尺寸: {{ imageSizeInfo.watermark.width }} x
{{ imageSizeInfo.watermark.height }}
</text>
<text class="size-text" v-if="imageSizeInfo.watermark.fileSize">
水印图片大小: {{ imageSizeInfo.watermark.fileSize.sizeKB }} KB
</text>
</view>
</view>
<!-- 表单区域 -->
<view class="form-item">
<text class="label">位置:</text>
<input
v-model="locationInfo"
type="text"
class="input worker-input"
disabled
/>
</view>
<!-- 表单区域 -->
<view class="form-item">
<text class="label">请填写工作内容:</text>
<uni-combox
:candidates="candidates"
:clear-able="true"
placeholder="请填写工作内容"
@select="handleComboxSelect"
v-model="workContent"
></uni-combox>
</view>
<view class="form-item">
<text class="label">请选择部门:</text>
<picker :range="departments" @change="handleDeptChange">
<view class="picker">{{ departments[deptIndex] }}</view>
</picker>
</view>
<view class="form-item">
<text class="label">请填写施工人员:</text>
<view
v-for="(worker, index) in workers"
:key="index"
class="worker-row"
>
<input
type="text"
v-model="workers[index]"
class="input worker-input"
/>
<view class="btns">
<button class="btn_f" size="mini" @click="addWorker">+</button>
<button
v-if="workers.length > 0"
class="btn_f"
size="mini"
@click="removeWorker(index)"
>
-
</button>
</view>
</view>
</view>
<view class="form-item">
<text class="label">请选择项目状态:</text>
<picker :range="statusList" @change="handleStatusChange">
<view class="picker">{{ statusList[statusIndex] }}</view>
</picker>
</view>
<!-- 底部操作按钮 -->
<view class="footer">
<view class="footer-row">
<button type="primary" class="btn-action btn-cancel" @click="handleRetakePhoto">
重新开始拍摄
</button>
<button type="primary" class="btn-action btn-continue" @click="handleAddMorePhoto">
继续拍照
</button>
</view>
<view class="footer-row">
<button type="primary" class="btn-action btn-delete" @click="handleDeleteCurrentImage">
删除图片
</button>
<button
type="primary"
class="btn-action btn-save"
@click="handleSaveImage"
:disabled="isSubmitting"
>
保存图片
</button>
<button
type="primary"
class="btn-action btn-submit"
@click="handleSaveAndSubmit"
:disabled="isSubmitting"
>
提交数据
</button>
</view>
<view class="footer-row" v-if="canShare">
<button
type="primary"
class="btn-action btn-share"
@click="handleShareImages"
:disabled="isSubmitting || imageList.length === 0"
>
分享图片
</button>
</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { ref } from "vue";
// import eruda from "eruda";
import {
getLocation,
chooseImage,
addWatermark,
formatDate,
getCachedLogo,
saveImageToPhotosAlbum,
saveImagesToPhotosAlbum,
imageToBase64,
} from "../../common/utils";
import {
getLocationTranslate,
getLocationGeocoder,
} from "../../common/mapTranslateResult";
import { getConfig, addWatermarkRecord, addWatermarkRecordV2, addWatermarkRecordV3 } from "../../common/server";
import { batchUploadToCos } from "../../common/cosUpload";
import { onLoad } from "@dcloudio/uni-app";
// 简单初始化
// window.erudaInstance = eruda.init();
// ==================== 响应式数据 ====================
// 基础数据
const title = ref("随工水印相机");
const candidates = ref([]);
const workContent = ref(""); // 工作内容
const popup = ref(null);
// 位置信息
const locations = ref({
location: {
lat: null,
lng: null,
},
translate: {
lat: null,
lng: null,
locationInfo: null,
},
});
// 图片相关
const imageSrc = ref("");
const originalImageSrc = ref("");
// 多图支持
const imageList = ref([]); // 存储多张图片 [{original, watermark, sizeInfo}]
const MAX_IMAGES = 15; // 最大图片数量
const currentImageIndex = ref(0); // 当前选中的图片索引
const imageSizeInfo = ref({
original: {
width: 0,
height: 0,
fileSize: null,
},
watermark: {
width: 0,
height: 0,
fileSize: null,
},
});
// 表单数据
const departments = ref(["请选择"]);
const deptIndex = ref(0);
const workers = ref([""]);
const statusList = ref([]);
const statusIndex = ref(0);
var locationData = new Map();
// 其他信息
const locationInfo = ref("");
const currentTime = ref("");
const isSubmitting = ref(false); // 防抖状态
const canShare = ref(false); // 是否支持 Web Share API
let logo = ""; // 非响应式数据
// ==================== 事件处理函数 ====================
// 开始拍摄
const handleStartCapture = async () => {
uni.showLoading({
title: "加载中。。。",
});
try {
// 获取位置信息
const _locationData = await getLocation();
console.log("系统坐标", _locationData);
const location = _locationData.latitude + "," + _locationData.longitude;
console.log("经纬度", location);
const translate = await getLocationTranslate(location);
console.log("转换后", translate);
const t_location = translate.lat + "," + translate.lng;
const geocoderResult = await getLocationGeocoder(t_location);
console.log("地理编码结果", geocoderResult);
// 保存位置信息
locations.value.location.lat = _locationData.latitude;
locations.value.location.lng = _locationData.longitude;
locations.value.translate.lat = translate.lat;
locations.value.translate.lng = translate.lng;
locations.value.translate.locationInfo = geocoderResult || "未知位置";
locationInfo.value = geocoderResult || "未知位置";
currentTime.value = new Date();
// 选择图片(支持多选)
const image = await chooseImage(9); // 最多选择9张
console.log("图片", image);
// 处理多张图片
for (let i = 0; i < image.tempFilePaths.length && imageList.value.length < MAX_IMAGES; i++) {
const imgPath = image.tempFilePaths[i];
await addImageToList(imgPath);
}
// 设置当前显示的图片为第一张
if (imageList.value.length > 0) {
imageSrc.value = imageList.value[0].watermark;
originalImageSrc.value = imageList.value[0].original;
}
uni.hideLoading();
popup.value.open();
} catch (error) {
console.log("错误", error);
uni.hideLoading();
uni.showToast({
title: "获取位置失败",
icon: "error",
});
}
};
// 添加更多图片
const handleAddMorePhoto = async () => {
if (imageList.value.length >= MAX_IMAGES) {
uni.showToast({
title: `最多只能拍摄${MAX_IMAGES}张图片`,
icon: "none",
});
return;
}
try {
const remainingSlots = MAX_IMAGES - imageList.value.length;
const image = await chooseImage(Math.min(remainingSlots, 9));
uni.showLoading({
title: "处理中...",
});
for (let i = 0; i < image.tempFilePaths.length && imageList.value.length < MAX_IMAGES; i++) {
const imgPath = image.tempFilePaths[i];
await addImageToList(imgPath);
}
uni.hideLoading();
} catch (error) {
console.log("添加图片失败:", error);
uni.hideLoading();
}
};
// 添加图片到列表(带水印处理)
const addImageToList = async (imgPath) => {
const watermarkInfo = {
time: formatDate(currentTime.value),
location: locationInfo.value,
longitude: locations.value.translate.lng,
latitude: locations.value.translate.lat,
department: departments.value[deptIndex.value],
workers: workers.value.filter((worker) => worker.trim() !== ""),
status: statusList.value[statusIndex.value],
remarks: workContent.value,
};
const watermarkResult = await addWatermark(imgPath, watermarkInfo, logo, 1);
imageList.value.push({
original: imgPath,
watermark: watermarkResult.filePath,
sizeInfo: {
original: watermarkResult.originalSize,
watermark: watermarkResult.watermarkSize,
},
});
};
// 删除图片(带二次确认弹窗)
const removeImage = (index) => {
uni.showModal({
title: '提示',
content: '确定要删除该图片吗?',
confirmText: '确定',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
// 用户点击确定,执行删除
if (imageList.value.length > index) {
imageList.value.splice(index, 1);
// 更新当前显示的图片
if (imageList.value.length > 0) {
const newIndex = Math.min(index, imageList.value.length - 1);
currentImageIndex.value = newIndex;
imageSrc.value = imageList.value[newIndex].watermark;
originalImageSrc.value = imageList.value[newIndex].original;
} else {
currentImageIndex.value = 0;
imageSrc.value = "";
originalImageSrc.value = "";
}
}
}
// 用户点击取消,关闭弹窗,不做任何操作
}
});
};
// 删除当前选中的图片(底部按钮调用)
const handleDeleteCurrentImage = () => {
if (imageList.value.length === 0) {
uni.showToast({
title: '没有可删除的图片',
icon: 'none'
});
return;
}
removeImage(currentImageIndex.value);
};
// 选中指定索引的图片(点击缩略图)
const handleSelectImage = (index) => {
currentImageIndex.value = index;
imageSrc.value = imageList.value[index].watermark;
originalImageSrc.value = imageList.value[index].original;
};
// 预览指定索引的图片(长按或双击可用)
const handlePreviewImage = (index) => {
const urls = imageList.value.map((img) => img.watermark);
uni.previewImage({
urls: urls,
current: index,
});
};
// 预览当前图片
const handlePreviewCurrentImage = () => {
if (imageList.value.length > 0) {
const urls = imageList.value.map((img) => img.watermark);
uni.previewImage({
urls: urls,
});
} else if (imageSrc.value) {
uni.previewImage({
urls: [imageSrc.value],
});
}
};
// #ifdef H5
// 将图片 URL 转换为 File 对象
const urlToFile = async (url, filename) => {
const response = await fetch(url);
const blob = await response.blob();
return new File([blob], filename, { type: blob.type || 'image/jpeg' });
};
// 分享图片H5 环境使用 Web Share API
const handleShareImages = async () => {
if (imageList.value.length === 0) {
uni.showToast({
title: '没有可分享的图片',
icon: 'none'
});
return;
}
if (isSubmitting.value) {
return;
}
isSubmitting.value = true;
try {
uni.showLoading({
title: '准备分享...',
});
// 先刷新所有图片的水印,确保包含最新的工作内容等信息
await refreshAllWatermarks();
// 将所有水印图片转换为 File 对象
const files = [];
for (let i = 0; i < imageList.value.length; i++) {
const img = imageList.value[i];
const filename = `工作照片_${i + 1}.jpg`;
const file = await urlToFile(img.watermark, filename);
files.push(file);
}
// 构建分享文本
let shareText = '';
if (workContent.value) {
shareText += `工作内容:${workContent.value}\n`;
}
if (locationInfo.value) {
shareText += `位置:${locationInfo.value}\n`;
}
if (departments.value[deptIndex.value] && deptIndex.value > 0) {
shareText += `部门:${departments.value[deptIndex.value]}\n`;
}
const validWorkers = workers.value.filter(w => w.trim() !== '');
if (validWorkers.length > 0) {
shareText += `施工人员:${validWorkers.join('、')}\n`;
}
shareText += `${files.length} 张照片`;
// 检查是否支持分享文件
const shareData = {
title: '工作照片分享',
text: shareText,
files: files
};
if (!navigator.canShare(shareData)) {
// 如果不支持分享文件,尝试只分享文本
uni.hideLoading();
uni.showToast({
title: '当前浏览器不支持分享图片',
icon: 'none'
});
return;
}
uni.hideLoading();
// 调用系统分享
await navigator.share(shareData);
uni.showToast({
title: '分享成功',
icon: 'success'
});
} catch (error) {
console.error('分享失败:', error);
uni.hideLoading();
// 用户取消分享不提示错误
if (error.name !== 'AbortError') {
uni.showToast({
title: '分享失败',
icon: 'none'
});
}
} finally {
setTimeout(() => {
isSubmitting.value = false;
}, 500);
}
};
// #endif
// #ifdef APP-PLUS
// App 端分享图片(调用系统分享)
const handleShareImages = async () => {
if (imageList.value.length === 0) {
uni.showToast({
title: '没有可分享的图片',
icon: 'none'
});
return;
}
if (isSubmitting.value) {
return;
}
isSubmitting.value = true;
try {
uni.showLoading({
title: '准备分享...',
});
// 先刷新所有图片的水印,确保包含最新的工作内容等信息
await refreshAllWatermarks();
// 收集所有水印图片路径
const imagePaths = imageList.value.map(img => img.watermark);
// 构建分享文本
let shareText = '';
if (workContent.value) {
shareText += `工作内容:${workContent.value}\n`;
}
if (locationInfo.value) {
shareText += `位置:${locationInfo.value}\n`;
}
if (departments.value[deptIndex.value] && deptIndex.value > 0) {
shareText += `部门:${departments.value[deptIndex.value]}\n`;
}
const validWorkers = workers.value.filter(w => w.trim() !== '');
if (validWorkers.length > 0) {
shareText += `施工人员:${validWorkers.join('、')}\n`;
}
shareText += `${imagePaths.length} 张照片`;
uni.hideLoading();
// 使用 plus.share 调用系统分享
plus.share.sendWithSystem({
type: 'image',
pictures: imagePaths,
content: shareText
}, () => {
console.log('分享成功');
uni.showToast({
title: '分享成功',
icon: 'success'
});
}, (err) => {
console.error('分享失败:', err);
// 用户取消分享不提示错误
if (err.code !== -2) {
uni.showToast({
title: '分享失败',
icon: 'none'
});
}
});
} catch (error) {
console.error('分享失败:', error);
uni.hideLoading();
uni.showToast({
title: '分享失败',
icon: 'none'
});
} finally {
setTimeout(() => {
isSubmitting.value = false;
}, 500);
}
};
// #endif
// 表单事件处理
const handleComboxSelect = async (value) => {
console.log("选择的工作内容:", value);
if (locationData[value] != null) {
var tempData = locationData[value];
// 设置部门
if (tempData.dept && departments.value.includes(tempData.dept)) {
deptIndex.value = departments.value.indexOf(tempData.dept);
}
// 设置项目状态
if (tempData.status && statusList.value.includes(tempData.status)) {
statusIndex.value = statusList.value.indexOf(tempData.status);
}
// 设置施工人员
if (tempData.workers && Array.isArray(tempData.workers)) {
workers.value.splice(0, workers.value.length);
workers.value.push(...tempData.workers);
}
await addWatermarkToImage(value);
console.log("加载的历史数据:", tempData);
}
};
const handleDeptChange = (e) => {
deptIndex.value = e.detail.value;
};
const handleStatusChange = (e) => {
statusIndex.value = e.detail.value;
};
// 施工人员管理
const addWorker = () => {
workers.value.push("");
};
const removeWorker = (index) => {
if (workers.value.length > 1) {
workers.value.splice(index, 1);
}
};
// ==================== 业务逻辑函数 ====================
// 添加水印
const addWatermarkToImage = async (work = "") => {
try {
const watermarkInfo = {
time: formatDate(currentTime.value),
location: locationInfo.value,
longitude: locations.value.translate.lng,
latitude: locations.value.translate.lat,
department: departments.value[deptIndex.value],
workers: workers.value.filter((worker) => worker.trim() !== ""),
status: statusList.value[statusIndex.value],
remarks: workContent.value,
};
if (workContent.value == "" && work != "") {
watermarkInfo.remarks = work;
}
console.log("水印信息:", watermarkInfo);
console.log("原图路径:", originalImageSrc.value);
const watermarkResult = await addWatermark(
originalImageSrc.value,
watermarkInfo,
logo,
1
);
console.log("水印结果:", watermarkResult);
imageSrc.value = watermarkResult.filePath;
imageSizeInfo.value = {
original: watermarkResult.originalSize,
watermark: watermarkResult.watermarkSize,
};
} catch (error) {
console.error("添加水印失败:", error);
uni.showToast({
title: "添加水印失败",
icon: "error",
});
}
};
// 重新拍照
const handleRetakePhoto = () => {
popup.value.close();
resetFormData();
};
// 保存图片(仅生成水印并保存到相册)
const handleSaveImage = async () => {
// 防抖检查
if (isSubmitting.value) {
return;
}
// 基本验证
if (!validateFormData(false)) {
return;
}
isSubmitting.value = true;
try {
uni.showLoading({
title: "提交数据中...",
});
// 先添加水印
await addWatermarkToImage();
// 保存水印图片到相册
if (imageSrc.value) {
await saveImageToPhotosAlbum(imageSrc.value);
}
uni.hideLoading();
uni.showToast({
title: "图片保存成功",
icon: "success",
});
} catch (error) {
console.error("保存图片失败:", error);
uni.hideLoading();
uni.showToast({
title: "保存图片失败",
icon: "error",
});
} finally {
// 延迟重置状态,防止快速重复点击
setTimeout(() => {
isSubmitting.value = false;
}, 1000);
}
};
const handleRetakeCancel = async () => {
workers.value.splice(0, workers.value.length);
workers.value.push("");
statusIndex.value = 0;
deptIndex.value = 0;
workContent.value = "";
imageList.value = []; // 清空图片列表
loadCandidates();
await refreshAllWatermarks();
};
// 刷新所有图片的水印
const refreshAllWatermarks = async () => {
if (imageList.value.length === 0) return;
uni.showLoading({ title: "处理中..." });
const watermarkInfo = {
time: formatDate(currentTime.value),
location: locationInfo.value,
longitude: locations.value.translate.lng,
latitude: locations.value.translate.lat,
department: departments.value[deptIndex.value],
workers: workers.value.filter((worker) => worker.trim() !== ""),
status: statusList.value[statusIndex.value],
remarks: workContent.value,
};
for (let i = 0; i < imageList.value.length; i++) {
const img = imageList.value[i];
const watermarkResult = await addWatermark(img.original, watermarkInfo, logo, 1);
imageList.value[i].watermark = watermarkResult.filePath;
imageList.value[i].sizeInfo = {
original: watermarkResult.originalSize,
watermark: watermarkResult.watermarkSize,
};
}
// 更新当前显示的图片
if (imageList.value.length > 0) {
imageSrc.value = imageList.value[0].watermark;
}
uni.hideLoading();
};
// 回退到 V2 Base64 上传(当 COS 上传失败时使用)
const fallbackToV2Upload = async (_remarks, validWorkers) => {
try {
uni.showLoading({
title: "转换图片中...",
});
// 转换所有图片为 Base64
const imagesBase64 = [];
for (const img of imageList.value) {
const base64 = await imageToBase64(img.watermark);
imagesBase64.push(base64);
}
console.log(`回退到 V2 上传,共${imagesBase64.length}张图片`);
uni.showLoading({
title: "提交数据中...",
});
const camRecordWorkDto = {
// 部门名称
DeptName: departments.value[deptIndex.value],
// 图片地址(第一张作为封面,兼容旧版)
Image: imagesBase64[0],
// 多图支持
Images: imagesBase64,
// 工作记录时间
RecordTime: formatDate(currentTime.value),
// 经度
Longitude: locations.value.translate.lng,
// 纬度
Latitude: locations.value.translate.lat,
// 工作地点
Address: locationInfo.value,
// 工作内容
Content: workContent.value,
// 状态
StatusName: statusList.value[statusIndex.value],
// 备注
Remarks: _remarks,
// 工作人员列表
Workers: validWorkers,
};
// 使用 v2 API 支持多图
var res = await addWatermarkRecordV2(camRecordWorkDto);
console.log("V2 API 响应:", res);
if (res.code != 200) {
uni.hideLoading();
uni.showToast({
title: res.msg || "保存失败",
icon: "error",
});
return;
}
uni.hideLoading();
uni.showToast({
title: "提交成功",
icon: "success",
});
// 保存所有水印图片到相册
try {
if (imageList.value.length > 0) {
const watermarkPaths = imageList.value.map(img => img.watermark);
await saveImagesToPhotosAlbum(watermarkPaths);
}
} catch (saveError) {
console.error("保存图片到相册失败:", saveError);
}
handleRetakePhoto();
} catch (error) {
console.error("V2 上传失败:", error);
uni.hideLoading();
uni.showToast({
title: "保存失败",
icon: "error",
});
} finally {
setTimeout(() => {
isSubmitting.value = false;
}, 1000);
}
};
// 保存并提交
const handleSaveAndSubmit = async () => {
// 防抖检查
if (isSubmitting.value) {
return;
}
// 参数验证
if (!validateFormData(true)) {
return;
}
// 检查是否有图片
if (imageList.value.length === 0) {
uni.showToast({
title: "请先拍摄图片",
icon: "error",
});
return;
}
isSubmitting.value = true;
try {
uni.showLoading({
title: "保存中...",
});
// 刷新所有图片的水印(确保最新信息)
await refreshAllWatermarks();
// 保存本地历史数据
var saveData = {
workContent: workContent.value,
workers: workers.value.filter((worker) => worker.trim() !== ""),
status: statusList.value[statusIndex.value],
dept: departments.value[deptIndex.value],
date: new Date().toISOString(),
};
console.log("locationData", locationData);
locationData[workContent.value] = saveData;
uni.setStorageSync("locationData", locationData);
var _remarks = JSON.stringify(locations.value);
const validWorkers = workers.value.filter((worker) => worker.trim() !== "");
// 收集所有水印图片路径
const watermarkPaths = imageList.value.map(img => img.watermark);
console.log(`${watermarkPaths.length}张图片,开始上传到 COS...`);
// 使用 COS 直传
uni.showLoading({
title: "上传图片中...",
});
let imageUrls;
try {
imageUrls = await batchUploadToCos(
watermarkPaths,
{
recordTime: currentTime.value,
deptName: departments.value[deptIndex.value],
content: workContent.value,
workers: validWorkers
},
(stage, completed, total) => {
if (stage === "getting_urls") {
uni.showLoading({ title: "获取上传地址..." });
} else if (stage === "uploading") {
uni.showLoading({ title: `上传中 ${completed}/${total}...` });
}
}
);
} catch (cosError) {
console.error("COS 上传失败,回退到 Base64 上传:", cosError);
// COS 上传失败,回退到 V2 Base64 上传
uni.showLoading({ title: "切换上传方式..." });
await fallbackToV2Upload(_remarks, validWorkers);
return;
}
console.log("COS 上传成功,图片 URL:", imageUrls);
// 使用 V3 API 保存记录
uni.showLoading({
title: "保存记录中...",
});
const camRecordWorkV3Dto = {
// 部门名称
DeptName: departments.value[deptIndex.value],
// 工作记录时间
RecordTime: formatDate(currentTime.value),
// 经度
Longitude: String(locations.value.translate.lng),
// 纬度
Latitude: String(locations.value.translate.lat),
// 工作地点
Address: locationInfo.value,
// 工作内容
Content: workContent.value,
// 状态
StatusName: statusList.value[statusIndex.value],
// 备注
Remarks: _remarks,
// 工作人员列表
Workers: validWorkers,
// COS 图片 URL 列表
ImageUrls: imageUrls,
};
var res = await addWatermarkRecordV3(camRecordWorkV3Dto);
console.log("V3 API 响应:", res);
if (res.code != 200) {
uni.hideLoading();
uni.showToast({
title: res.msg || "保存失败",
icon: "error",
});
return;
}
uni.hideLoading();
uni.showToast({
title: "提交成功",
icon: "success",
});
} catch (error) {
console.error("保存失败:", error);
uni.hideLoading();
uni.showToast({
title: "保存失败",
icon: "error",
});
} finally {
// 延迟重置状态,防止快速重复点击
setTimeout(() => {
isSubmitting.value = false;
}, 1000);
}
try {
// 保存所有水印图片到相册
if (imageList.value.length > 0) {
const watermarkPaths = imageList.value.map(img => img.watermark);
const saveResult = await saveImagesToPhotosAlbum(watermarkPaths);
console.log(`保存到相册完成: 成功${saveResult.success}张, 失败${saveResult.failed}`);
}
} catch (error) {
console.error("保存图片失败", error);
uni.hideLoading();
uni.showToast({
title: "保存图片失败",
icon: "error",
});
}
handleRetakePhoto();
};
// ==================== 工具函数 ====================
// 验证表单数据
const validateFormData = (isSubmit = false) => {
// 基本验证 - 位置信息
if (!locationInfo.value || locationInfo.value.trim() === "") {
uni.showToast({
title: "位置信息不能为空",
icon: "error",
});
return false;
}
// 如果是提交操作,需要更严格的验证
if (isSubmit) {
if (!workContent.value || workContent.value.trim() === "") {
uni.showToast({
title: "请填写工作内容",
icon: "error",
});
return false;
}
if (deptIndex.value === 0) {
uni.showToast({
title: "请选择部门",
icon: "error",
});
return false;
}
const validWorkers = workers.value.filter((worker) => worker.trim() !== "");
if (validWorkers.length === 0) {
uni.showToast({
title: "请填写施工人员",
icon: "error",
});
return false;
}
// if (statusIndex.value === 0) {
// uni.showToast({
// title: "请选择项目状态",
// icon: "error",
// });
// return false;
// }
}
return true;
};
// 重置表单数据
const resetFormData = () => {
imageSrc.value = "";
originalImageSrc.value = "";
imageList.value = []; // 清空图片列表
locationInfo.value = "";
currentTime.value = "";
imageSizeInfo.value = {
original: {
width: 0,
height: 0,
fileSize: null,
},
watermark: {
width: 0,
height: 0,
fileSize: null,
},
};
workers.value.splice(0, workers.value.length);
workers.value.push("");
statusIndex.value = 0;
deptIndex.value = 0;
workContent.value = "";
loadCandidates();
};
const loadCandidates = () => {
candidates.value.splice(0, candidates.value.length);
// 将对象转换为数组并按时间倒序排序
const sortedEntries = Object.entries(locationData)
.filter(([key, data]) => data && data.date) // 过滤掉没有时间字段的数据
.sort((a, b) => {
const dateA = new Date(a[1].date);
const dateB = new Date(b[1].date);
return dateB - dateA; // 倒序排列(最新的在前)
});
// 提取排序后的键名
sortedEntries.forEach(([key, data]) => {
console.log(`工作内容: ${key}, 时间: ${data.date}`);
candidates.value.push(key);
});
};
// ==================== 生命周期 ====================
onLoad(async () => {
uni.showLoading({
title: "loading...",
});
// #ifdef H5
// 检测 Web Share API 支持
if (navigator.share && navigator.canShare) {
canShare.value = true;
}
// #endif
// #ifdef APP-PLUS
// App 端始终支持系统分享
canShare.value = true;
// #endif
try {
const config = await getConfig();
console.log("配置", config);
logo = config.logo;
departments.value.push(...config.deptList);
statusList.value.push(...config.construction);
// 缓存logo
await getCachedLogo(logo);
var _locationData = uni.getStorageSync("locationData");
if (_locationData != null && _locationData != "") {
locationData = _locationData;
loadCandidates();
}
} catch (error) {
console.error("初始化失败:", error);
} finally {
uni.hideLoading();
}
});
</script>
<style>
.logo {
height: 200px;
width: 200px;
margin: 0 auto;
position: absolute;
top: 25%;
left: 50%;
transform: translate(-50%, -50%);
}
.btn {
width: 100px;
margin: 0 auto;
position: absolute;
bottom: 22%;
left: 50%;
transform: translate(-50%, -50%);
}
.title {
margin: 0 auto;
font-size: 20px;
font-weight: 700;
color: #000;
text-align: center;
position: absolute;
top: 35%;
left: 50%;
transform: translate(-50%, -50%);
}
.container {
padding: 20rpx;
background: #fff;
height: 100vh;
overflow: auto;
}
.preview-box {
background: #fff;
padding: 20rpx;
margin-bottom: 20rpx;
text-align: center;
border-bottom: 1rpx solid #eee;
}
/* 当前图片预览区域 */
.current-preview-box {
background: #fff;
padding: 20rpx;
margin-bottom: 20rpx;
text-align: center;
}
.preview-title {
font-size: 32rpx;
font-weight: bold;
}
/* 多图预览容器 */
.multi-preview-container {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
margin: 20rpx 0;
padding: 16rpx;
background: #f5f5f5;
border-radius: 12rpx;
justify-content: flex-start;
}
.preview-item {
position: relative;
width: 140rpx;
height: 140rpx;
border-radius: 8rpx;
overflow: hidden;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
border: 4rpx solid transparent;
box-sizing: border-box;
}
/* 选中状态 */
.preview-item-selected {
border: 4rpx solid #007aff;
box-shadow: 0 4rpx 12rpx rgba(0, 122, 255, 0.4);
}
.preview-thumb {
width: 100%;
height: 100%;
object-fit: cover;
}
.delete-btn {
position: absolute;
top: 4rpx;
right: 4rpx;
width: 36rpx;
height: 36rpx;
background: rgba(255, 0, 0, 0.8);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
}
.delete-icon {
color: #fff;
font-size: 24rpx;
font-weight: bold;
line-height: 1;
}
.image-index {
position: absolute;
bottom: 4rpx;
left: 4rpx;
background: rgba(0, 0, 0, 0.6);
color: #fff;
font-size: 20rpx;
padding: 2rpx 8rpx;
border-radius: 8rpx;
}
.add-more-btn {
width: 140rpx;
height: 140rpx;
border: 2rpx dashed #007aff;
border-radius: 8rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: #f0f8ff;
}
.add-icon {
font-size: 48rpx;
color: #007aff;
font-weight: bold;
}
.add-text {
font-size: 20rpx;
color: #007aff;
margin-top: 4rpx;
}
.preview-img {
margin: 20rpx 0;
width: 100%;
height: 400rpx;
background: #ddd;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.img {
max-width: 100%;
max-height: 400rpx;
}
.preview-info {
font-size: 26rpx;
color: #666;
text-align: left;
margin-top: 10rpx;
}
.form-item {
margin-bottom: 20rpx;
}
.label {
font-size: 28rpx;
margin-bottom: 10rpx;
display: block;
}
.input {
border: 1px solid #ddd;
border-radius: 8rpx;
padding: 10rpx;
background: #fff;
}
.picker {
border: 1px solid #ddd;
border-radius: 8rpx;
padding: 10rpx;
background: #fff;
}
.worker-row {
display: flex;
align-items: center;
margin-bottom: 10rpx;
}
.worker-input {
flex: 1;
}
.btns {
display: flex;
margin-left: 10rpx;
}
.btn_f {
margin-left: 5rpx;
}
.footer {
display: flex;
flex-direction: column;
gap: 20rpx;
margin-top: 40rpx;
padding-bottom: 40rpx;
}
.footer-row {
display: flex;
justify-content: space-between;
gap: 16rpx;
}
.btn-action {
flex: 1;
font-size: 26rpx;
padding: 16rpx 0;
margin: 0;
}
.btn-cancel {
background: #007aff;
color: #fff;
}
.btn-continue {
background: #17a2b8;
color: #fff;
}
.btn-delete {
background: #dc3545;
color: #fff;
}
.btn-save {
background: #28a745;
color: #fff;
}
.btn-submit {
background: #007aff;
color: #fff;
}
.btn-share {
background: #ff9500;
color: #fff;
}
.btn-save:disabled,
.btn-submit:disabled,
.btn-share:disabled,
.btn-action:disabled {
background: #ccc !important;
color: #999 !important;
opacity: 0.6;
}
.watermark-btn {
margin-top: 20rpx;
background: #28a745;
color: #fff;
font-size: 28rpx;
padding: 10rpx 30rpx;
border-radius: 8rpx;
}
.size-info {
margin-top: 20rpx;
padding: 20rpx;
background: #f8f9fa;
border-radius: 8rpx;
}
.size-text {
display: block;
font-size: 24rpx;
color: #666;
margin-bottom: 10rpx;
text-align: center;
}
</style>