挑战使用cursor写微信小程序工具——标尺工具

43 阅读3分钟
  • 使用工具:微信小程序开发工具,cursor编码工具
  • 技术方案: 通过微信小程序api获取手机设备名称,通过手机设备名称获取手机设备对应的ppi值,通过ppi值计算屏幕的一个像素代表多少毫米,提供给canvas绘制卡尺界面。支持滑动显示当前长度。

wxml页面内容是一个简单画布和一个标记

<!--pages/ruler/ruler.wxml-->
<view class="ruler-container">
  <canvas type="2d" id="rulerCanvas" class="ruler-canvas" bindtouchmove="onTouchMove" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd"></canvas>
  <view class="selected-mark {{isMarked ? 'show' : ''}}" style="height: {{markPosition}}px;">
    <view class="mark-line"></view>
    <view class="mark-label">{{currentLength}}mm</view>
  </view>
</view>
/* pages/ruler/ruler.wxss */
.ruler-container {
  width: 100%;
  height: 100vh;
  background: #E5D3B3;
  position: relative;
  overflow: hidden;
  background-image: 
    repeating-linear-gradient(
      90deg,
      rgba(139, 69, 19, 0.05) 0px,
      rgba(139, 69, 19, 0.05) 2px,
      transparent 2px,
      transparent 20px
    );
}

.ruler-canvas {
  width: 100%;
  height: 100vh;
  background: transparent;
}

.measurement {
  position: fixed;
  top: 40rpx;
  right: 40rpx;
  background: rgba(0, 0, 0, 0.7);
  color: #fff;
  padding: 10rpx 20rpx;
  border-radius: 10rpx;
  font-size: 32rpx;
}

.selected-mark {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s;
  display: flex;
  align-items: center; 
}

.selected-mark.show {
  opacity: 1;
}

.mark-line {
  width: 100%;
  height: 100%;
  background: rgba(0, 122, 255, 0.2);
  border-top: 1px solid rgba(0, 122, 255, 0.5);
  border-bottom: 1px solid rgba(0, 122, 255, 0.5);
}

.mark-label {
  position: absolute;
  right: 20px;
  background: rgba(0, 122, 255, 0.9);
  color: #fff;
  padding: 4px 12px;
  border-radius: 15px;
  font-size: 14px;
  transform: none;
  white-space: nowrap;
}

.calibrate-btn {
  position: fixed;
  bottom: 40rpx;
  right: 40rpx;
  background: rgba(0, 122, 255, 0.9);
  color: #fff;
  padding: 20rpx 40rpx;
  border-radius: 10rpx;
  font-size: 28rpx;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}

js中从接口获取设备PPI的接口需要自己写,因为微信小程序获取的手机型号没有一个标准,所有我的做法是用户带手机型号请求,如果查到就用具体的值,如果查不到我新建一个我后续补充。

// pages/ruler/ruler.js
Page({

  /**
   * 页面的初始数据
   */
  data: {
    currentLength: 0,
    pixelRatio: 1,
    ppi: 0,
    calibrationValue: 1.0, // 校准系数
    standardLength: 50,    // 校准用标准长度(毫米)
    isMarked: false,
    markPosition: 0,
    deviceInfo: {},
    rulerLength: 100,
    showTip: true
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad(options) {
    this.initRuler();
  },

  async initRuler() {
    const windowInfo = wx.getWindowInfo()
    const deviceInfo = wx.getDeviceInfo()

    // 获取设备信息
    this.setData({ pixelRatio: windowInfo.pixelRatio });

    // 先从本地获取PPI
    this.getPPIFromStorage(deviceInfo.model);
  },

  // 从本地存储获取PPI
  getPPIFromStorage(deviceName) {
    try {
      const ppi = wx.getStorageSync(`device_ppi_${deviceName}`);
      if (ppi) {
        // 本地存在PPI数据
        this.setData({ ppi });
        this.drawRulerWithCalibration();
      } else {
        // 本地无数据,从接口获取
        this.getDevicePPI(deviceName);
      }
    } catch (e) {
      console.error('读取本地PPI失败:', e);
      // 读取失败也从接口获取
      this.getDevicePPI(deviceName);
    }
  },

  // 从接口获取设备PPI
  getDevicePPI(deviceName) {
    wx.request({
      url: 'https://xxx.xxx.com/device-ppi/get-ppi',
      method: 'GET',
      data: {
        deviceName: deviceName
      },
      success: (res) => {
        if (res.data === 0) {
          wx.showToast({
            title: '未适配该手机型号',
            icon: 'none',
            duration: 2000
          });
        } else {
          // 更新数据
          this.setData({ ppi: res.data });
          // 保存到本地
          this.savePPIToStorage(deviceName, res.data);
          this.drawRulerWithCalibration();
        }
      },
      fail: (err) => {
        console.error('获取PPI失败:', err);
        wx.showToast({
          title: '获取PPI失败',
          icon: 'none'
        });
      }
    });
  },

  // 保存PPI到本地存储
  savePPIToStorage(deviceName, ppi) {
    try {
      wx.setStorageSync(`device_ppi_${deviceName}`, ppi);
    } catch (e) {
      console.error('保存PPI到本地失败:', e);
    }
  },

  drawRulerWithCalibration() {
    const query = wx.createSelectorQuery();
    query.select('#rulerCanvas')
      .fields({ node: true, size: true })
      .exec((res) => {
        const canvas = res[0].node;
        const ctx = canvas.getContext('2d');
        canvas.width = res[0].width * this.data.pixelRatio;
        canvas.height = res[0].height * this.data.pixelRatio;
        this.drawRuler(ctx, canvas.width, canvas.height);
      });
  },

  drawRuler(ctx, width, height) {
    const { pixelRatio, ppi, calibrationValue } = this.data;
    // 计算每毫米对应的像素数
    const pxPerMM = (ppi / 25.4) * calibrationValue;

    ctx.clearRect(0, 0, width, height);

    // 设置透明背景
    ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
    ctx.fillRect(0, 0, width * 0.5, height);

    ctx.beginPath();
    ctx.strokeStyle = '#000';
    ctx.fillStyle = '#000';
    ctx.lineWidth = pixelRatio;

    // 计算可显示的总毫米数
    const totalMM = Math.floor(height / pxPerMM);

    // 绘制刻度
    for (let i = 0; i <= totalMM; i++) {
      const y = i * pxPerMM;
      // 厘米刻度
      if (i % 10 === 0) {
        ctx.moveTo(0, y);
        ctx.lineTo(width * 0.4, y);

        ctx.font = `${14 * pixelRatio}px Arial`;
        ctx.textAlign = 'left';
        ctx.textBaseline = 'middle';
        ctx.fillText(`${i / 10}`, width * 0.45, y);
      }
      // 5毫米刻度
      else if (i % 5 === 0) {
        ctx.moveTo(0, y);
        ctx.lineTo(width * 0.3, y);
      }
      // 1毫米刻度
      else {
        ctx.moveTo(0, y);
        ctx.lineTo(width * 0.2, y);
      }
    }

    ctx.stroke();
  },

  onTouchStart(e) {
    const touchY = e.touches[0].clientY;
    this.updateMark(touchY);
  },

  onTouchMove(e) {
    const touchY = e.touches[0].clientY;
    this.updateMark(touchY);
  },

  onTouchEnd() {
    // 可选:是否在触摸结束后保持标记显示
    // this.setData({ isMarked: false });
  },

  updateMark(touchY) {
    const { ppi, calibrationValue } = this.data;
    const pxPerMM = (ppi / 25.4) * calibrationValue;

    // 计算实际毫米数
    const mm = Math.floor(touchY * this.data.pixelRatio / pxPerMM);
    this.setData({
      currentLength: mm,
      isMarked: true,
      markPosition: touchY
    });
  },

  onShareAppMessage() {
    return {
      title: '便捷标尺工具',
      path: '/pages/ruler/ruler',
      imageUrl: '/assets/images/ruler-icon.png'
    }
  },

  onShareTimeline() {
    return {
      title: '便捷标尺工具',
      query: '',
      imageUrl: '/assets/images/ruler-icon.png'
    }
  }
})
{
  "usingComponents": {},
  "enablePullDownRefresh": false,
  "disableScroll": true,
  "navigationBarTitleText": "标尺工具"
}

以上内容除了从接口获取设备PPI接口,都有cursor编辑,我们只需要描述自己想要的效果。

在使用cursor的时候建议一小步一小步的修改更为精准

  • [待优化] 在精确到小于1mm的刻度

最后附上我的小程序,可通过扫码体验效果。欢迎留言,提意见或建议。

gh_385eb4777c22_1280.jpg