使用uni-app和Canvas生成简易双列表格布局并长按下载到本地

在现代Web和移动应用开发中,Canvas技术为我们提供了强大的图形绘制能力。本文将介绍如何使用uni-app框架结合Canvas技术,创建一个美观的健康风险评估报告页面,并实现长按保存功能。

实现思路

1. Canvas基础设置

首先,我们需要设置Canvas画布并获取其实际尺寸:

2. 获取Canvas尺寸

使用uni-app的API(createSelectorQuery)获取Canvas的实际尺寸:

3. 绘制双列表格

核心的绘制函数drawCanvas实现了双列表格布局,左侧key右侧对应value

完整代码如下

TypeScript 复制代码
<template>
  <view class="canvaspage">
    <canvas
      style="width: 100%; height: 100vh"
      class="canvas"
      ref="canvasbox"
      canvas-id="canvasbox"
      @longpress="downloadPic(state.createImgPath)"
    ></canvas>
  </view>
</template>

<script setup lang="ts">
import { getCanvasSize } from "@/utils/canvas";
import { ref, onMounted, getCurrentInstance, nextTick, reactive } from "vue";
import { downloadPic } from "@/utils/common";

const canvasbox = ref<HTMLCanvasElement | null | any>(null);
const instance = getCurrentInstance();
const state = reactive({
  createImgPath: "",
});

onMounted(() => {
  console.log("onMounted", instance);
  nextTick(() => {
    drawCanvas();
  });
});

async function drawCanvas() {
  const context = uni.createCanvasContext("canvasbox");
  const res = await getCanvasSize(".canvas", instance?.proxy);
  context.setFillStyle("white"); //全局背景
  const width = res.width;
  const startY = 50; // 脑卒中标准 Y轴起始位置  30为上下间距
  setCutLine(context, { x: 12, y: 30 }, width); // 分割线

  // 重新计算布局
  const leftMargin = 12; // 左边距
  const rightMargin = 12; // 右边距
  const tableWidth = width - leftMargin - rightMargin; // 表格总宽度
  const columnWidth = tableWidth / 2; // 每列宽度(平均分配)

  // 左列和右列的起始位置
  const leftColumnStart = leftMargin;
  const rightColumnStart = leftMargin + columnWidth;

  // 更精确的文本宽度估算函数
  const estimateTextWidth = (text: string) => {
    let width = 0;
    for (let i = 0; i < text.length; i++) {
      const char = text[i];
      if (/[\u0000-\u00FF]/.test(char)) {
        // 英文字符或数字
        width += 7;
      } else {
        // 中文字符
        width += 14;
      }
    }
    return width;
  };

  standardList().forEach((item: any, index: any) => {
    const y = index ? index * 30 + startY : startY;

    // 估算文本宽度
    const keyWidth = estimateTextWidth(item.key);
    const valueWidth = estimateTextWidth(item.value);

    // 计算左侧文本X坐标,使其在左列居中
    const tNumX = leftColumnStart + (columnWidth - keyWidth) / 2;
    // 计算右侧文本X坐标,使其在右列居中
    const vNumX = rightColumnStart + (columnWidth - valueWidth) / 2;

    context.setFillStyle("#333333");
    context.setFontSize(14); // 设置字体大小
    // 调整Y坐标,使文本垂直居中
    const textY = y - 2; // 向上偏移一点,使文本在行中垂直居中
    context.fillText(item.key, tNumX, textY);
    context.fillText(item.value, vNumX, textY);

    // 分割线
    setCutLine(context, { x: 12, y: y + 8 }, width);
  });

  // 画竖线
  context.lineWidth = 1;
  context.strokeStyle = "#E5E5E5";
  context.beginPath();

  const lineY = 30;
  const endY = standardList().length * 30 - 2 + lineY;
  // 竖线
  context.moveTo(leftColumnStart, lineY); // 左
  context.lineTo(leftColumnStart, endY);
  context.moveTo(rightColumnStart, lineY); // 中
  context.lineTo(rightColumnStart, endY);
  context.moveTo(width - rightMargin, lineY); // 右
  context.lineTo(width - rightMargin, endY);
  context.stroke();

  context.setFontSize(42);
  context.setFillStyle("red");
  context.setTextAlign("center");
  context.setTextBaseline("middle");
  context.fillText("长按点击下载", width / 2, res.height - 300);

  context.draw(false, () => {
    uni.canvasToTempFilePath({
      canvasId: "canvasbox",
      success: (result: any) => {
        state.createImgPath = result.tempFilePath;
      },
    });
  });
}

// 分割线
const setCutLine = (ctx: any, xy: any, width?: any) => {
  const startX = 12;
  ctx.lineWidth = 1;
  ctx.beginPath();
  ctx.moveTo(xy.x, xy.y + 0.5); // + 0.5 线像素为1px
  ctx.lineTo(width ? width - startX : startX, xy.y + 0.5);
  ctx.closePath();
  ctx.strokeStyle = "#E5E5E5";
  ctx.stroke();
};

//  标准列表
const standardList = () => {
  return [
    {
      key: "高血压",
      value: "≥140/90mmHg",
    },
    {
      key: "血脂情况",
      value: "血脂异常或不知道",
    },
    {
      key: "糖尿病",
      value: "有",
    },
    {
      key: "心房颤动",
      value: "心跳不规则",
    },
    {
      key: "吸烟",
      value: "有",
    },
    {
      key: "体重",
      value: "明显超重或肥胖",
    },
    {
      key: "运动",
      value: "缺乏运动",
    },
    {
      key: "是否抽烟",
      value: "是的",
    },
    {
      key: "是否喝酒",
      value: "是的",
    },
    {
      key: "是否患有精神洁癖",
      value: "不曾",
    },
  ];
};
</script>

<style scoped lang="scss">
.canvaspage {
  width: 100%;
  min-height: 100vh;
  box-sizing: border-box;
  padding: 32rpx;
}
</style>

应用场景

这种技术可以广泛应用于:

健康评估报告生成

电子病历可视化

体检结果展示

医疗数据报告

引入方法补充

getCanvasSize方法

TypeScript 复制代码
//获取canvas画布的实际尺寸
export const getCanvasSize = (classname: any, nodeInfo: any): Promise<any> => {
  return new Promise((resolve, reject) => {
    const query = uni.createSelectorQuery().in(nodeInfo);
    query
      .select(classname)
      .fields({ size: true }, (res: any) => {
        if (res) {
          resolve(res);
        } else {
          reject("获取画布尺寸失败");
        }
      })
      .exec();
  });
};

downpic方法--参考本篇文章:

uni微信小程序实现保存图片到本地_uniapp保存图片到相册-CSDN博客

希望本文对你在uni-app中使用Canvas有所帮助!

相关推荐
anyup15 小时前
太全面啦!总结篇!99% 开发者可能都会遇到的 uView Pro 组件库问题
前端·vue.js·uni-app
iOS阿玮20 小时前
苹果市场常见的处罚邮件,最低处罚基本上听劝稳过。
uni-app·app·apple
xkxnq20 小时前
UniApp 基础开发第一步:HBuilderX 安装与环境配置
uni-app
xkxnq20 小时前
Uniapp 项目创建与目录结构解析
uni-app
少油少盐不要辣1 天前
uniapp小程序使用自定义底部tabbar,并根据用户类型动态切换tabbar数据
小程序·uni-app
复苏季风2 天前
UniApp 小白入门实战专栏①:从安装 HBuilderX 开始,3 步跑通第一个项目
前端·微信小程序·uni-app
雪芽蓝域zzs2 天前
uniapp解析富文本,视频无法显示问题
uni-app·音视频
2501_915918412 天前
uni-app 跨平台项目的 iOS 上架流程:多工具组合的高效协作方案
android·ios·小程序·https·uni-app·iphone·webview
lumi.2 天前
HarmonyOS image组件深度解析:多场景应用与性能优化指南(2.4详细解析,完整见uniapp官网)
前端·javascript·小程序·uni-app·html·css3