This commit is contained in:
zpc 2025-12-27 15:07:12 +08:00
parent 7285dd64f8
commit c640b911b1
12 changed files with 1365 additions and 81 deletions

View File

@ -34,14 +34,16 @@ namespace ZR.Admin.WebApi.Controllers.Business
private OptionsSetting OptionsSetting;
private IWebHostEnvironment WebHostEnvironment;
private readonly ICamWorkerService _CamWorkerService;
private readonly ICamWorkrecordImageService _CamWorkrecordImageService;
public CamWorkrecordController(ICamWorkrecordService CamWorkrecordService, IOptions<OptionsSetting> options, IWebHostEnvironment webHostEnvironment,
ICamWorkerService camWorkerService)
ICamWorkerService camWorkerService, ICamWorkrecordImageService camWorkrecordImageService)
{
_CamWorkrecordService = CamWorkrecordService;
OptionsSetting = options.Value;
WebHostEnvironment = webHostEnvironment;
_CamWorkerService = camWorkerService;
_CamWorkrecordImageService = camWorkrecordImageService;
}
/// <summary>
@ -54,6 +56,37 @@ namespace ZR.Admin.WebApi.Controllers.Business
public IActionResult QueryCamWorkrecord([FromQuery] CamWorkrecordQueryDto parm)
{
var response = _CamWorkrecordService.GetList(parm);
// 获取所有记录的图片
var ids = response.Result.Select(x => x.Id).ToList();
if (ids.Count > 0)
{
var allImages = _CamWorkrecordImageService.GetListByWorkrecordIds(ids);
var imagesByRecordId = allImages.GroupBy(x => x.WorkrecordId)
.ToDictionary(g => g.Key, g => g.OrderBy(x => x.SortOrder).ToList());
response.Result.ForEach(item =>
{
if (imagesByRecordId.TryGetValue(item.Id, out var imgs))
{
item.Images = imgs.Select(x => new CamWorkrecordImageDto
{
Id = x.Id,
WorkrecordId = x.WorkrecordId,
ImageUrl = x.ImageUrl,
SortOrder = x.SortOrder,
CreateTime = x.CreateTime
}).ToList();
item.ImageCount = imgs.Count;
}
else
{
item.Images = new List<CamWorkrecordImageDto>();
item.ImageCount = 0;
}
});
}
return SUCCESS(response);
}
@ -74,6 +107,18 @@ namespace ZR.Admin.WebApi.Controllers.Business
{
var names = _CamWorkerService.AsQueryable().Where(it => it.WorkrecordId == info.Id).Select(it => it.WorkerName).ToList();
info.Worker = names != null && names.Count > 0 ? string.Join(",", names) : "";
// 获取图片列表
var imageRecords = _CamWorkrecordImageService.GetListByWorkrecordId(info.Id);
info.Images = imageRecords.Select(x => new CamWorkrecordImageDto
{
Id = x.Id,
WorkrecordId = x.WorkrecordId,
ImageUrl = x.ImageUrl,
SortOrder = x.SortOrder,
CreateTime = x.CreateTime
}).ToList();
info.ImageCount = imageRecords.Count;
}
return SUCCESS(info);
}
@ -141,9 +186,17 @@ namespace ZR.Admin.WebApi.Controllers.Business
}
var url = OptionsSetting.Upload.UploadUrl;
string savePath = Path.Combine(WebHostEnvironment.WebRootPath);
// 获取所有记录的图片
var ids = list.Select(x => x.Id).ToList();
var allImages = _CamWorkrecordImageService.GetListByWorkrecordIds(ids);
var imagesByRecordId = allImages.GroupBy(x => x.WorkrecordId)
.ToDictionary(g => g.Key, g => g.OrderBy(x => x.SortOrder).Select(x => x.ImageUrl).ToList());
int i = 1;
list.ForEach(it =>
{
// 处理主图(兼容旧数据)
var temp = string.IsNullOrEmpty(it.ImageUrl) ? "" : it.ImageUrl;
if (temp.IndexOf("http") > -1)
{
@ -152,20 +205,26 @@ namespace ZR.Admin.WebApi.Controllers.Business
}
it.Index = i;
it.ImageUrl = temp;
//it.Image = CompressImage(temp, 50);
i++;
});
ExcelHelper excelHelper = new ExcelHelper(WebHostEnvironment);
ExcelPackage.License.SetNonCommercialPersonal("pnaa");
var result = ExportWorkRecordExcel(list, "工作记录", 200, 70, 60);
//ExportExcelMini(list, "工作记录", "工作记录");
//ExportExcelWithImagesInBatches(result.Item2, "工作记录", urlList, 10);
//loadExcel();
var result = ExportWorkRecordExcel(list, imagesByRecordId, url, savePath, "工作记录", 100, 70, 60);
return ExportExcel(result.Item2, result.Item1);
}
/// <summary>
public (string fileName, string fullPath) ExportWorkRecordExcel(List<CamWorkrecordExcelDto> list, string fileName, int imageWidth = 100, int imageHeight = 60, int imageQuality = 50)
/// 导出工作记录Excel支持多图
/// </summary>
public (string fileName, string fullPath) ExportWorkRecordExcel(
List<CamWorkrecordExcelDto> list,
Dictionary<int, List<string>> imagesByRecordId,
string uploadUrl,
string savePath,
string fileName,
int imageWidth = 100,
int imageHeight = 60,
int imageQuality = 50)
{
IWebHostEnvironment webHostEnvironment = (IWebHostEnvironment)App.ServiceProvider.GetService(typeof(IWebHostEnvironment));
string sFileName = $"{fileName}_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx";
@ -185,11 +244,8 @@ namespace ZR.Admin.WebApi.Controllers.Business
{
ws.Cells[1, i + 1].Value = headers[i];
ws.Cells[1, i + 1].Style.Font.Bold = true;
//ws.Column(i).Width = 120;
}
//ws.Column(10).Width = 40;
int row = 2;
foreach (var item in list)
{
@ -205,26 +261,62 @@ namespace ZR.Admin.WebApi.Controllers.Business
ws.Cells[row, 11].Value = item.CreateTime?.ToString("yyyy-MM-dd HH:mm:ss");
ws.Cells[row, 12].Value = item.UpdateTime?.ToString("yyyy-MM-dd HH:mm:ss");
// 如果 ImageUrl 有值,则压缩后插入 Excel
if (!string.IsNullOrEmpty(item.ImageUrl) && IOFile.Exists(item.ImageUrl))
// 获取该记录的所有图片
var imageUrls = new List<string>();
if (imagesByRecordId.TryGetValue(item.Id, out var imgs) && imgs.Count > 0)
{
using var image = SixLabors.ImageSharp.Image.Load(item.ImageUrl);
var encoder = new JpegEncoder { Quality = imageQuality };
using var ms = new MemoryStream();
image.Save(ms, encoder);
ms.Position = 0;
var pic = ws.Drawings.AddPicture($"Pic_{row}_10", ms);
pic.SetPosition(row - 1, 5, 9, 5); // 第10列
pic.SetSize(imageWidth, imageHeight);
imageUrls = imgs;
}
else if (!string.IsNullOrEmpty(item.ImageUrl))
{
// 兼容旧数据使用主表的ImageUrl
imageUrls.Add(item.ImageUrl.Contains("http") ? item.ImageUrl : "");
}
// 插入多张图片(水平排列)
int picIndex = 0;
int totalImageWidth = 0;
foreach (var imgUrl in imageUrls)
{
var localPath = imgUrl;
if (imgUrl.Contains("http"))
{
localPath = savePath + imgUrl.Replace(uploadUrl, "");
}
if (!string.IsNullOrEmpty(localPath) && IOFile.Exists(localPath))
{
try
{
using var image = SixLabors.ImageSharp.Image.Load(localPath);
var encoder = new JpegEncoder { Quality = imageQuality };
using var ms = new MemoryStream();
image.Save(ms, encoder);
ms.Position = 0;
var pic = ws.Drawings.AddPicture($"Pic_{row}_{picIndex}", ms);
// 水平排列:每张图片偏移 imageWidth 像素
int pixelOffset = picIndex * (imageWidth + 5);
pic.SetPosition(row - 1, 5, 9, 5 + pixelOffset);
pic.SetSize(imageWidth, imageHeight);
totalImageWidth = (picIndex + 1) * (imageWidth + 5);
picIndex++;
}
catch
{
// 忽略无法加载的图片
}
}
}
ws.Row(row).Height = imageHeight;
row++;
}
ws.Cells[ws.Dimension.Address].AutoFitColumns();
ws.Cells[ws.Dimension.Address].Style.VerticalAlignment = OfficeOpenXml.Style.ExcelVerticalAlignment.Center;
ws.Column(10).Width = 40;
// 设置施工图片列宽度(根据最大图片数量调整)
ws.Column(10).Width = 60;
package.SaveAs(new FileInfo(fullPath));
return (sFileName, fullPath);

View File

@ -47,6 +47,11 @@ namespace ZR.Admin.WebApi.Controllers
/// 工作人员记录接口
/// </summary>
private readonly ICamWorkerService _CamWorkerService;
/// <summary>
/// 工作记录图片接口
/// </summary>
private readonly ICamWorkrecordImageService _CamWorkrecordImageService;
/// <summary>
/// CommonController 构造函数
/// </summary>
@ -63,7 +68,8 @@ namespace ZR.Admin.WebApi.Controllers
ISysDeptService deptService,
ISysDictDataService sysDictDataService,
ICamWorkrecordService _CamWorkrecordService,
ICamWorkerService camWorkerService)
ICamWorkerService camWorkerService,
ICamWorkrecordImageService camWorkrecordImageService)
{
WebHostEnvironment = webHostEnvironment;
SysFileService = fileService;
@ -73,6 +79,7 @@ namespace ZR.Admin.WebApi.Controllers
this.sysDictDataService = sysDictDataService;
this._CamWorkrecordService = _CamWorkrecordService;
_CamWorkerService = camWorkerService;
_CamWorkrecordImageService = camWorkrecordImageService;
}
/// <summary>
@ -443,6 +450,275 @@ namespace ZR.Admin.WebApi.Controllers
return SUCCESS(1);
}
/// <summary>
/// 添加工作记录(多图支持 v2
/// </summary>
/// <returns></returns>
[HttpPost]
[Route("/addworkrecordv2")]
[AllowAnonymous]
public async Task<IActionResult> AddCamWorkRecordV2([FromBody] CamRecordWorkDto parm)
{
if (parm.Workers == null || parm.Workers.Count == 0)
{
return ToResponse(ResultCode.PARAM_ERROR, "请选择工作人员");
}
// 获取图片列表(优先使用 Images兼容旧版 Image
var imageList = new List<string>();
if (parm.Images != null && parm.Images.Count > 0)
{
imageList = parm.Images;
}
else if (!string.IsNullOrEmpty(parm.Image))
{
imageList.Add(parm.Image);
}
if (imageList.Count == 0)
{
return ToResponse(ResultCode.PARAM_ERROR, "请上传图片");
}
// 限制最多15张图片
if (imageList.Count > 15)
{
return ToResponse(ResultCode.PARAM_ERROR, "最多支持15张图片");
}
var filePath = "/workfiles/" + parm.RecordTime?.ToString("yyyyMM/yyyyMMdd");
string savePath = Path.Combine(WebHostEnvironment.WebRootPath);
var path = savePath + filePath;
var domainUrl = $"{OptionsSetting.Upload.UploadUrl}";
// 如果是编辑模式,先删除旧的图片
if (parm.Id != null && parm.Id > 0)
{
var m = await _CamWorkrecordService.AsQueryable().Where(it => it.Id == parm.Id).FirstAsync();
var m_workers = await _CamWorkerService.AsQueryable().Where(it => it.WorkrecordId == parm.Id).ToListAsync();
if (m == null)
{
return ToResponse(ResultCode.PARAM_ERROR, "未找到该记录");
}
// 删除旧的图片记录
var oldImages = _CamWorkrecordImageService.GetListByWorkrecordId((int)parm.Id);
foreach (var oldImg in oldImages)
{
await DeleteImageFiles(oldImg.ImageUrl, m.RecordTime, m_workers, m.Content, m.DeptName);
}
_CamWorkrecordImageService.DeleteByWorkrecordId((int)parm.Id);
// 删除旧的主图
if (!string.IsNullOrEmpty(m.ImageUrl) && m.ImageUrl.IndexOf("http") != -1)
{
await DeleteImageFiles(m.ImageUrl, m.RecordTime, m_workers, m.Content, m.DeptName);
}
// 删除施工人员数据
await _CamWorkerService.DeleteAsync(it => it.WorkrecordId == parm.Id);
}
// 保存图片列表
var savedImageUrls = new List<string>();
int sortOrder = 1;
foreach (var imageBase64 in imageList)
{
var imageprx = ImageConverter.GetFileExtensionFromBase64(imageBase64);
var images = ImageConverter.Base64ToImageBytes(imageBase64);
if (images.Length == 0)
{
continue;
}
int MaxSize = 300 * 1024; // 300KB
if (images.Length > MaxSize)
{
images = CompressImage(images, 50);
imageprx = ".jpg";
}
var imageName = ImageConverter.GenerateImageFileName(imageprx);
var participantsUrl = $"{path}/参与人员/";
var photosDay = $"{path}/当日照片/";
var jobContent = $"{path}/工作内容/";
var department = $"{path}/部门/";
// 添加当日照片
if (!Directory.Exists(photosDay))
{
Directory.CreateDirectory(photosDay);
}
var photosDayFileName = $"{photosDay}/{imageName}";
using (var stream = new FileStream(photosDayFileName, FileMode.Create))
{
await stream.WriteAsync(images, 0, images.Length);
}
// 添加当日根据【人名】分类的照片
foreach (var work in parm.Workers)
{
var participantsUrlFIleName = $"{participantsUrl}/{work}/";
if (!Directory.Exists(participantsUrlFIleName))
{
Directory.CreateDirectory(participantsUrlFIleName);
}
participantsUrlFIleName += imageName;
using (var stream = new FileStream(participantsUrlFIleName, FileMode.Create))
{
await stream.WriteAsync(images, 0, images.Length);
}
}
// 添加当日根据【工作内容】分类的照片
var jobContentUrl = $"{jobContent}/{parm.Content}/";
if (!Directory.Exists(jobContentUrl))
{
Directory.CreateDirectory(jobContentUrl);
}
jobContentUrl += imageName;
using (var stream = new FileStream(jobContentUrl, FileMode.Create))
{
await stream.WriteAsync(images, 0, images.Length);
}
// 添加当日根据【部门】分类的照片
var departmentUrl = $"{department}{parm.DeptName}/";
if (!Directory.Exists(departmentUrl))
{
Directory.CreateDirectory(departmentUrl);
}
departmentUrl += imageName;
using (var stream = new FileStream(departmentUrl, FileMode.Create))
{
await stream.WriteAsync(images, 0, images.Length);
}
var imageUrl = $"{domainUrl}{filePath}/当日照片/{imageName}";
savedImageUrls.Add(imageUrl);
sortOrder++;
}
if (savedImageUrls.Count == 0)
{
return ToResponse(ResultCode.CUSTOM_ERROR, "图片上传失败");
}
// 保存工作记录
var modal = parm.Adapt<CamWorkrecord>().ToCreate(HttpContext);
modal.CreateTime = DateTime.Now;
modal.UpdateTime = DateTime.Now;
modal.ImageUrl = savedImageUrls[0]; // 第一张图作为封面图
int workid;
if (parm.Id != null && parm.Id > 0)
{
await _CamWorkrecordService.UpdateAsync(modal);
workid = (int)parm.Id;
}
else
{
modal = await _CamWorkrecordService.Insertable(modal).ExecuteReturnEntityAsync();
workid = modal.Id;
}
// 保存图片记录到新表
var imageRecords = new List<CamWorkrecordImage>();
sortOrder = 1;
foreach (var imgUrl in savedImageUrls)
{
imageRecords.Add(new CamWorkrecordImage
{
WorkrecordId = workid,
ImageUrl = imgUrl,
SortOrder = sortOrder++,
CreateTime = DateTime.Now,
UpdateTime = DateTime.Now
});
}
_CamWorkrecordImageService.AddCamWorkrecordImages(imageRecords);
// 保存工作人员
var workers = new List<CamWorker>();
foreach (var item in parm.Workers)
{
var worker = new CamWorker()
{
WorkrecordId = workid,
WorkerName = item,
CreateTime = DateTime.Now,
UpdateTime = DateTime.Now
};
workers.Add(worker);
}
_CamWorkerService.AsInsertable(workers).ExecuteCommand();
return SUCCESS(new { id = workid, imageCount = savedImageUrls.Count });
}
/// <summary>
/// 删除图片文件(辅助方法)
/// </summary>
private async Task DeleteImageFiles(string imageUrl, DateTime? recordTime, List<CamWorker> workers, string content, string deptName)
{
if (string.IsNullOrEmpty(imageUrl) || imageUrl.IndexOf("workfiles") == -1)
return;
try
{
var imagePath = imageUrl.Substring(imageUrl.IndexOf("workfiles"));
var fullImagePath = Path.Combine(WebHostEnvironment.WebRootPath, imagePath);
var fileName = Path.GetFileName(fullImagePath);
// 删除当日照片
if (IOFile.Exists(fullImagePath))
{
IOFile.Delete(fullImagePath);
}
// 删除参与人员照片
var participantsPath = Path.Combine(WebHostEnvironment.WebRootPath, "workfiles", recordTime?.ToString("yyyyMM/yyyyMMdd"), "参与人员");
if (Directory.Exists(participantsPath))
{
foreach (var workerDir in workers)
{
var workerImagePath = Path.Combine(participantsPath, workerDir.WorkerName, fileName);
if (IOFile.Exists(workerImagePath))
{
IOFile.Delete(workerImagePath);
}
}
}
// 删除工作内容照片
if (!string.IsNullOrEmpty(content))
{
var jobContentPath = Path.Combine(WebHostEnvironment.WebRootPath, "workfiles", recordTime?.ToString("yyyyMM/yyyyMMdd"), "工作内容", content);
var jobContentImagePath = Path.Combine(jobContentPath, fileName);
if (IOFile.Exists(jobContentImagePath))
{
IOFile.Delete(jobContentImagePath);
}
}
// 删除部门照片
if (!string.IsNullOrEmpty(deptName))
{
var departmentPath = Path.Combine(WebHostEnvironment.WebRootPath, "workfiles", recordTime?.ToString("yyyyMM/yyyyMMdd"), "部门", deptName);
var departmentImagePath = Path.Combine(departmentPath, fileName);
if (IOFile.Exists(departmentImagePath))
{
IOFile.Delete(departmentImagePath);
}
}
}
catch (Exception ex)
{
logger.Error($"删除图片文件失败: {imageUrl}, 错误: {ex.Message}");
}
}
/// <summary>
/// 删除工作记录
/// </summary>
@ -465,7 +741,14 @@ namespace ZR.Admin.WebApi.Controllers
try
{
// 删除图片文件
// 删除多图表中的图片文件
var imageRecords = _CamWorkrecordImageService.GetListByWorkrecordId((int)id);
foreach (var imgRecord in imageRecords)
{
await DeleteImageFiles(imgRecord.ImageUrl, workRecord.RecordTime, workers, workRecord.Content, workRecord.DeptName);
}
// 删除主图片文件(兼容旧数据)
if (!string.IsNullOrEmpty(workRecord.ImageUrl))
{
// 从URL中提取文件路径
@ -540,6 +823,10 @@ namespace ZR.Admin.WebApi.Controllers
{
logger.Error($"删除文件失败,错误: {ex.Message}");
}
// 删除图片记录(多图支持)
_CamWorkrecordImageService.DeleteByWorkrecordId((int)id);
// 删除工作人员记录
await _CamWorkerService.DeleteAsync(it => it.WorkrecordId == id);

View File

@ -0,0 +1,42 @@
namespace ZR.Model.Business
{
/// <summary>
/// 工作记录图片
/// </summary>
[SugarTable("cam_workrecord_image")]
public class CamWorkrecordImage
{
/// <summary>
/// 主键
/// </summary>
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
public long Id { get; set; }
/// <summary>
/// 工作记录ID
/// </summary>
public int WorkrecordId { get; set; }
/// <summary>
/// 图片路径
/// </summary>
public string ImageUrl { get; set; }
/// <summary>
/// 排序1=第一张封面图)
/// </summary>
public int SortOrder { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime? CreateTime { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public DateTime? UpdateTime { get; set; }
}
}

View File

@ -63,6 +63,16 @@ namespace ZR.Model.Business.Dto
[ExcelColumn(Name = "状态")]
public string StatusNameLabel { get; set; }
/// <summary>
/// 图片列表(多图支持)
/// </summary>
public List<CamWorkrecordImageDto> Images { get; set; }
/// <summary>
/// 图片数量
/// </summary>
public int ImageCount { get; set; }
}
/// <summary>
@ -119,9 +129,14 @@ namespace ZR.Model.Business.Dto
public string Remarks { get; set; }
/// <summary>
///
///
/// </summary>
public List<string> Workers { get; set; }
/// <summary>
/// 图片列表(多图支持)
/// </summary>
public List<string> Images { get; set; }
}
/// <summary>
@ -201,4 +216,35 @@ namespace ZR.Model.Business.Dto
}
/// <summary>
/// 工作记录图片输出对象
/// </summary>
public class CamWorkrecordImageDto
{
/// <summary>
/// 图片ID
/// </summary>
public long Id { get; set; }
/// <summary>
/// 工作记录ID
/// </summary>
public int WorkrecordId { get; set; }
/// <summary>
/// 图片地址
/// </summary>
public string ImageUrl { get; set; }
/// <summary>
/// 排序号
/// </summary>
public int SortOrder { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime? CreateTime { get; set; }
}
}

View File

@ -0,0 +1,89 @@
using Infrastructure.Attribute;
using ZR.Model.Business;
using ZR.Repository;
using ZR.Service.Business.IBusinessService;
namespace ZR.Service.Business
{
/// <summary>
/// 工作记录图片Service业务层处理
/// </summary>
[AppService(ServiceType = typeof(ICamWorkrecordImageService), ServiceLifetime = LifeTime.Transient)]
public class CamWorkrecordImageService : BaseService<CamWorkrecordImage>, ICamWorkrecordImageService
{
/// <summary>
/// 根据工作记录ID获取图片列表
/// </summary>
/// <param name="workrecordId"></param>
/// <returns></returns>
public List<CamWorkrecordImage> GetListByWorkrecordId(int workrecordId)
{
return Queryable()
.Where(x => x.WorkrecordId == workrecordId)
.OrderBy(x => x.SortOrder)
.ToList();
}
/// <summary>
/// 根据工作记录ID获取图片列表批量
/// </summary>
/// <param name="workrecordIds"></param>
/// <returns></returns>
public List<CamWorkrecordImage> GetListByWorkrecordIds(List<int> workrecordIds)
{
if (workrecordIds == null || workrecordIds.Count == 0)
return new List<CamWorkrecordImage>();
return Queryable()
.Where(x => workrecordIds.Contains(x.WorkrecordId))
.OrderBy(x => x.WorkrecordId)
.OrderBy(x => x.SortOrder)
.ToList();
}
/// <summary>
/// 添加工作记录图片
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public CamWorkrecordImage AddCamWorkrecordImage(CamWorkrecordImage model)
{
return Insertable(model).ExecuteReturnEntity();
}
/// <summary>
/// 批量添加工作记录图片
/// </summary>
/// <param name="list"></param>
/// <returns></returns>
public int AddCamWorkrecordImages(List<CamWorkrecordImage> list)
{
if (list == null || list.Count == 0)
return 0;
return Insertable(list).ExecuteCommand();
}
/// <summary>
/// 根据工作记录ID删除图片
/// </summary>
/// <param name="workrecordId"></param>
/// <returns></returns>
public int DeleteByWorkrecordId(int workrecordId)
{
return Delete(x => x.WorkrecordId == workrecordId);
}
/// <summary>
/// 获取工作记录的图片数量
/// </summary>
/// <param name="workrecordId"></param>
/// <returns></returns>
public int GetImageCount(int workrecordId)
{
return Queryable()
.Where(x => x.WorkrecordId == workrecordId)
.Count();
}
}
}

View File

@ -0,0 +1,52 @@
using ZR.Model.Business;
namespace ZR.Service.Business.IBusinessService
{
/// <summary>
/// 工作记录图片service接口
/// </summary>
public interface ICamWorkrecordImageService : IBaseService<CamWorkrecordImage>
{
/// <summary>
/// 根据工作记录ID获取图片列表
/// </summary>
/// <param name="workrecordId"></param>
/// <returns></returns>
List<CamWorkrecordImage> GetListByWorkrecordId(int workrecordId);
/// <summary>
/// 根据工作记录ID获取图片列表批量
/// </summary>
/// <param name="workrecordIds"></param>
/// <returns></returns>
List<CamWorkrecordImage> GetListByWorkrecordIds(List<int> workrecordIds);
/// <summary>
/// 添加工作记录图片
/// </summary>
/// <param name="parm"></param>
/// <returns></returns>
CamWorkrecordImage AddCamWorkrecordImage(CamWorkrecordImage parm);
/// <summary>
/// 批量添加工作记录图片
/// </summary>
/// <param name="list"></param>
/// <returns></returns>
int AddCamWorkrecordImages(List<CamWorkrecordImage> list);
/// <summary>
/// 根据工作记录ID删除图片
/// </summary>
/// <param name="workrecordId"></param>
/// <returns></returns>
int DeleteByWorkrecordId(int workrecordId);
/// <summary>
/// 获取工作记录的图片数量
/// </summary>
/// <param name="workrecordId"></param>
/// <returns></returns>
int GetImageCount(int workrecordId);
}
}

View File

@ -80,10 +80,31 @@
align="center"
:show-overflow-tooltip="true"
v-if="columns.showColumn('deptName')" />
<el-table-column prop="imageUrl" width="140" label="图片" align="center" v-if="columns.showColumn('imageUrl')">
<el-table-column prop="imageUrl" width="180" label="图片" align="center" v-if="columns.showColumn('imageUrl')">
<template #default="scope">
<div class="image-container">
<ImagePreview class="table-image" :src="scope.row.imageUrl"></ImagePreview>
<!-- 多图显示 -->
<template v-if="scope.row.images && scope.row.images.length > 0">
<div class="multi-image-wrapper">
<ImagePreview
v-for="img in scope.row.images.slice(0, 3)"
:key="img.id"
class="table-image-small"
:src="img.imageUrl">
</ImagePreview>
<span v-if="scope.row.images.length > 3" class="image-count-badge">
+{{ scope.row.images.length - 3 }}
</span>
</div>
<div class="image-count-text">{{ scope.row.imageCount || scope.row.images.length }}</div>
</template>
<!-- 兼容旧数据单图显示 -->
<template v-else-if="scope.row.imageUrl">
<ImagePreview class="table-image" :src="scope.row.imageUrl"></ImagePreview>
</template>
<template v-else>
<span class="no-image">暂无图片</span>
</template>
</div>
</template>
</el-table-column>
@ -240,14 +261,39 @@
<el-col :lg="24">
<el-form-item label="图片" prop="imageUrl">
<el-upload class="upload-demo" drag :auto-upload="false" :show-file-list="false" accept="image/*" @change="handleImageChange">
<!-- 多图上传 -->
<el-upload
class="upload-demo"
drag
multiple
:auto-upload="false"
:show-file-list="false"
accept="image/*"
@change="handleImageChange">
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip">只能上传jpg/png文件且不超过10MB</div>
<div class="el-upload__tip">支持上传多张图片最多15张单张不超过10MB</div>
</template>
</el-upload>
<div v-if="form.imageUrl" class="image-preview">
<!-- 多图预览 -->
<div v-if="form.images && form.images.length > 0" class="multi-image-preview">
<div v-for="(img, idx) in form.images" :key="idx" class="preview-item">
<img :src="img.imageUrl || img" alt="预览图片" />
<el-button
v-if="opertype !== 3"
class="delete-btn"
type="danger"
size="small"
circle
icon="delete"
@click="removeImage(idx)">
</el-button>
<span class="image-index">{{ idx + 1 }}</span>
</div>
</div>
<!-- 兼容旧数据单图预览 -->
<div v-else-if="form.imageUrl" class="image-preview">
<img :src="form.imageUrl" alt="预览图片" style="max-width: 200px; max-height: 200px" />
</div>
</el-form-item>
@ -451,6 +497,7 @@ function reset() {
id: null,
deptName: null,
imageUrl: null,
images: [], //
recordTime: null,
longitude: null,
latitude: null,
@ -465,7 +512,7 @@ function reset() {
proxy.resetForm('formRef')
}
// Base64
// Base64
function handleImageChange(file) {
const isImage = file.raw.type.startsWith('image/')
const isLt10M = file.raw.size / 1024 / 1024 < 10
@ -479,11 +526,42 @@ function handleImageChange(file) {
return
}
//
if (!form.value.images) {
form.value.images = []
}
if (form.value.images.length >= 15) {
proxy.$modal.msgError('最多只能上传15张图片!')
return
}
const reader = new FileReader()
reader.readAsDataURL(file.raw)
reader.onload = (e) => {
form.value.imageUrl = e.target.result
form.value.image = e.target.result
//
form.value.images.push({
imageUrl: e.target.result,
isNew: true //
})
//
if (form.value.images.length === 1) {
form.value.imageUrl = e.target.result
form.value.image = e.target.result
}
}
}
//
function removeImage(index) {
if (form.value.images && form.value.images.length > index) {
form.value.images.splice(index, 1)
//
if (form.value.images.length > 0) {
form.value.imageUrl = form.value.images[0].imageUrl || form.value.images[0]
} else {
form.value.imageUrl = null
form.value.image = null
}
}
}
@ -539,12 +617,8 @@ function handlePreview(row) {
title.value = '查看'
opertype.value = 3
form.value = {
...data
}
// Base64
if (data.imageUrl) {
convertImageUrlToBase64(data.imageUrl)
...data,
images: data.images || [] // images
}
}
})
@ -571,12 +645,8 @@ function handleUpdate(row) {
opertype.value = 2
form.value = {
...data
}
// Base64
if (data.imageUrl) {
convertImageUrlToBase64(data.imageUrl)
...data,
images: data.images || [] // images
}
}
})
@ -692,10 +762,60 @@ handleQuery()
//
.image-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 8px;
.multi-image-wrapper {
display: flex;
flex-wrap: wrap;
gap: 4px;
justify-content: center;
align-items: center;
position: relative;
}
.table-image-small {
width: 50px;
height: 50px;
border-radius: 4px;
object-fit: cover;
border: 1px solid #e4e7ed;
transition: all 0.3s ease;
&:hover {
transform: scale(1.1);
border-color: #409eff;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
z-index: 10;
}
}
.image-count-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 30px;
height: 30px;
background: linear-gradient(135deg, #409eff 0%, #36a3f7 100%);
color: white;
border-radius: 50%;
font-size: 11px;
font-weight: bold;
}
.image-count-text {
margin-top: 4px;
font-size: 11px;
color: #909399;
}
.no-image {
color: #c0c4cc;
font-size: 12px;
}
.table-image {
width: 80px;
height: 80px;
@ -970,6 +1090,62 @@ handleQuery()
}
}
//
.multi-image-preview {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 12px;
padding: 12px;
background: #f5f7fa;
border-radius: 8px;
border: 1px dashed #dcdfe6;
.preview-item {
position: relative;
width: 120px;
height: 120px;
border-radius: 6px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
.delete-btn {
opacity: 1;
}
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
.delete-btn {
position: absolute;
top: 4px;
right: 4px;
opacity: 0;
transition: opacity 0.3s ease;
}
.image-index {
position: absolute;
bottom: 4px;
left: 4px;
background: rgba(0, 0, 0, 0.6);
color: white;
font-size: 11px;
padding: 2px 6px;
border-radius: 10px;
}
}
}
//
@media (max-width: 1200px) {
.work-record-table {

View File

@ -0,0 +1,53 @@
USE [WatermarkCamera]
GO
/****** Object: Table [dbo].[cam_workrecord_image] Script Date: 2025/12/27 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- 创建工作记录图片表
CREATE TABLE [dbo].[cam_workrecord_image](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[WorkrecordId] [int] NOT NULL,
[ImageUrl] [nvarchar](500) NOT NULL,
[SortOrder] [int] NOT NULL DEFAULT 1,
[CreateTime] [datetime] NOT NULL DEFAULT (getdate()),
[UpdateTime] [datetime] NOT NULL DEFAULT (getdate()),
CONSTRAINT [PK_cam_workrecord_image] PRIMARY KEY CLUSTERED ([Id] ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
-- 添加外键约束(删除工作记录时级联删除图片)
ALTER TABLE [dbo].[cam_workrecord_image]
ADD CONSTRAINT [FK_cam_workrecord_image_workrecord]
FOREIGN KEY ([WorkrecordId]) REFERENCES [dbo].[cam_workrecord]([Id])
ON DELETE CASCADE
GO
-- 添加索引按工作记录ID查询
CREATE NONCLUSTERED INDEX [IX_cam_workrecord_image_WorkrecordId]
ON [dbo].[cam_workrecord_image] ([WorkrecordId] ASC)
GO
-- 添加字段说明
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'主键' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'cam_workrecord_image', @level2type=N'COLUMN',@level2name=N'Id'
GO
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'工作记录ID' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'cam_workrecord_image', @level2type=N'COLUMN',@level2name=N'WorkrecordId'
GO
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'图片路径' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'cam_workrecord_image', @level2type=N'COLUMN',@level2name=N'ImageUrl'
GO
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'排序1=第一张封面图)' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'cam_workrecord_image', @level2type=N'COLUMN',@level2name=N'SortOrder'
GO
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'创建时间' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'cam_workrecord_image', @level2type=N'COLUMN',@level2name=N'CreateTime'
GO
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'更新时间' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'cam_workrecord_image', @level2type=N'COLUMN',@level2name=N'UpdateTime'
GO

View File

@ -0,0 +1,120 @@
USE [WatermarkCamera]
GO
/*
* v1.0.5
* cam_workrecord.ImageUrl cam_workrecord_image
*
*
* 1. cam_workrecord_image.sql
* 2.
*
*
* 1. cam_workrecord_image.sql
* 2.
*/
-- =============================================
-- 第一步:检查表是否存在
-- =============================================
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[cam_workrecord_image]') AND type in (N'U'))
BEGIN
RAISERROR('错误cam_workrecord_image 表不存在,请先执行建表脚本!', 16, 1)
RETURN
END
GO
-- =============================================
-- 第二步:迁移前数据统计
-- =============================================
PRINT '========== 迁移前数据统计 =========='
DECLARE @count_before INT
SELECT @count_before = COUNT(*) FROM [dbo].[cam_workrecord] WHERE [ImageUrl] IS NOT NULL AND [ImageUrl] <> ''
PRINT 'cam_workrecord 有图片的记录数:' + CAST(@count_before AS VARCHAR(10))
DECLARE @count_image_before INT
SELECT @count_image_before = COUNT(*) FROM [dbo].[cam_workrecord_image]
PRINT 'cam_workrecord_image 现有记录数:' + CAST(@count_image_before AS VARCHAR(10))
GO
-- =============================================
-- 第三步:执行数据迁移
-- =============================================
PRINT '========== 开始数据迁移 =========='
-- 迁移老数据:将 cam_workrecord.ImageUrl 迁移到 cam_workrecord_image 表
-- 只迁移尚未迁移的数据(避免重复迁移)
INSERT INTO [dbo].[cam_workrecord_image]
([WorkrecordId], [ImageUrl], [SortOrder], [CreateTime], [UpdateTime])
SELECT
w.[Id] AS WorkrecordId,
w.[ImageUrl],
1 AS SortOrder, -- 老数据都是第一张(封面图)
ISNULL(w.[CreateTime], GETDATE()),
ISNULL(w.[UpdateTime], GETDATE())
FROM [dbo].[cam_workrecord] w
WHERE w.[ImageUrl] IS NOT NULL
AND w.[ImageUrl] <> ''
AND NOT EXISTS (
SELECT 1 FROM [dbo].[cam_workrecord_image] img
WHERE img.[WorkrecordId] = w.[Id]
)
DECLARE @migrated INT = @@ROWCOUNT
PRINT '本次迁移记录数:' + CAST(@migrated AS VARCHAR(10))
GO
-- =============================================
-- 第四步:迁移后数据验证
-- =============================================
PRINT '========== 迁移后数据验证 =========='
-- 验证1对比数量
SELECT
'迁移验证' AS [],
(SELECT COUNT(*) FROM [dbo].[cam_workrecord] WHERE [ImageUrl] IS NOT NULL AND [ImageUrl] <> '') AS [cam_workrecord有图片数],
(SELECT COUNT(*) FROM [dbo].[cam_workrecord_image]) AS [cam_workrecord_image记录数]
-- 验证2检查是否有遗漏
DECLARE @missing INT
SELECT @missing = COUNT(*)
FROM [dbo].[cam_workrecord] w
WHERE w.[ImageUrl] IS NOT NULL
AND w.[ImageUrl] <> ''
AND NOT EXISTS (
SELECT 1 FROM [dbo].[cam_workrecord_image] img
WHERE img.[WorkrecordId] = w.[Id]
)
IF @missing > 0
BEGIN
PRINT '警告:有 ' + CAST(@missing AS VARCHAR(10)) + ' 条记录未迁移成功!'
END
ELSE
BEGIN
PRINT '验证通过:所有数据迁移成功!'
END
GO
-- =============================================
-- 第五步:显示迁移结果样例
-- =============================================
PRINT '========== 迁移结果样例前10条 =========='
SELECT TOP 10
w.[Id] AS [WorkrecordId],
w.[Content] AS [],
w.[ImageUrl] AS [ImageUrl],
img.[Id] AS [ImageId],
img.[ImageUrl] AS [ImageUrl],
img.[SortOrder] AS []
FROM [dbo].[cam_workrecord] w
INNER JOIN [dbo].[cam_workrecord_image] img ON w.[Id] = img.[WorkrecordId]
ORDER BY w.[Id] DESC
GO
PRINT '========== 迁移完成 =========='
PRINT '注意cam_workrecord.ImageUrl 字段已保留,用于兼容老代码。'
PRINT '后续新数据会同时写入 cam_workrecord.ImageUrl封面图和 cam_workrecord_image所有图片'
GO

View File

@ -23,4 +23,16 @@ export const addWatermarkRecord = async (data) => {
console.log(url, data);
const res = await post(url, data);
return res;
}
/**
* 添加工作记录多图支持 v2
* @param {Object} data - 包含 Images 数组的数据
* @returns
*/
export const addWatermarkRecordV2 = async (data) => {
var url = base_url + "addworkrecordv2";
console.log(url, data);
const res = await post(url, data);
return res;
}

View File

@ -1,8 +1,53 @@
/**
* 获取位置的异步函数
* H5 端会尝试使用浏览器定位失败则返回模拟数据
*/
export const getLocation = async () => {
return new Promise((resolve, reject) => {
// #ifdef H5
// H5 端:尝试使用浏览器定位,失败则使用模拟数据
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
resolve({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
altitude: position.coords.altitude || 0
});
},
(error) => {
console.warn('H5 定位失败,使用模拟数据:', error.message);
// 返回模拟的经纬度(默认:北京天安门附近)
resolve({
latitude: 39.908823,
longitude: 116.397470,
accuracy: 100,
altitude: 0,
_isMock: true // 标记为模拟数据
});
},
{
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
}
);
} else {
console.warn('浏览器不支持定位,使用模拟数据');
// 返回模拟的经纬度
resolve({
latitude: 39.908823,
longitude: 116.397470,
accuracy: 100,
altitude: 0,
_isMock: true
});
}
// #endif
// #ifndef H5
// App/小程序端:使用原生定位
uni.getLocation({
isHighAccuracy: true,
altitude: true,
@ -11,17 +56,31 @@ export const getLocation = async () => {
resolve(res);
},
fail: (err) => {
reject(err);
console.error('定位失败:', err);
// App端定位失败也返回模拟数据方便调试
resolve({
latitude: 39.908823,
longitude: 116.397470,
accuracy: 100,
altitude: 0,
_isMock: true
});
}
});
// #endif
});
}
export const chooseImage = async () => {
/**
* 选择图片支持多选
* @param {number} count - 最多选择的图片数量默认1
* @returns {Promise<object>} - 返回选择的图片信息
*/
export const chooseImage = async (count = 1) => {
return new Promise((resolve, reject) => {
uni.chooseImage({
sourceType:['camera'],
count: 1,
sourceType: ['camera', 'album'], // 支持拍照和相册选择
count: count, // 支持多选
success: (res) => {
resolve(res);
},

View File

@ -37,11 +37,39 @@
<!-- 图片预览区域 -->
<view class="preview-box">
<text class="preview-title">图片预览</text>
<view class="preview-img">
<text class="preview-title">图片预览 ({{ imageList.length }}/{{ MAX_IMAGES }})</text>
<!-- 多图预览列表 -->
<view class="multi-preview-container" v-if="imageList.length > 0">
<view
class="preview-item"
v-for="(img, index) in imageList"
:key="index"
@click="handlePreviewImage(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 class="preview-img" v-if="imageSrc">
<image
:src="imageSrc"
@click="handlePreviewImage"
@click="handlePreviewCurrentImage"
mode="aspectFit"
class="img"
></image>
@ -176,7 +204,7 @@ import {
getLocationTranslate,
getLocationGeocoder,
} from "../../common/mapTranslateResult";
import { getConfig, addWatermarkRecord } from "../../common/server";
import { getConfig, addWatermarkRecord, addWatermarkRecordV2 } from "../../common/server";
import { onLoad } from "@dcloudio/uni-app";
//
// window.erudaInstance = eruda.init();
@ -203,6 +231,9 @@ const locations = ref({
//
const imageSrc = ref("");
const originalImageSrc = ref("");
//
const imageList = ref([]); // [{original, watermark, sizeInfo}]
const MAX_IMAGES = 15; //
const imageSizeInfo = ref({
original: {
width: 0,
@ -260,13 +291,22 @@ const handleStartCapture = async () => {
locationInfo.value = geocoderResult || "未知位置";
currentTime.value = new Date();
//
const image = await chooseImage();
//
const image = await chooseImage(9); // 9
console.log("图片", image);
originalImageSrc.value = image.tempFilePaths[0];
imageSrc.value = image.tempFilePaths[0];
await addWatermarkToImage();
//
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) {
@ -279,13 +319,100 @@ const handleStartCapture = async () => {
}
};
//
const handlePreviewImage = () => {
uni.previewImage({
urls: [imageSrc.value],
//
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) => {
if (imageList.value.length > index) {
imageList.value.splice(index, 1);
//
if (imageList.value.length > 0) {
const newIndex = Math.min(index, imageList.value.length - 1);
imageSrc.value = imageList.value[newIndex].watermark;
originalImageSrc.value = imageList.value[newIndex].original;
} else {
imageSrc.value = "";
originalImageSrc.value = "";
}
}
};
//
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],
});
}
};
//
const handleComboxSelect = async (value) => {
console.log("选择的工作内容:", value);
@ -432,8 +559,44 @@ const handleRetakeCancel = async () => {
statusIndex.value = 0;
deptIndex.value = 0;
workContent.value = "";
imageList.value = []; //
loadCandidates();
await addWatermarkToImage();
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();
};
//
@ -448,6 +611,15 @@ const handleSaveAndSubmit = async () => {
return;
}
//
if (imageList.value.length === 0) {
uni.showToast({
title: "请先拍摄图片",
icon: "error",
});
return;
}
isSubmitting.value = true;
try {
@ -455,15 +627,15 @@ const handleSaveAndSubmit = async () => {
title: "保存中...",
});
//
await addWatermarkToImage();
//
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(), // 使ISO
date: new Date().toISOString(),
};
console.log("locationData", locationData);
@ -471,24 +643,24 @@ const handleSaveAndSubmit = async () => {
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);
// Base64
const imagesBase64 = [];
for (const img of imageList.value) {
const base64 = await imageToBase64(img.watermark);
imagesBase64.push(base64);
}
console.log(`${imagesBase64.length}张图片`);
const camRecordWorkDto = {
//
DeptName: departments.value[deptIndex.value],
//
Image: imageBase64,
//
Image: imagesBase64[0],
//
Images: imagesBase64,
//
RecordTime: formatDate(currentTime.value),
//
@ -503,11 +675,12 @@ const handleSaveAndSubmit = async () => {
StatusName: statusList.value[statusIndex.value],
//
Remarks: _remarks,
//
Workers: workers.value,
};
var res = await addWatermarkRecord(camRecordWorkDto);
// 使 v2 API
var res = await addWatermarkRecordV2(camRecordWorkDto);
console.log(res);
if (res.code != 200) {
@ -610,6 +783,7 @@ const validateFormData = (isSubmit = false) => {
const resetFormData = () => {
imageSrc.value = "";
originalImageSrc.value = "";
imageList.value = []; //
locationInfo.value = "";
currentTime.value = "";
imageSizeInfo.value = {
@ -731,6 +905,88 @@ onLoad(async () => {
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);
}
.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%;