This commit is contained in:
zpc 2025-08-20 15:09:40 +08:00
parent 3fccba37d3
commit 0482e7af25
4 changed files with 6 additions and 658 deletions

2
.env
View File

@ -1,3 +1,3 @@
# 页面标题
VITE_APP_TITLE = '工作相机'
VITE_APP_TITLE = '随工水印相机'

View File

@ -1,10 +1,10 @@
# 生产环境配置
ENV = 'production'
VITE_APP_API_HOST = 'https://gift.zpc-xy.com/'
VITE_APP_API_HOST = 'https://wc.zpc-xy.com/'
# 生产环境
VITE_APP_BASE_API = 'https://gift.zpc-xy.com/'
VITE_APP_BASE_API = 'https://wc.zpc-xy.com/'
# 路由前缀
VITE_APP_ROUTER_PREFIX = '/'
@ -13,7 +13,7 @@ VITE_APP_ROUTER_PREFIX = '/'
VITE_APP_UPLOAD_URL = 'Common/UploadFile'
#socket API
VITE_APP_SOCKET_API = 'https://gift.zpc-xy.com/msghub'
VITE_APP_SOCKET_API = 'https://wc.zpc-xy.com/msghub'
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip
VITE_BUILD_COMPRESS = gzip

View File

@ -7,7 +7,7 @@
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="/favicon.jpg">
<title>工作相机</title>
<title>随工水印相机后台管理</title>
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
<style>
html,

View File

@ -1,652 +0,0 @@
<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用于水印处理 -->
<canvas canvas-id="watermarkCanvas" style="
position: fixed;
top: -9999px;
left: -9999px;
width: 2000px;
height: 2000px;
"></canvas>
<!-- 弹窗内容 -->
<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">图片预览</text>
<view class="preview-img">
<image :src="imageSrc" @click="handlePreviewImage" 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" />
</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">
<button type="primary" class="btn-cancel" @click="handleRetakePhoto">
重拍
</button>
<button type="primary" class="btn-cancel" @click="handleRetakeCancel">
清空
</button>
<button type="primary" class="btn-submit" @click="handleSaveAndSubmit">
保存本地并提交
</button>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import {
ref
} from "vue";
import {
getLocation,
chooseImage,
addWatermark,
formatDate,
getCachedLogo,
saveImageToPhotosAlbum,
imageToBase64,
} from "../../common/utils";
import {
getLocationTranslate,
getLocationGeocoder,
} from "../../common/mapTranslateResult";
import {
getConfig,
addWatermarkRecord
} from "../../common/server";
import {
onLoad
} from "@dcloudio/uni-app";
// ==================== ====================
//
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 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);
//
const locationInfo = ref("");
const currentTime = ref("");
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();
console.log("图片", image);
originalImageSrc.value = image.tempFilePaths[0];
imageSrc.value = image.tempFilePaths[0];
await addWatermarkToImage();
uni.hideLoading();
popup.value.open();
} catch (error) {
console.log("错误", error);
uni.hideLoading();
}
};
//
const handlePreviewImage = () => {
uni.previewImage({
urls: [imageSrc.value]
});
};
//
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 handleRetakeCancel = async () => {
workers.value.splice(0, workers.value.length);
workers.value.push("");
statusIndex.value = 0;
deptIndex.value = 0;
workContent.value = "";
loadCandidates();
await addWatermarkToImage();
};
var locationData = {};
//
const handleSaveAndSubmit = async () => {
try {
uni.showLoading({
title: "保存中...",
});
//
await addWatermarkToImage();
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(), // 使ISO
};
console.log(saveData);
locationData[workContent.value] = saveData;
uni.setStorageSync("locationData", locationData);
var fromData = {
locations: locations.value,
workContent: workContent.value,
workers: workers.value,
status: statusList.value[statusIndex.value],
dept: departments.value[deptIndex.value],
};
var _remarks = JSON.stringify(locations.value);
var imageBase64 = await imageToBase64(imageSrc.value);
console.log(fromData);
const camRecordWorkDto = {
//
DeptName: departments.value[deptIndex.value],
//
Image: imageBase64,
//
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: workers.value,
};
var res = await addWatermarkRecord(camRecordWorkDto);
console.log(res);
if (res.code != 200) {
uni.hideLoading();
uni.showToast({
title: res.msg,
icon: "error",
});
return;
}
//
if (imageSrc.value) {
await saveImageToPhotosAlbum(imageSrc.value);
}
uni.hideLoading();
uni.showToast({
title: "保存成功",
icon: "success",
});
handleRetakePhoto();
} catch (error) {
console.error("保存失败:", error);
uni.hideLoading();
uni.showToast({
title: "保存失败",
icon: "error",
});
}
};
// ==================== ====================
//
const resetFormData = () => {
imageSrc.value = "";
originalImageSrc.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...",
});
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;
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;
}
.preview-title {
font-size: 32rpx;
font-weight: bold;
}
.preview-img {
margin: 20rpx 0;
width: 100%;
height: 400rpx;
background: #ddd;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.img {
width: 400rpx;
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;
justify-content: space-between;
margin-top: 40rpx;
}
.btn-cancel {
background: #007aff;
color: #fff;
}
.btn-submit {
background: #007aff;
color: #fff;
}
.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>