|
|
@@ -83,6 +83,14 @@ const props = defineProps({
|
|
|
type: Number,
|
|
|
required: true,
|
|
|
},
|
|
|
+ isAchievementImgs: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ imgData: {
|
|
|
+ type: Object,
|
|
|
+ default: () => ({}),
|
|
|
+ },
|
|
|
});
|
|
|
let img = null;
|
|
|
let ctx = null;
|
|
|
@@ -171,8 +179,15 @@ async function generateImageWithQRCode() {
|
|
|
drawImageCoverByNatural(tempCtx, cachedSourceImg, w, h);
|
|
|
|
|
|
// 绘制底部遮罩和文字
|
|
|
- drawBottomMask(tempCtx, w, h);
|
|
|
- drawBottomTextOverlay(tempCtx, w, h);
|
|
|
+ if (props.isAchievementImgs) {
|
|
|
+ if (props.imgData?.reCheckText) {
|
|
|
+ drawBottomMask(tempCtx, w, h);
|
|
|
+ drawReviewEffectText(tempCtx, w, h);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ drawBottomMask(tempCtx, w, h);
|
|
|
+ drawBottomTextOverlay(tempCtx, w, h);
|
|
|
+ }
|
|
|
|
|
|
// 二维码尺寸和位置(参考 albumCarouselItem 的样式)
|
|
|
const qrSize = 40;
|
|
|
@@ -255,8 +270,15 @@ function drawWatermark2(sourceImg, displayImg) {
|
|
|
// ✅ 用「原始像素图」做 cover
|
|
|
drawImageCoverByNatural(ctx, sourceImg, w, h);
|
|
|
|
|
|
- drawBottomMask(ctx, w, h);
|
|
|
- drawBottomTextOverlay(ctx, w, h);
|
|
|
+ if (props.isAchievementImgs) {
|
|
|
+ if (props.imgData?.reCheckText) {
|
|
|
+ drawBottomMask(ctx, w, h);
|
|
|
+ drawReviewEffectText(ctx, w, h);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ drawBottomMask(ctx, w, h);
|
|
|
+ drawBottomTextOverlay(ctx, w, h);
|
|
|
+ }
|
|
|
|
|
|
watermark.value = canvas.toDataURL("image/jpeg", 0.85);
|
|
|
}
|
|
|
@@ -303,7 +325,7 @@ function drawImageCover(ctx, img, w, h) {
|
|
|
}
|
|
|
function drawBottomTextOverlay(ctx, w, h) {
|
|
|
const paddingX = 12;
|
|
|
- const paddingBottom = 8;
|
|
|
+ const paddingBottom = 12;
|
|
|
const lineHeight = 16;
|
|
|
|
|
|
ctx.textBaseline = "alphabetic";
|
|
|
@@ -316,33 +338,145 @@ function drawBottomTextOverlay(ctx, w, h) {
|
|
|
|
|
|
// 第三行(最底)
|
|
|
ctx.font = "10px sans-serif";
|
|
|
- console.log("paddingX", paddingX, y);
|
|
|
ctx.drawImage(imageCache.get("address"), paddingX, y - 9, 9, 10);
|
|
|
ctx.fillText("荔博园(广东省广州市从化区)", paddingX + 12, y);
|
|
|
|
|
|
- // 第二行
|
|
|
- y -= 15;
|
|
|
- ctx.font = "16px PangMenZhengDao";
|
|
|
- const workNameText = "梢期杀虫";
|
|
|
- const prescriptionText = "药物处方:乙烯利";
|
|
|
- ctx.fillText(workNameText, paddingX, y);
|
|
|
+ // 第二行:农事名称 + 药物处方(处方过长时向下换行,整体块仍贴近底部)
|
|
|
+ // 单行基准位置(仅一行时的 baseline)
|
|
|
+ const singleLineY = y - 15;
|
|
|
+
|
|
|
+ // 计算处方文本(长度大于 30 时才自动换行)
|
|
|
ctx.font = "10px sans-serif";
|
|
|
- ctx.fillText(prescriptionText, paddingX + workNameText.length * 20, y);
|
|
|
+ const workNameText = props.imgData?.farmWorkName || "";
|
|
|
+ const prescriptionFull =
|
|
|
+ "药物处方:" + (buildPrescriptionText(props.imgData?.prescriptionList) || "");
|
|
|
+
|
|
|
+ const maxWidth = w - paddingX * 2;
|
|
|
+
|
|
|
+ let firstLineY = singleLineY;
|
|
|
+
|
|
|
+ if (prescriptionFull.length <= 30) {
|
|
|
+ // 不足 30 个字符,保持一行:名称 + 处方在同一 baseline
|
|
|
+ firstLineY = singleLineY;
|
|
|
+
|
|
|
+ // 农事名称
|
|
|
+ ctx.font = "16px PangMenZhengDao";
|
|
|
+ ctx.fillText(workNameText, paddingX, firstLineY);
|
|
|
+
|
|
|
+ // 处方文本紧跟在名称后面
|
|
|
+ ctx.font = "10px sans-serif";
|
|
|
+ const nameWidth = ctx.measureText(workNameText).width;
|
|
|
+ const gap = 8;
|
|
|
+ ctx.fillText(prescriptionFull, paddingX + nameWidth + gap + 28, firstLineY);
|
|
|
+ } else {
|
|
|
+ // 超过 30 个字符,自动换行显示(处方整体从左对齐)
|
|
|
+ const prescriptionLines = [];
|
|
|
+ let line = "";
|
|
|
+ for (let i = 0; i < prescriptionFull.length; i++) {
|
|
|
+ const testLine = line + prescriptionFull[i];
|
|
|
+ const testWidth = ctx.measureText(testLine).width;
|
|
|
+ if (testWidth > maxWidth && line !== "") {
|
|
|
+ prescriptionLines.push(line);
|
|
|
+ line = prescriptionFull[i];
|
|
|
+ } else {
|
|
|
+ line = testLine;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (line) {
|
|
|
+ prescriptionLines.push(line);
|
|
|
+ }
|
|
|
+
|
|
|
+ const blockLines = 1 + prescriptionLines.length; // 1 行名称 + N 行处方
|
|
|
+ // 整个“第二行块”的第一行 baseline,使最后一行仍然靠近 singleLineY
|
|
|
+ firstLineY = singleLineY - (blockLines - 1) * lineHeight;
|
|
|
+
|
|
|
+ // 农事名称(第一行,靠左)
|
|
|
+ ctx.font = "16px PangMenZhengDao";
|
|
|
+ ctx.fillText(workNameText, paddingX, firstLineY);
|
|
|
+
|
|
|
+ // 处方文本,从下一行开始,全部从左侧对齐
|
|
|
+ ctx.font = "10px sans-serif";
|
|
|
+ let textY = firstLineY + lineHeight;
|
|
|
+ prescriptionLines.forEach((text) => {
|
|
|
+ ctx.fillText(text, paddingX, textY);
|
|
|
+ textY += lineHeight;
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
- // 第一行(最上)
|
|
|
- y -= 17;
|
|
|
+ // 第一行(最上)的 baseline 在整个“第二行块”之上 17px
|
|
|
+ y = firstLineY - 17;
|
|
|
ctx.font = "12px PangMenZhengDao";
|
|
|
- const timeText = "2025.12.25";
|
|
|
+ const timeText = props.imgData?.executeDate;
|
|
|
ctx.fillText(timeText, paddingX, y);
|
|
|
- const executorText = "执行人:张三李四";
|
|
|
+ const executorText = "执行人:" + props.imgData?.executeName;
|
|
|
ctx.font = "10px sans-serif";
|
|
|
- ctx.fillText(executorText, paddingX + 80, y);
|
|
|
+ ctx.fillText(executorText, paddingX + 90, y);
|
|
|
+
|
|
|
+ ctx.shadowBlur = 0;
|
|
|
+}
|
|
|
+
|
|
|
+// 成果复核文案(水印文字版本)
|
|
|
+// 参考 UI:「复核成效」标题 + 一段说明文字,整体靠底部显示
|
|
|
+function drawReviewEffectText(ctx, w, h) {
|
|
|
+ const paddingX = 12;
|
|
|
+ const paddingBottom = 8; // 底部留白
|
|
|
+
|
|
|
+ ctx.textBaseline = "top";
|
|
|
+ ctx.fillStyle = "#ffffff";
|
|
|
+ ctx.shadowColor = "rgba(0,0,0,0.45)";
|
|
|
+ ctx.shadowBlur = 4;
|
|
|
+
|
|
|
+ // 文案内容
|
|
|
+ const content =
|
|
|
+ props.imgData?.reCheckText;
|
|
|
+
|
|
|
+ // 按宽度自动换行,先得到所有行
|
|
|
+ ctx.font = "10px sans-serif";
|
|
|
+ const lineHeight = 15;
|
|
|
+ const maxWidth = w - paddingX * 2; // 文本最大宽度
|
|
|
+ const lines = [];
|
|
|
+ let line = "";
|
|
|
+ for (let i = 0; i < content.length; i++) {
|
|
|
+ const testLine = line + content[i];
|
|
|
+ const testWidth = ctx.measureText(testLine).width;
|
|
|
+ if (testWidth > maxWidth && line !== "") {
|
|
|
+ lines.push(line);
|
|
|
+ line = content[i];
|
|
|
+ } else {
|
|
|
+ line = testLine;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (line) {
|
|
|
+ lines.push(line);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算整体高度:标题高度 + 间距 + 正文行高
|
|
|
+ const titleFontSize = 16;
|
|
|
+ const titleGap = 4;
|
|
|
+ const textBlockHeight = lines.length * lineHeight;
|
|
|
+ const totalHeight = titleFontSize + titleGap + textBlockHeight;
|
|
|
+
|
|
|
+ // 从底部往上布局,使整体块贴近底部
|
|
|
+ const baseY = h - paddingBottom - totalHeight;
|
|
|
+
|
|
|
+ // 标题
|
|
|
+ ctx.font = "16px PangMenZhengDao";
|
|
|
+ const titleText = "复核成效";
|
|
|
+ ctx.fillText(titleText, paddingX, baseY);
|
|
|
+
|
|
|
+ // 正文
|
|
|
+ ctx.font = "10px sans-serif";
|
|
|
+ let textY = baseY + titleFontSize + titleGap;
|
|
|
+ lines.forEach((ln) => {
|
|
|
+ ctx.fillText(ln, paddingX, textY);
|
|
|
+ textY += lineHeight;
|
|
|
+ });
|
|
|
|
|
|
ctx.shadowBlur = 0;
|
|
|
}
|
|
|
|
|
|
function drawBottomMask(ctx, w, h) {
|
|
|
- const maskHeight = 60; // 和 3 行文字 + padding 精确匹配
|
|
|
+ const maskHeight = 66; // 和 3 行文字 + padding 精确匹配
|
|
|
|
|
|
ctx.fillStyle = "rgba(0,0,0,0.45)";
|
|
|
ctx.fillRect(
|
|
|
@@ -353,6 +487,22 @@ function drawBottomMask(ctx, w, h) {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
+function buildPrescriptionText(list) {
|
|
|
+ try {
|
|
|
+ return list
|
|
|
+ .map((group) =>
|
|
|
+ (group.pesticideFertilizerList || [])
|
|
|
+ .map((p) => p.defaultName || p.pesticideFertilizerName || "")
|
|
|
+ .filter(Boolean)
|
|
|
+ .join("+")
|
|
|
+ )
|
|
|
+ .filter(Boolean)
|
|
|
+ .join("+");
|
|
|
+ } catch {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
const showTagBox = ref(true); // 控制 tag-box 的显示状态
|
|
|
const hideTagBox = (event) => {
|
|
|
event.stopPropagation();
|