Mini Program Custom Poster

As shown in the picture, the WeChat applet generates a poster to customize and adjust the position

 

 

//微信小程序组件 poster.wxml

<view style='position: relative;{
   
   {customStyle}};{
   
   {painterStyle}}'>
  <block wx:if="{
   
   {!use2D}}">
    <canvas canvas-id="photo" style="{
   
   {photoStyle}};position: absolute; left: -9999px; top: -9999rpx;" />
    <block wx:if="{
   
   {dancePalette}}">
      <canvas canvas-id="bottom" style="{
   
   {painterStyle}};position: absolute;" />
      <canvas canvas-id="k-canvas" style="{
   
   {painterStyle}};position: absolute;" />
      <canvas canvas-id="top" style="{
   
   {painterStyle}};position: absolute;" />
      <canvas 
        canvas-id="front" 
        style="{
   
   {painterStyle}};position: absolute;"
        bindtouchstart="onTouchStart"
        bindtouchmove="onTouchMove"
        bindtouchend="onTouchEnd"
        bindtouchcancel="onTouchCancel"
        disable-scroll="{
   
   {true}}" />
      </block>
  </block>
  <block wx:if="{
   
   {use2D}}">
    <canvas type="2d" id="photo" style="{
   
   {photoStyle}};" />
  </block>
</view>
//组件poster.js

import Pen, { penCache, clearPenCache } from './lib/pen';
import Downloader from './lib/downloader';
import WxCanvas from './lib/wx-canvas';

const util = require('./lib/util');
const calc = require('./lib/calc');

const downloader = new Downloader();

// 最大尝试的绘制次数
const MAX_PAINT_COUNT = 5;
const ACTION_DEFAULT_SIZE = 24;
const ACTION_OFFSET = '2rpx';
Component({
  canvasWidthInPx: 0,
  canvasHeightInPx: 0,
  canvasNode: null,
  paintCount: 0,
  currentPalette: {},
  outterDisabled: false,
  isDisabled: false,
  needClear: false,
  /**
   * 组件的属性列表
   */
  properties: {
    use2D: {
      type: Boolean,
    },
    customStyle: {
      type: String,
    },
    // 运行自定义选择框和删除缩放按钮
    customActionStyle: {
      type: Object,
    },
    palette: {
      type: Object,
      observer: function (newVal, oldVal) {
        if (this.isNeedRefresh(newVal, oldVal)) {
          this.paintCount = 0;
          clearPenCache();
          this.startPaint();
        }
      },
    },
    dancePalette: {
      type: Object,
      observer: function (newVal, oldVal) {
        if (!this.isEmpty(newVal) && !this.properties.use2D) {
          clearPenCache();
          this.initDancePalette(newVal);
        }
      },
    },
    // 缩放比,会在传入的 palette 中统一乘以该缩放比
    scaleRatio: {
      type: Number,
      value: 1,
    },
    widthPixels: {
      type: Number,
      value: 0,
    },
    // 启用脏检查,默认 false
    dirty: {
      type: Boolean,
      value: false,
    },
    LRU: {
      type: Boolean,
      value: false,
    },
    action: {
      type: Object,
      observer: function (newVal, oldVal) {
        if (newVal && !this.isEmpty(newVal) && !this.properties.use2D) {
          this.doAction(newVal, null, false, true);
        }
      },
    },
    disableAction: {
      type: Boolean,
      observer: function (isDisabled) {
        this.outterDisabled = isDisabled;
        this.isDisabled = isDisabled;
      },
    },
    clearActionBox: {
      type: Boolean,
      observer: function (needClear) {
        if (needClear && !this.needClear) {
          if (this.frontContext) {
            setTimeout(() => {
              this.frontContext.draw();
            }, 100);
            this.touchedView = {};
            this.prevFindedIndex = this.findedIndex;
            this.findedIndex = -1;
          }
        }
        this.needClear = needClear;
      },
    },
  },

  data: {
    picURL: '',
    showCanvas: true,
    painterStyle: '',
  },

  methods: {
    /**
     * 判断一个 object 是否为 空
     * @param {object} object
     */
    isEmpty(object) {
      for (const i in object) {
        return false;
      }
      return true;
    },

    isNeedRefresh(newVal, oldVal) {
      if (!newVal || this.isEmpty(newVal) || (this.data.dirty && util.equal(newVal, oldVal))) {
        return false;
      }
      return true;
    },

    getBox(rect, type) {
      const boxArea = {
        type: 'rect',
        css: {
          height: `${rect.bottom - rect.top}px`,
          width: `${rect.right - rect.left}px`,
          left: `${rect.left}px`,
          top: `${rect.top}px`,
          borderWidth: '4rpx',
          borderColor: '#1A7AF8',
          color: 'transparent',
        },
      };
      if (type === 'text') {
        boxArea.css = Object.assign({}, boxArea.css, {
          borderStyle: 'dashed',
        });
      }
      if (this.properties.customActionStyle && this.properties.customActionStyle.border) {
        boxArea.css = Object.assign({}, boxArea.css, this.properties.customActionStyle.border);
      }
      Object.assign(boxArea, {
        id: 'box',
      });
      return boxArea;
    },

    getScaleIcon(rect, type) {
      let scaleArea = {};
      const { customActionStyle } = this.properties;
      if (customActionStyle && customActionStyle.scale) {
        scaleArea = {
          type: 'image',
          url: type === 'text' ? customActionStyle.scale.textIcon : customActionStyle.scale.imageIcon,
          css: {
            height: `${2 * ACTION_DEFAULT_SIZE}rpx`,
            width: `${2 * ACTION_DEFAULT_SIZE}rpx`,
            borderRadius: `${ACTION_DEFAULT_SIZE}rpx`,
          },
        };
      } else {
        scaleArea = {
          type: 'rect',
          css: {
            height: `${2 * ACTION_DEFAULT_SIZE}rpx`,
            width: `${2 * ACTION_DEFAULT_SIZE}rpx`,
            borderRadius: `${ACTION_DEFAULT_SIZE}rpx`,
            color: '#0000ff',
          },
        };
      }
      scaleArea.css = Object.assign({}, scaleArea.css, {
        align: 'center',
        left: `${rect.right + ACTION_OFFSET.toPx()}px`,
        top:
          type === 'text'
            ? `${rect.top - ACTION_OFFSET.toPx() - scaleArea.css.height.toPx() / 2}px`
            : `${rect.bottom - ACTION_OFFSET.toPx() - scaleArea.css.height.toPx() / 2}px`,
      });
      Object.assign(scaleArea, {
        id: 'scale',
      });
      return scaleArea;
    },

    getDeleteIcon(rect) {
      let deleteArea = {};
      const { customActionStyle } = this.properties;
      if (customActionStyle && customActionStyle.scale) {
        deleteArea = {
          type: 'image',
          url: customActionStyle.delete.icon,
          css: {
            height: `${2 * ACTION_DEFAULT_SIZE}rpx`,
            width: `${2 * ACTION_DEFAULT_SIZE}rpx`,
            borderRadius: `${ACTION_DEFAULT_SIZE}rpx`,
          },
        };
      } else {
        deleteArea = {
          type: 'rect',
          css: {
            height: `${2 * ACTION_DEFAULT_SIZE}rpx`,
            width: `${2 * ACTION_DEFAULT_SIZE}rpx`,
            borderRadius: `${ACTION_DEFAULT_SIZE}rpx`,
            color: '#0000ff',
          },
        };
      }
      deleteArea.css = Object.assign({}, deleteArea.css, {
        align: 'center',
        left: `${rect.left - ACTION_OFFSET.toPx()}px`,
        top: `${rect.top - ACTION_OFFSET.toPx() - deleteArea.css.height.toPx() / 2}px`,
      });
      Object.assign(deleteArea, {
        id: 'delete',
      });
      return deleteArea;
    },

    doAction(action, callback, isMoving, overwrite) {
      if (this.properties.use2D) {
        return;
      }
      let newVal = null;
      if (action) {
        newVal = action.view;
      }
      if (newVal && newVal.id && this.touchedView.id !== newVal.id) {
        // 带 id 的动作给撤回时使用,不带 id,表示对当前选中对象进行操作
        const { views } = this.currentPalette;
        for (let i = 0; i < views.length; i++) {
          if (views[i].id === newVal.id) {
            // 跨层回撤,需要重新构建三层关系
            this.touchedView = views[i];
            this.findedIndex = i;
            this.sliceLayers();
            break;
          }
        }
      }

      const doView = this.touchedView;

      if (!doView || this.isEmpty(doView)) {
        return;
      }
      if (newVal && newVal.css) {
        if (overwrite) {
          doView.css = newVal.css;
        } else if (Array.isArray(doView.css) && Array.isArray(newVal.css)) {
          doView.css = Object.assign({}, ...doView.css, ...newVal.css);
        } else if (Array.isArray(doView.css)) {
          doView.css = Object.assign({}, ...doView.css, newVal.css);
        } else if (Array.isArray(newVal.css)) {
          doView.css = Object.assign({}, doView.css, ...newVal.css);
        } else {
          doView.css = Object.assign({}, doView.css, newVal.css);
        }
      }
      if (newVal && newVal.rect) {
        doView.rect = newVal.rect;
      }
      if (newVal && newVal.url && doView.url && newVal.url !== doView.url) {
        downloader
          .download(newVal.url, this.properties.LRU)
          .then(path => {
            if (newVal.url.startsWith('https')) {
              doView.originUrl = newVal.url;
            }
            doView.url = path;
            wx.getImageInfo({
              src: path,
              success: res => {
                doView.sHeight = res.height;
                doView.sWidth = res.width;
                this.reDraw(doView, callback, isMoving);
              },
              fail: () => {
                this.reDraw(doView, callback, isMoving);
              },
            });
          })
          .catch(error => {
            // 未下载成功,直接绘制
            console.error(error);
            this.reDraw(doView, callback, isMoving);
          });
      } else {
        newVal && newVal.text && doView.text && newVal.text !== doView.text && (doView.text = newVal.text);
        newVal &&
          newVal.content &&
          doView.content &&
          newVal.content !== doView.content &&
          (doView.content = newVal.content);
        this.reDraw(doView, callback, isMoving);
      }
    },

    reDraw(doView, callback, isMoving) {
      const draw = {
        width: this.currentPalette.width,
        height: this.currentPalette.height,
        views: this.isEmpty(doView) ? [] : [doView],
      };
      const pen = new Pen(this.globalContext, draw);

      pen.paint(callbackInfo => {
        callback && callback(callbackInfo);
        this.triggerEvent('viewUpdate', {
          view: this.touchedView,
        });
      });

      const { rect, css, type } = doView;

      this.block = {
        width: this.currentPalette.width,
        height: this.currentPalette.height,
        views: this.isEmpty(doView) ? [] : [this.getBox(rect, doView.type)],
      };
      if (css && css.scalable) {
        this.block.views.push(this.getScaleIcon(rect, type));
      }
      if (css && css.deletable) {
        this.block.views.push(this.getDeleteIcon(rect));
      }
      const topBlock = new Pen(this.frontContext, this.block);
      topBlock.paint();
    },

    isInView(x, y, rect) {
      return x > rect.left && y > rect.top && x < rect.right && y < rect.bottom;
    },

    isInDelete(x, y) {
      for (const view of this.block.views) {
        if (view.id === 'delete') {
          return x > view.rect.left && y > view.rect.top && x < view.rect.right && y < view.rect.bottom;
        }
      }
      return false;
    },

    isInScale(x, y) {
      for (const view of this.block.views) {
        if (view.id === 'scale') {
          return x > view.rect.left && y > view.rect.top && x < view.rect.right && y < view.rect.bottom;
        }
      }
      return false;
    },

    touchedView: {},
    findedIndex: -1,
    onClick() {
      const x = this.startX;
      const y = this.startY;
      const totalLayerCount = this.currentPalette.views.length;
      let canBeTouched = [];
      let isDelete = false;
      let deleteIndex = -1;
      for (let i = totalLayerCount - 1; i >= 0; i--) {
        const view = this.currentPalette.views[i];
        const { rect } = view;
        if (this.touchedView && this.touchedView.id && this.touchedView.id === view.id && this.isInDelete(x, y, rect)) {
          canBeTouched.length = 0;
          deleteIndex = i;
          isDelete = true;
          break;
        }
        if (this.isInView(x, y, rect)) {
          canBeTouched.push({
            view,
            index: i,
          });
        }
      }
      this.touchedView = {};
      if (canBeTouched.length === 0) {
        this.findedIndex = -1;
      } else {
        let i = 0;
        const touchAble = canBeTouched.filter(item => Boolean(item.view.id));
        if (touchAble.length === 0) {
          this.findedIndex = canBeTouched[0].index;
        } else {
          for (i = 0; i < touchAble.length; i++) {
            if (this.findedIndex === touchAble[i].index) {
              i++;
              break;
            }
          }
          if (i === touchAble.length) {
            i = 0;
          }
          this.touchedView = touchAble[i].view;
          this.findedIndex = touchAble[i].index;
          this.triggerEvent('viewClicked', {
            view: this.touchedView,
          });
        }
      }
      if (this.findedIndex < 0 || (this.touchedView && !this.touchedView.id)) {
        // 证明点击了背景 或无法移动的view
        this.frontContext.draw();
        if (isDelete) {
          this.triggerEvent('touchEnd', {
            view: this.currentPalette.views[deleteIndex],
            index: deleteIndex,
            type: 'delete',
          });
          this.doAction();
        } else if (this.findedIndex < 0) {
          this.triggerEvent('viewClicked', {});
        }
        this.findedIndex = -1;
        this.prevFindedIndex = -1;
      } else if (this.touchedView && this.touchedView.id) {
        this.sliceLayers();
      }
    },

    sliceLayers() {
      const bottomLayers = this.currentPalette.views.slice(0, this.findedIndex);
      const topLayers = this.currentPalette.views.slice(this.findedIndex + 1);
      const bottomDraw = {
        width: this.currentPalette.width,
        height: this.currentPalette.height,
        background: this.currentPalette.background,
        views: bottomLayers,
      };
      const topDraw = {
        width: this.currentPalette.width,
        height: this.currentPalette.height,
        views: topLayers,
      };
      if (this.prevFindedIndex < this.findedIndex) {
        new Pen(this.bottomContext, bottomDraw).paint();
        this.doAction();
        new Pen(this.topContext, topDraw).paint();
      } else {
        new Pen(this.topContext, topDraw).paint();
        this.doAction();
        new Pen(this.bottomContext, bottomDraw).paint();
      }
      this.prevFindedIndex = this.findedIndex;
    },

    startX: 0,
    startY: 0,
    startH: 0,
    startW: 0,
    isScale: false,
    startTimeStamp: 0,
    onTouchStart(event) {
      if (this.isDisabled) {
        return;
      }
      const { x, y } = event.touches[0];
      this.startX = x;
      this.startY = y;
      this.startTimeStamp = new Date().getTime();
      if (this.touchedView && !this.isEmpty(this.touchedView)) {
        const { rect } = this.touchedView;
        if (this.isInScale(x, y, rect)) {
          this.isScale = true;
          this.startH = rect.bottom - rect.top;
          this.startW = rect.right - rect.left;
        } else {
          this.isScale = false;
        }
      } else {
        this.isScale = false;
      }
    },

    onTouchEnd(e) {
      if (this.isDisabled) {
        return;
      }
      const current = new Date().getTime();
      if (current - this.startTimeStamp <= 500 && !this.hasMove) {
        !this.isScale && this.onClick(e);
      } else if (this.touchedView && !this.isEmpty(this.touchedView)) {
        this.triggerEvent('touchEnd', {
          view: this.touchedView,
        });
      }
      this.hasMove = false;
    },

    onTouchCancel(e) {
      if (this.isDisabled) {
        return;
      }
      this.onTouchEnd(e);
    },

    hasMove: false,
    onTouchMove(event) {
      if (this.isDisabled) {
        return;
      }
      this.hasMove = true;
      if (!this.touchedView || (this.touchedView && !this.touchedView.id)) {
        return;
      }
      const { x, y } = event.touches[0];
      const offsetX = x - this.startX;
      const offsetY = y - this.startY;
      const { rect, type } = this.touchedView;
      let css = {};
      if (this.isScale) {
        clearPenCache(this.touchedView.id);
        const newW = this.startW + offsetX > 1 ? this.startW + offsetX : 1;
        if (this.touchedView.css && this.touchedView.css.minWidth) {
          if (newW < this.touchedView.css.minWidth.toPx()) {
            return;
          }
        }
        if (this.touchedView.rect && this.touchedView.rect.minWidth) {
          if (newW < this.touchedView.rect.minWidth) {
            return;
          }
        }
        const newH = this.startH + offsetY > 1 ? this.startH + offsetY : 1;
        css = {
          width: `${newW}px`,
        };
        if (type !== 'text') {
          if (type === 'image') {
            css.height = `${(newW * this.startH) / this.startW}px`;
          } else {
            css.height = `${newH}px`;
          }
        }
      } else {
        this.startX = x;
        this.startY = y;
        css = {
          left: `${rect.x + offsetX}px`,
          top: `${rect.y + offsetY}px`,
          right: undefined,
          bottom: undefined,
        };
      }
      this.doAction(
        {
          view: {
            css,
          },
        },
        null,
        !this.isScale,
      );
    },

    initScreenK() {
      if (!(getApp() && getApp().systemInfo && getApp().systemInfo.screenWidth)) {
        try {
          getApp().systemInfo = wx.getSystemInfoSync();
        } catch (e) {
          console.error(`Painter get system info failed, ${JSON.stringify(e)}`);
          return;
        }
      }
      this.screenK = 0.5;
      if (getApp() && getApp().systemInfo && getApp().systemInfo.screenWidth) {
        this.screenK = getApp().systemInfo.screenWidth / 750;
      }
      setStringPrototype(this.screenK, this.properties.scaleRatio);
    },

    initDancePalette() {
      if (this.properties.use2D) {
        return;
      }
      this.isDisabled = true;
      this.initScreenK();
      this.downloadImages(this.properties.dancePalette).then(async palette => {
        this.currentPalette = palette;
        const { width, height } = palette;

        if (!width || !height) {
          console.error(`You should set width and height correctly for painter, width: ${width}, height: ${height}`);
          return;
        }
        this.setData({
          painterStyle: `width:${width.toPx()}px;height:${height.toPx()}px;`,
        });
        this.frontContext || (this.frontContext = await this.getCanvasContext(this.properties.use2D, 'front'));
        this.bottomContext || (this.bottomContext = await this.getCanvasContext(this.properties.use2D, 'bottom'));
        this.topContext || (this.topContext = await this.getCanvasContext(this.properties.use2D, 'top'));
        this.globalContext || (this.globalContext = await this.getCanvasContext(this.properties.use2D, 'k-canvas'));
        new Pen(this.bottomContext, palette, this.properties.use2D).paint(() => {
          this.isDisabled = false;
          this.isDisabled = this.outterDisabled;
          this.triggerEvent('didShow');
        });
        this.globalContext.draw();
        this.frontContext.draw();
        this.topContext.draw();
      });
      this.touchedView = {};
    },

    startPaint() {
      this.initScreenK();
      const { width, height } = this.properties.palette;

      if (!width || !height) {
        console.error(`You should set width and height correctly for painter, width: ${width}, height: ${height}`);
        return;
      }

      let needScale = false;
      // 生成图片时,根据设置的像素值重新绘制
      if (width.toPx() !== this.canvasWidthInPx) {
        this.canvasWidthInPx = width.toPx();
        needScale = this.properties.use2D;
      }
      if (this.properties.widthPixels) {
        setStringPrototype(this.screenK, this.properties.widthPixels / this.canvasWidthInPx);
        this.canvasWidthInPx = this.properties.widthPixels;
      }

      if (this.canvasHeightInPx !== height.toPx()) {
        this.canvasHeightInPx = height.toPx();
        needScale = needScale || this.properties.use2D;
      }
      this.setData(
        {
          photoStyle: `width:${this.canvasWidthInPx}px;height:${this.canvasHeightInPx}px;`,
        },
        function () {
          this.downloadImages(this.properties.palette).then(async palette => {
            if (!this.photoContext) {
              this.photoContext = await this.getCanvasContext(this.properties.use2D, 'photo');
            }
            if (needScale) {
              const scale = getApp().systemInfo.pixelRatio;
              this.photoContext.width = this.canvasWidthInPx * scale;
              this.photoContext.height = this.canvasHeightInPx * scale;
              this.photoContext.scale(scale, scale);
            }
            new Pen(this.photoContext, palette).paint(() => {
              this.saveImgToLocal();
            });
            setStringPrototype(this.screenK, this.properties.scaleRatio);
          });
        },
      );
    },

    downloadImages(palette) {
      return new Promise((resolve, reject) => {
        let preCount = 0;
        let completeCount = 0;
        const paletteCopy = JSON.parse(JSON.stringify(palette));
        if (paletteCopy.background) {
          preCount++;
          downloader.download(paletteCopy.background, this.properties.LRU).then(
            path => {
              paletteCopy.background = path;
              completeCount++;
              if (preCount === completeCount) {
                resolve(paletteCopy);
              }
            },
            () => {
              completeCount++;
              if (preCount === completeCount) {
                resolve(paletteCopy);
              }
            },
          );
        }
        if (paletteCopy.views) {
          for (const view of paletteCopy.views) {
            if (view && view.type === 'image' && view.url) {
              preCount++;
              /* eslint-disable no-loop-func */
              downloader.download(view.url, this.properties.LRU).then(
                path => {
                  view.originUrl = view.url;
                  view.url = path;
                  wx.getImageInfo({
                    src: path,
                    success: res => {
                      // 获得一下图片信息,供后续裁减使用
                      view.sWidth = res.width;
                      view.sHeight = res.height;
                    },
                    fail: error => {
                      // 如果图片坏了,则直接置空,防止坑爹的 canvas 画崩溃了
                      console.warn(`getImageInfo ${view.originUrl} failed, ${JSON.stringify(error)}`);
                      view.url = '';
                    },
                    complete: () => {
                      completeCount++;
                      if (preCount === completeCount) {
                        resolve(paletteCopy);
                      }
                    },
                  });
                },
                () => {
                  completeCount++;
                  if (preCount === completeCount) {
                    resolve(paletteCopy);
                  }
                },
              );
            }
          }
        }
        if (preCount === 0) {
          resolve(paletteCopy);
        }
      });
    },

    saveImgToLocal() {
      const that = this;
      const optionsOf2d = {
        canvas: that.canvasNode,
      }
      const optionsOfOld = {
        canvasId: 'photo',
        destWidth: that.canvasWidthInPx,
        destHeight: that.canvasHeightInPx,
      }
      setTimeout(() => {
        wx.canvasToTempFilePath(
          {
            ...(that.properties.use2D ? optionsOf2d : optionsOfOld),
            success: function (res) {
              that.getImageInfo(res.tempFilePath);
            },
            fail: function (error) {
              console.error(`canvasToTempFilePath failed, ${JSON.stringify(error)}`);
              that.triggerEvent('imgErr', {
                error: error,
              });
            },
          },
          this,
        );
      }, 300);
    },

    getCanvasContext(use2D, id) {
      const that = this;
      return new Promise(resolve => {
        if (use2D) {
          const query = wx.createSelectorQuery().in(that);
          const selectId = `#${id}`;
          query
            .select(selectId)
            .fields({ node: true, size: true })
            .exec(res => {
              that.canvasNode = res[0].node;
              const ctx = that.canvasNode.getContext('2d');
              const wxCanvas = new WxCanvas('2d', ctx, id, true, that.canvasNode);
              resolve(wxCanvas);
            });
        } else {
          const temp = wx.createCanvasContext(id, that);
          resolve(new WxCanvas('mina', temp, id, true));
        }
      });
    },

    getImageInfo(filePath) {
      const that = this;
      wx.getImageInfo({
        src: filePath,
        success: infoRes => {
          if (that.paintCount > MAX_PAINT_COUNT) {
            const error = `The result is always fault, even we tried ${MAX_PAINT_COUNT} times`;
            console.error(error);
            that.triggerEvent('imgErr', {
              error: error,
            });
            return;
          }
          // 比例相符时才证明绘制成功,否则进行强制重绘制
          if (
            Math.abs(
              (infoRes.width * that.canvasHeightInPx - that.canvasWidthInPx * infoRes.height) /
                (infoRes.height * that.canvasHeightInPx),
            ) < 0.01
          ) {
            that.triggerEvent('imgOK', {
              path: filePath,
            });
          } else {
            that.startPaint();
          }
          that.paintCount++;
        },
        fail: error => {
          console.error(`getImageInfo failed, ${JSON.stringify(error)}`);
          that.triggerEvent('imgErr', {
            error: error,
          });
        },
      });
    },
  },
});

function setStringPrototype(screenK, scale) {
  /* eslint-disable no-extend-native */
  /**
   * string 到对应的 px
   * @param {Number} baseSize 当设置了 % 号时,设置的基准值
   */
  String.prototype.toPx = function toPx(_, baseSize) {
    if (this === '0') {
      return 0;
    }
    const REG = /-?[0-9]+(\.[0-9]+)?(rpx|px|%)/;

    const parsePx = origin => {
      const results = new RegExp(REG).exec(origin);
      if (!origin || !results) {
        console.error(`The size: ${origin} is illegal`);
        return 0;
      }
      const unit = results[2];
      const value = parseFloat(origin);

      let res = 0;
      if (unit === 'rpx') {
        res = Math.round(value * (screenK || 0.5) * (scale || 1));
      } else if (unit === 'px') {
        res = Math.round(value * (scale || 1));
      } else if (unit === '%') {
        res = Math.round((value * baseSize) / 100);
      }
      return res;
    };
    const formula = /^calc\((.+)\)$/.exec(this);
    if (formula && formula[1]) {
      // 进行 calc 计算
      const afterOne = formula[1].replace(/([^\s\(\+\-\*\/]+)\.(left|right|bottom|top|width|height)/g, word => {
        const [id, attr] = word.split('.');
        return penCache.viewRect[id][attr];
      });
      const afterTwo = afterOne.replace(new RegExp(REG, 'g'), parsePx);
      return calc(afterTwo);
    } else {
      return parsePx(this);
    }
  };
}

References in the project:

<view class="shopView">
  <!-- 轮播 -->
  <view class="swp">
    <zswiper style="position: relative;" SwiperList="{
   
   {shopBase.slider_image_arr}}" current="{
   
   {current}}" bind:monitorCurrent="monitorCurrent"></zswiper>
    <view class="shopHead">
      <view><text>让利价:</text>¥{
   
   {shopBase.price}}</view>
      <view>市场价: ¥{
   
   {shopBase.ot_price}}</view>
    </view>
  </view>
  <!-- 介绍 -->
  <view class="shopRemark">
    <view class="shopTitle hiddenD">{
   
   {shopBase.title}}</view>
    <view class="shopbag">{
   
   {shopBase.keyword}}</view>
    <view class="shopyy">
      <view class="shopRadioView">剩余{
   
   {shopBase.stock}}{
   
   {shopBase.unit_name}} | 月销量{
   
   {shopBase.ficti }}{
   
   {shopBase.unit_name}}</view>
    </view>
    <view class="shopyys">
      <view class="colView">
        <image src="../../images/all/hd.png" mode="" /> <text>品质保障</text>
      </view>
      <view class="colView">
        <image src="../../images/all/twy.png" mode="" /> <text>退换无忧</text>
      </view>
      <view class="colView">
        <image src="../../images/all/che.png" mode="" /> <text>直采直销</text>
      </view>
    </view>

  </view>
  <!-- 优惠券 -->
<!-- 优惠券-->
<view wx:if="{
   
   {couponList.money}}">
<view class="yhq animated heartBeats delay-5s" bindtap="getSelected" data-id="{
   
   {couponList.id}}">
  <image src="../../images/all/yhq.png" wx:if="{
   
   {couponList.status==0}}" mode=""/>
  <image src="../../images/all/yyq.png" wx:else mode=""/>
  <view class="copunBox">
  <view>{
   
   {couponList.money}}元{
   
   {couponList.name}}</view>
  <view>部分商品通用</view>
  </view>
</view></view>
  <!-- end -->
  <!-- <view class="chListBox" wx:if="{
   
   {couponList.length>0}}" style="margin-bottom: 20rpx;" bindtap="onClose">
   <view class="cliflex">
    <label>优惠</label>
    <view class="cpubox">
    <view>满100减10</view>
    <view class="bgap"></view>
    <view>领券</view>
    </view>
   </view>
  <view class="flexicon">
    去使用
    <image src="../../images/all/right.png" mode=""/>
  </view>
  </view> -->
  <!-- 尺寸 -->
  <!-- <view class="chList" style="margin-bottom: 20rpx;">
    <label>包装尺寸</label>
    <text>100cm × 120cm × 60cm</text>
  </view> -->
  <view class="chList" style=" border-bottom-left-radius: 0;border-bottom-right-radius: 0;">
    <label>规格</label>
    <text>默认规格</text>
  </view>
  <view class="border"></view>
  <view class="chLists">
    <label>数量</label>
    <text>(当前剩余{
   
   {shopBase.stock }}件)</text>
    <view class="stepper">
      <van-stepper value="{
   
   {shopNum}}" bind:change="getShopNum" disable-input integer />
    </view>
  </view>
  <view class="chList" style="border-bottom-left-radius: 0;border-bottom-right-radius: 0;">
    <label>配送</label>
    <text>商家配送</text>
  </view>
  <view class="border"></view>
  <view class="chList" style="margin-bottom: 20rpx;">
    <label>服务</label>
    <text> 支持7天无理由退货</text>
  </view>
  <!-- 详情 -->
  <view class="shopBaseView">
    <view class="headT">
      <view class="text" style="font-weight: 600;">商品详情</view>
      <view class="druk"></view>
    </view>
    <richtext rich_content="{
   
   {shopBase.newHtml}}" />
  </view>
  <!-- 评价 -->
  <!-- <view class="shopBaseView">
    <view class="headT">
      <view class="text" style="font-weight: 600;">商品</view>
      <view class="druk"></view>
    </view>
  </view> -->
  <!-- 操作 -->
  <view class="submitView">
    <view class="iconviewtexts" bindtap="goHome">
      <button plain="true" class="iconviewtexts">
        <image src="../../images/all/sy.png" class="icons"></image>
        <text lines="1" class="texty">首页</text>
      </button>
    </view>
    <view>
      <view class="iconviewtexts" bindtap="initCollect" style="margin-left: 0rpx;" data-item="{
   
   {shopBase}}" wx:if="{
   
   {collectStatus==0}}" data-type="{
   
   {1}}">
        <image src="../../images/all/ai.png" class="icons"></image>
        <text lines="1" class="texty">收藏</text>
      </view>
      <view class="iconviewtexts" bindtap="initCollect" style="margin-left: 0rpx;" data-type="{
   
   {0}}" data-item="{
   
   {shopBase}}" wx:else>
        <image src="../../images/all/xss.png" class="icons animated rubberBand"></image>
        <text lines="1" class="texty">已收藏</text>
      </view>
    </view>
    <view>
      <view class="iconviewtexts" style="margin-left: 0;">
        <button plain="true" open-type="contact" class="iconviewtexts">
          <image src="../../images/all/tes.png" class="icons"></image>
          <text lines="1" class="texty">客服</text>
        </button>
      </view>
    </view>
    <view class="aleview">
      <view bindtap="getshare" class="rViewBtn">分享得{
   
   {shopBase.rebate_money}}元
        <view class="bubble animated animate__rubberBand">本商品每分享成功一次可得{
   
   {shopBase.rebate_money}}元</view>
      </view>
      <!-- <button plain="true" class="rViewBtn" open-type="share">分享得9元</button> -->
      <view class="rSelBtn" bindtap="getPayOrder" data-shopBase="{
   
   {shopBase}}">立即购买</view>
    </view>
  </view>
  <!-- end -->
</view>


<!-- 海报 -->
<van-overlay show="{
   
   { show }}" z-index="999" bind:click="onClickHide">
  <view class="wrapper">
    <view style="height: 72rpx;"></view>
    <!-- poster -->
    <view class="block" catch:tap="noop">
      <view class="closeView" bindtap="getClose">
        <image src="../../images/all/cha.png" mode="" />
      </view>
      <view style="height: 30rpx;"></view>
      <view style='position: relative;'>
        <view class="top">
          <!-- <button bind:tap='onRevert'>撤销</button>
    <button bind:tap='onRecover'>恢复</button> -->
          <!-- <button bind:tap='saveImage'>保存</button> -->
        </view>
        <painter customActionStyle="{
   
   {customActionStyle}}" palette="{
   
   {paintPallette}}" bind:imgOK="onImgOK"  wx:if="{
   
   {show}}" action="{
   
   {action}}" widthPixels="580" />
        <image wx:if="{
   
   {show}}" src="{
   
   {image}}" style="width: 640rpx; height:928rpx;" />
      </view>

      <image class="logo" src="../../images/all/logo.png" mode="" />
    </view>
    <!-- end -->
    <!-- 底部弹窗 -->
    <view class="bottomView">
      <view class="headBoxs">
        <text>
          分享给好友,好友注册并下单后您可得{
   
   {shopBase.rebate_money}}元
        </text>
      </view>
      <view class="fwidth">
      <van-row gutter="20">
        <van-col class="colConter iconshars" span="12">
          <button open-type='share' plain="true">
            <image class="shareicons" src="../../images/all/s1.png" mode="" />
          </button>
        </van-col>
        <!-- <van-col class="colConter iconshars" span="8">
          <button open-type='share' plain="true">
            <image class="shareicons" src="../../images/all/s2.png" mode="" />
          </button>
        </van-col> -->
        <van-col class="colConter" span="12" bind:tap='saveImage'>
          <image class="shareicons" src="../../images/all/s3.png" mode="" />
        </van-col>
      </van-row>
    </view>
    <view class="fwidth">
      <van-row gutter="20">
        <van-col class="colConter" span="12">
          <button open-type='share' plain="true">微信好友</button>
        </van-col>
        <!-- <van-col class="colConter" span="8" bindtap="shopShare"> <button open-type='share' plain="true">朋友圈</button></van-col> -->
        <van-col class="colConter" span="12" bind:tap='saveImage'>保存图片</van-col>
      </van-row>
    </view>
    </view>
    <!-- end -->
  </view>

</van-overlay>

<!-- 优惠券 -->
<!-- 自定义图标 -->
<van-popup show="{
   
   { modelSHow }}" closeable round z-index="999" position="bottom" custom-style="height: 50%;  background-color:#f5f5f5 ;" bind:close="onClose">
  <view class="headBox" bindtap="onClose">
    选择优惠券
  </view>
  <scroll-view class="couBox" scroll-y="true" bindscrolltoupper="upper" bindscrolltolower="lower" bindscroll="scroll" scroll-into-view="{
   
   {toView}}" scroll-top="{
   
   {scrollTop}}">
    <sCouList List="{
   
   {couponList}}" bind:getSelected="getSelected"></sCouList>
  </scroll-view>
  <view class="btnBox">
    <view class="btnview" bindtap="onClose">
      确定
    </view>
  </view>
</van-popup>

 

import {
  request
} from '../../utils/request'
Page({
  imagePath: '',
  history: [],
  future: [],
  isSave: false,
  /**
   * 页面的初始数据
   */
  data: {
    drawing: [],
    show: false,
    modelSHow: false,
    savebtnText: '点击按钮,保存图片',
    //轮播图的数组
    shopBase: {},
    shopNum: 1,
    shopid: 0,
    coupon_id:0,
    couponList: {},
  collectStatus: 0,
  // 海报生成
  paintPallette: {
    width: '640rpx',
    height: '928rpx',
    background: '#f5f5f5',
    left: 0,
    views: [{
        type: 'rect',
        css: {
          width: '580rpx',
          left: '30rpx',
          top: '30rpx',
          height: '766rpx',
          color: "#fff",
        },
      },
      {
        type: 'image',
        url: 'https://shop.rlmeijia.com/statics/uploads/recommendimg/recommendimg20221111141855.jpg',
        css: {
          top: '30rpx',
          left: '30rpx',
          width: '580rpx',
          height: '580rpx',
        },
      },
      {
        type: 'text',
        text: '【现货】民福康 天然维生素E软胶囊  100粒装',
        css: {
          top: '638rpx',
          left: '58rpx',
          width: "376rpx",
          lineHeight: "44rpx",
          color: '#333333',
          fontSize: "28rpx",

        },
      },
      {
        type: 'text',
        text: '¥194.00',
        css: {
          top: '730rpx',
          left: '58rpx',
          color: '#E1A765',
          fontSize: "32rpx",
        },
      },
      {
        type: 'qrcode',
        content: 'https://github.com/Kujiale-Mobile/Painter',
        css: {
          top: '638rpx',
          right: '62rpx',
          width: '96rpx',
          height: '96rpx',
        },
      },
      {
        type: 'text',
        text: '扫码购买',
        css: {
          top: '750rpx',
          right: '66rpx',
          color: '#333',
          fontSize: "22rpx",
        },
      },
      {
        type: 'image',
        url: '../../images/all/logo.png',
        css: {
          top: '818rpx',
          left: '212rpx',
          width: '222rpx',
          height: '72rpx',
        },
      },
    ],
  },
  customActionStyle: {
    border: {
      borderColor: '#1A7AF8',
    },
    scale: {
      textIcon: '/palette/switch.png',
      imageIcon: '/palette/scale.png',
    },
    delete: {
      icon: '/palette/close.png',
    },
  },
},
goHome() {
  wx.redirectTo({
    url: '/pages/index/index',
  })
},
getShopNum(e){
  console.error(e);
  this.setData({
    shopNum:e.detail
  })
},
/**
 * 生命周期函数--监听页面加载
 */
onLoad(options) {
   wx.showShareMenu({
    withShareTicket: true,
    menus: ['shareAppMessage', 'shareTimeline'],
    success(res){

    },fail(error){}
  })
  if (options) {

    this.initData(options.id)
    this.setData({
      shopid: options.id
    })
    this.initCouponList(options.id)
    this.getcollect(options.id)
    if (options.chnnel) {
      console.error("分享进入==>", options);
      wx.login({
        success: res => {
          // 发送 res.code 到后台换取 openId, sessionKey, unionId
          request("/login/getoauthtoken", 'post', {
            "authcode": res.code
          }).then(res => {
            if (res.code == 200) {
              console.error("粉丝添加获取传参==>", "商品id" + options.id, "老用户" + options.openid, "新用户" + res.openid);
              request("/login/addfriend", 'post', {
                pid: options.id,
                openid: options.openid,
                form_openid: res.openid,
                sitepage: 2
              }).then(res => {
                if (res.code == 0) {

                } else {

                }
              }).catch(err => {
                console.log(err);
              })
            } else {
              // wx.setStorageSync('openid', 1352168512)
            }


          }).catch(err => {
            console.log(err);
          })
        }
      })
    }
  }
},
/**
 * 获取优惠券列表
 * @param {*} options 
 */
initCouponList(id) {
  var openid = wx.getStorageSync('openid')
  request("/coupon/proccoupon", 'post', {
    openid: openid,
    pid: id
  }).then(res => {
    if (res.code == 0) {
      this.setData({
        couponList: res.data
      })
    } else {

    }
  }).catch(err => {
    console.log(err);
  })
},
/**
 * 初始化数据
 */
initData(id) {
  var that = this;
  request("/product/details/", 'post', {
    pid: id,
  }).then(res => {
    if (res.code == 0) {
      res.detailsRow.newHtml = res.detailsRow.detailed.replace(/(<img\s+.*?style=")(.*?)(".*?>)/g, function (match, p1, p2, p3) {
        var newStyle = 'width:100%;height:100%;';
        return p1 + newStyle + p3;
      });
      if (res.detailsRow.recommend_image != '') {
        console.error("判断是否有商品图片生成==>",res.detailsRow);
     var openid=wx.getStorageSync('openid')
    request("/login/createwxaqrcode", 'post', {
      path: "pages/shopBase/index?chnnel=1&id=" + this.data.shopid + "&openid=" + openid,
    }).then(data => {
      console.error("生成二维码==>",data);
      if (data.code == 0) {
        console.error("成功生成==>",data);
        that.setData({
          shopBase: res.detailsRow,
          paintPallette: {
            width: '640rpx',
            height: '928rpx',
            background: '#f5f5f5',
            left: 0,
            views: [{
                type: 'rect',
                css: {
                  width: '580rpx',
                  left: '30rpx',
                  top: '30rpx',
                  height: '766rpx',
                  color: "#fff",
                },
              },
              {
                type: 'image',
                url: res.detailsRow.recommend_image,
                css: {
                  top: '30rpx',
                  left: '30rpx',
                  width: '580rpx',
                  height: '580rpx',
                },
              },
              {
                type: 'text',
                text: res.detailsRow.title,
                css: {
                  top: '648rpx',
                  left: '58rpx',
                  width: "376rpx",
                  maxLines: 2,
                  lineHeight: "40rpx",
                  color: '#333333',
                  fontSize: "27rpx",

                },
              },
              {
                type: 'text',
                text: '¥' + res.detailsRow.price,
                css: {
                  top: '730rpx',
                  left: '58rpx',
                  color: '#E1A765',
                  fontSize: "32rpx",
                },
              },
              {
                type: 'image',
                url: data.wxaqrcodepath,
                css: {
                  top: '638rpx',
                  right: '62rpx',
                  width: '100rpx',
                  height: '100rpx',
                },
              },
              // {
              //   type: 'qrcode',
              //   content: data.wxaqrcodepath,
              //   css: {
              //     top: '638rpx',
              //     right: '62rpx',
              //     width: '96rpx',
              //     height: '96rpx',
              //   },
              // },
              {
                type: 'text',
                text: '扫码购买',
                css: {
                  top: '750rpx',
                  right: '66rpx',
                  color: '#333',
                  fontSize: "22rpx",
                },
              },
              {
                type: 'image',
                url: '../../images/all/logo.png',
                css: {
                  top: '818rpx',
                  left: '212rpx',
                  width: '222rpx',
                  height: '72rpx',
                },
              },
            ],
          }
        })
      } else {

      }
    }).catch(err => {
      console.log(err);
    })
     
      }
    } else {

    }
  }).catch(err => {
    console.log(err);
  })
},
/**
 * 生命周期函数--监听页面初次渲染完成
 */
onReady() {

},
/**
 * 商品是否收藏
 */
getcollect(id) {
  var openid = wx.getStorageSync('openid')
  request("/collect/getcollect", 'post', {
    openid: openid,
    cid: id
  }).then(res => {
    this.setData({
      collectStatus: res.code
    })

  }).catch(err => {
    console.log(err);
  })
},
getSelected(e) {
  var openid=wx.getStorageSync('openid')
request("/coupon/receivecoupon", 'post', {
  openid:openid,
  coupon_id:parseInt(e.currentTarget.dataset.id),
}).then(res => {
  if (res.code == 0) {
wx.showToast({
  title: '领取成功',
  icon:"none"
})
  } else {
    wx.showToast({
      title:res.msg,
      icon:"none"
    })
  }
}).catch(err => {
  console.log(err);
})
//   for (var i in this.data.couponList) {
//     if (e.detail.item.coupon_id == this.data.couponList[i].coupon_id) {
//       this.data.couponList[i].stats = 1;
// var openid=wx.getStorageSync('openid')
// request("/coupon/receivecoupon", 'post', {
//   openid:openid,
//   coupon_id:this.data.couponList[i].coupon_id,
// }).then(res => {
//   if (res.code == 0) {
// wx.showToast({
//   title: '领取成功',
//   icon:"none"
// })
//   } else {

//   }
// }).catch(err => {
//   console.log(err);
// })
//       this.setData({
//         coupon_id: this.data.couponList[i].coupon_id
//       })
//     } else {
//       this.data.couponList[i].stats = 0;
//     }
//   }
//   this.setData({
//     couponList: this.data.couponList
//   })
},
/**
 * 收藏
 */
initCollect(data, type) {
  var type = data.currentTarget.dataset.type;
  var item = data.currentTarget.dataset.item;
  var openid = wx.getStorageSync('openid')
  console.error("type", type);
  if (type == 1) {
    request("/collect/collect", 'post', {
      openid: openid,
      cid: item.id,
      type: 2,
      stats: type,
    }).then(res => {
      if (res.code == 0) {
        wx.showToast({
          title: '收藏成功',
          icon: "none",
        })
        this.setData({
          collectStatus: 1
        })
      }
    }).catch(err => {
      console.log(err);
    })
  } else {
    request("/collect/getcollect", 'post', {
      openid: openid,
      cid: item.id
    }).then(res => {
      console.error("“删除", res);
      if (res.code == 1) {
        wx.showToast({
          title: '取消收藏',
          icon: "none",
        })
        this.setData({
          collectStatus: 0
        })
      }
    }).catch(err => {
      console.log(err);
    })
  }
  return;

},
getClose() {
  this.setData({
    show: false
  })
},
onClose() {
  this.data.modelSHow = !this.data.modelSHow
  this.setData({
    modelSHow: this.data.modelSHow
  })
},
shopShare() {
  wx.shopShareMenu()
},
getshare() {
  var that = this;

  setTimeout(() => {
    if (this.data.shopBase.recommend_image != '') {
      that.setData({
        show: true,
      })
    } else {
      wx.showToast({
        title: '生成失败,请重新尝试',
      })
    }
  }, 800);



},
/**
 * 生命周期函数--监听页面显示
 */
onShow() {

},
getPayOrder(data) {
  var item = data.currentTarget.dataset.shopbase;
  var shopNum = this.data.shopNum;
  var arrbase = {};
  arrbase.id = item.id;
  arrbase.coupon_id=this.data.coupon_id;
  arrbase.shopNum = shopNum;
  arrbase.recommend_image = item.recommend_image;
  arrbase.title = item.title;
  arrbase.price = item.price;
  wx.setStorageSync('shopBase', arrbase)
  console.error(arrbase);
  wx.navigateTo({
    url: '/pages/nextOrder/index',
  })

},
/**
 * 生命周期函数--监听页面隐藏
 */
onHide() {},

/**
 * 生命周期函数--监听页面卸载
 */
onUnload() {

},

/**
 * 页面相关事件处理函数--监听用户下拉动作
 */
onPullDownRefresh() {

},

/**
 * 页面上拉触底事件的处理函数
 */
onReachBottom() {

},

/**
 * 用户点击右上角分享
 */
onShareAppMessage() {
 
  this.setData({
    show: false
  })
  var openid = wx.getStorageSync('openid')
  return {
    title: this.data.shopBase.title,
    path: '/pages/shopBase/index?chnnel=1&id=' + this.data.shopid + "&openid=" + openid,
  }

},
onShareTimeline(){
  this.setData({
    show:false
  })
  var openid = wx.getStorageSync('openid')
  return {
    title: this.data.shopBase.title,
    path: '/pages/shopBase/index?chnnel=1&id=' + this.data.shopid + "&openid=" + openid,
    imageUrl: "/images/share_3.png"
  }
},
// 海报生成
onImgOK(e) {
  this.imagePath = e.detail.path;

  this.setData({
    image: this.imagePath,
  });
  if (this.isSave) {
    this.saveImage(this.imagePath);
  }
},
saveImage() {
  var that=this;
  wx.showToast({
    title: '正在生成中,请稍后',
    icon:"none",
    duration:1500
  })
  var openid=wx.getStorageSync('openid')
  if (this.imagePath && typeof this.imagePath === 'string') {
    this.isSave = false;
    wx.getFileSystemManager().readFile({ // 读取本地文件内容
      filePath:this.imagePath,
      encoding: 'base64', //编码格式
      success(res) {
          request("/login/dowfile", 'post', {
      "fileUrl": res.data,
      openid:openid
    }, 3).then(gbase => {
console.error("https",gbase);
      var timestamp = Date.parse(new Date());  
    timestamp = timestamp / 1000;  
    console.error("传参",gbase.picUrl+"?timestamp="+timestamp);
      wx.downloadFile({
        url:gbase.picUrl+"?timestamp="+timestamp,
        success: res => {
          console.error("down-success保存到相册权限", res);
          //保存到相册
          wx.saveImageToPhotosAlbum({
            filePath: res.tempFilePath,
            success: res => {
              console.info("saveImageToPhotosAlbum-success===>", res);
              that.setData({
                show: false,
                // successShow: true
              })
              wx.showToast({
                title: '已保存到系统相册',
                icon:'none'
              })
            },
            fail: function (err) {
              console.info("saveImageToPhotosAlbum-fail===>", err);
                  wx.showModal({
                      title: '提示',
                      content: '需要您授权保存相册',
                      modalType: false,
                      success: modalSuccess => {
                          wx.openSetting({
                              success(settingdata) {
                                  console.log("settingdata", settingdata)
                                  if (settingdata.authSetting['scope.writePhotosAlbum']) {
                                      wx.showModal({
                                          title: '提示',
                                          content: '获取权限成功,再次点击图片即可保存',
                                          modalType: false,
                                      })
                                  } else {
                                      wx.showModal({
                                          title: '提示',
                                          content: '获取权限失败,将无法保存到相册哦~',
                                          modalType: false,
                                      })
                                  }
                              },
                              fail(failData) {
                                  console.log("failData", failData)
                              },
                              complete(finishData) {
                                  console.log("finishData", finishData)
                              }
                          })
                      }
                  })
          },complete(res) {
           that.setData({
             show:false
           })
        }

          })
        },
        fail: error => {
          console.error("downloadFile-fail失败2", error);
          wx.showToast({
            title: '保存失败,请稍后再试~',
            icon:'none'
          })
          that.setData({
            show: false,
          })
        },
      });
    
    }).catch(err => {
      console.log(err);
    })
      }
    })
  
 

    
    return;
    this.isSave = false;
    wx.saveImageToPhotosAlbum({
      filePath: this.imagePath,
    });
    
    console.error("保存",this.imagePath);
    this.setData({
      show: false
    })
    
  }
},


})
page {
  background: #F5F5F5;
  padding-bottom: 150rpx;
}

.shopRemark {
  width: 100%;
  margin: 0 auto;
  padding-bottom: 35rpx;
  background: #fff;
  border-bottom-left-radius: 20rpx;
  border-bottom-right-radius: 20rpx;
  /* margin-bottom: 20rpx; */
}
.abs{
  position: relative;
  left:  30rpx;
}
richtext img{
  width: 100!important;
}
.iconviewtexts  button[plain]{
  border: 0;
  border-radius: 0;
  position: relative;
  right: 15rpx;
}
.aleview button[plain]{
  border: 0;
  border-radius: 0;
  width: 208rpx;
  height: 98rpx;
  text-align: center;
  line-height: 98rpx;
  font-size: 28rpx;
  font-family: PingFangSC-Semibold, PingFang SC;
  font-weight: 600;
  color: #FFDB9E;
  background: linear-gradient(90deg, #505050 0%, #515151 100%);
}
.border {
  width: 666rpx;
  border-bottom: 1rpx solid #E4E4E4;
  margin: 0 auto;
}

.shopTitle {
  width: 686rpx;
  margin: 0 auto;
  font-size: 32rpx;
  padding-top: 20rpx;
  font-family: PingFangSC-Semibold, PingFang SC;
  font-weight: 600;
  color: #333333;
}

.shopbag {
  width: 686rpx;
  margin: 0 auto;
  font-size: 28rpx;
  font-family: PingFangSC-Regular, PingFang SC;
  font-weight: 400;
  color: #999999;
}

.shopyy {
  width: 686rpx;
  margin: 0 auto;
  margin-top: 16rpx;
}

.shopRadioView {
  width: 344rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 52rpx;
  padding: 0rpx 20rpx;

  background: #FFF3F3;
  font-size: 26rpx;
  font-family: PingFangSC-Medium, PingFang SC;
  font-weight: 500;
  color: #E1A765;
  border-radius: 28rpx;
}

.colView image {
  width: 24rpx;
  height: 24rpx;
  margin-right: 10rpx;
}

.shopyys {
  width: 686rpx;
  margin: 0 auto;
  margin-top: 16rpx;
  display: flex;
  align-items: center;
}

.colView {
  font-size: 24rpx;
  font-family: PingFangSC-Medium, PingFang SC;
  font-weight: 500;
  display: flex;
  align-items: center;
  color: #B38953;
}

.colView:nth-child(2) {
  margin-left: 80rpx;
}

.colView:nth-child(3) {
  margin-left: 80rpx;
}

.chList {
  width: 100%;
  height: 108rpx;
  margin: 0 auto;
  line-height: 108rpx;
  background: #FFFFFF;
  border-radius: 20rpx;
}
.chListBox{
  width: 100%;
  height: 108rpx;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  line-height: 108rpx;
  background: #FFFFFF;
  border-radius: 20rpx;
}
.chListBox image{
  width: 16rpx;
  height: 32rpx;
  margin-right: 32rpx;
}
.chListBox label {
  margin-left: 20rpx;
  font-size: 28rpx;
  font-family: PingFangSC-Medium, PingFang SC;
  font-weight: 600;
  color: #333333;
}
.flexicon{
  display: flex;
  align-items: center;font-size: 26rpx;
  font-family: PingFangSC-Regular, PingFang SC;
  font-weight: 400;
  color: #666666;
}
.cpubox view:nth-child(1){
  margin-left: 12rpx;


}
.cpubox view:nth-child(2){
  width: 2rpx;
  height: 34rpx;
  border-right: 1rpx dotted #E1A765;

}
.cpubox view:nth-child(3){
  margin-right: 12rpx;

}
.cpubox{
  width: 228rpx;
height: 52rpx;
line-height: 52rpx;
position: relative;
margin-left: 86rpx;
text-align: center;
display: flex;
align-items: center;
justify-content: space-between;
border-radius: 10rpx;
font-size: 26rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #E1A765;
background: #FFF3F3;
}
.cliflex{
  display: flex;
  align-items: center;
}
.flexicon image{
margin-left: 12rpx;
}
.chList label {
  margin-left: 32rpx;
  font-size: 28rpx;
  font-family: PingFangSC-Medium, PingFang SC;
  font-weight: 600;
  color: #333333;
}
.bu{
  width: 31rpx;
height: 30rpx;
background: #E1A765;
}
.chList text {
  font-size: 28rpx;
  font-family: PingFangSC-Medium, PingFang SC;
  font-weight: 500;
  color: #999999;
  margin-left: 30rpx;
}.chListBox text {
  font-size: 28rpx;
  font-family: PingFangSC-Medium, PingFang SC;
  font-weight: 500;
  color: #999999;
  margin-left: 30rpx;
}

.chLists {
  width: 100%;
  margin: 0 auto;
  height: 136rpx;
  border-bottom-right-radius: 20rpx;
  display: flex;
  border-bottom-left-radius: 20rpx;
  background-color: #fff;
  align-items: center;
  line-height: 136rpx;
  position: relative;
  margin-bottom: 20rpx;
}

.chLists label {
  font-size: 30rpx;
  font-family: PingFangSC-Medium, PingFang SC;
  font-weight: 600;
  color: #333333;
  margin-left: 32rpx;
  margin-right: 4rpx;
}

.chLists text {
  font-size: 26rpx;
  font-family: PingFangSC-Regular, PingFang SC;
  font-weight: 400;
  color: #666666;
}

.stepper {
  position: absolute;
  right: 38rpx;
}

.shopBaseView {
  width: 100%;
  margin: 0 auto;
  background-color: #fff;
  border-radius: 20rpx;
  padding-bottom: 30rpx;
}

.headT .text {
  padding-top: 30rpx;
  margin-left: 32rpx;
  font-size: 32rpx;
  font-family: PingFangSC-Medium, PingFang SC;
  font-weight: 500;
  height: 44rpx;
  color: #333333;
}

.druk {
  width: 32rpx;
  height: 8rpx;
  position: relative;
  top: 5rpx;
  left: 65rpx;
  border: 5rpx solid #E1A765;
  border-radius: 0 0 50% 50%/0 0 100% 100%;
  border-top: none;
  margin-bottom: 36rpx;
}

.shopBaseView image {
  margin: 0 auto;
  display: flex;
  justify-content: center;
}

.submitView {
  width: 100%;
  height: 98rpx;
  display: flex;
  align-items: center;
  background: #FFFFFF;
  position: fixed;
  justify-content: space-between;
  bottom: 0;
  z-index: 999;
}

.iconviewtexts {
  display: flex;
  width: 80rpx;
  text-align: center;
  align-items: center;
  /* background-color: red; */
  flex-direction: column;
  align-items: center;
  margin-left: 10rpx;
}


.shopHead view:nth-child(1){
  font-size: 48rpx;
font-family: PingFangSC-Semibold, PingFang SC;
font-weight: 600;
color: #E1A765;
display: flex;
align-items: center;

margin-left: 32rpx;
margin-bottom: 10rpx;
}
.shopHead view:nth-child(1) text{
  font-size: 30rpx;
font-family: PingFangSC-Semibold, PingFang SC;
font-weight: 600;
color: #E1A765;
}
.shopHead view:nth-child(2){
  font-size: 28rpx;
  font-family: PingFangSC-Regular, PingFang SC;
  font-weight: 400;margin-left: 32rpx;
  color: #999999;
  text-decoration: line-through;
}
.swp{
  position: relative;
}
.shopHead{
 padding-top: 20rpx; 
 width: 100%;
 margin: 0 auto;
 background: #fff;
 border-top-left-radius: 20rpx;
 border-top-right-radius: 20rpx;
 position: absolute;
 z-index: 8;
 bottom: 0rpx;
 text-align: left;
transform: translate(-50%);
left: 50%;
}
.texty {
  overflow-wrap: break-word;
  color: rgba(51,51,51,1);
  font-size: 22rpx;
  font-family: PingFangSC-Regular;
  font-weight: normal;
  text-align: right;
  white-space: nowrap;
  line-height: 20rpx;
  margin-top: 16rpx;
}

.icons {
  width: 36rpx;
  height: 32rpx;
  align-self: center;
}

.aleview {
  display: flex;
}

.rViewBtn {
  width: 208rpx;
  height: 98rpx;
  position: relative;
  text-align: center;
  line-height: 98rpx;
  font-size: 28rpx;
  font-family: PingFangSC-Semibold, PingFang SC;
  font-weight: 600;
  color: #FFDB9E;
  background: linear-gradient(90deg, #505050 0%, #515151 100%);
}

.rSelBtn {
  width: 250rpx;
  font-size: 28rpx;
  text-align: center;
  line-height: 98rpx;
  font-family: PingFangSC-Semibold, PingFang SC;
  font-weight: 600;
  color: #8A511A;
  height: 98rpx;
  background: linear-gradient(90deg, #E6C58E 0%, #E1A664 100%);
}

/* 海报 */
/* 点击分享弹窗 */
.wrapper {
  width: 100%;
  height: 2024rpx!important;
  overflow: auto;
}
.closeView{
  width: 48rpx;
  position: absolute;
height: 48rpx;
top: -25rpx;
right: -25rpx;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
z-index: 9;
background: rgba(0,0,0,0.5);
}
.overlays{
  width: 750rpx;
height: 1624rpx!important;
background: rgba(0,0,0,0.6);
overflow: auto!important;
}
.closeView image{
  width: 30rpx;
  height: 30rpx;
}
.block {
  width: 640rpx;
  position: relative;
  height: 928rpx;
  display: flex;
  justify-content: center;
  align-items: center;
  margin: 0 auto;
  background: #EEEEEE;
}


.canvas-style{
  width: 580rpx !important;
  height: 766rpx  !important;
  margin: 0 auto;
}
.savebtn-style{
  position: relative;
  top:220rpx!important;
  width: 130rpx!important;
  height: 162rpx!important;
  right: -265rpx!important;
  z-index: 999;
  /* opacity: 0; */
  /* display: none; */
}
.logo{
  display: flex;
  align-items: center;
  margin: 0 auto;
  width: 222rpx;
height: 72rpx;
margin-top: 22rpx;
}


.bottomView{
  width: 100%;
/* height: 376rpx; */
background: #FFFFFF;
position: fixed;
bottom: 0;

margin-top: 30rpx;
padding-bottom: 50rpx;
border: 2rpx solid #FFFFFF;
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
}
.headBoxs{
  width: 100%;
  border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
  height: 110rpx;
  /* position: fixed;
  top: 0;
  z-index: 2; */
  text-align: center;
background: linear-gradient(180deg, rgba(230,142,142,0.83) 0%, rgba(225,166,100,0) 100%);
}
.headBoxs text{
  font-size: 30rpx;
  font-family: PingFangSC-Regular, PingFang SC;
  font-weight: 400;
  color: #8A511A;
}
.colConter{
  text-align: center;
    font-size: 28rpx;
  font-family: PingFangSC-Medium, PingFang SC;
  font-weight: 500;
  color: #333333;
  margin-top: 14rpx;
}
.shareicons{
  width: 100rpx;
height: 100rpx;
}
.colConter button[plain]{
  font-size: 28rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
height: 40rpx;
line-height: 40rpx;
}
.iconshars button[plain]{
width: 100rpx;
height: 100rpx;
padding: 0;
margin: 0 auto;
}

/* 气泡 */
.bubble:before{
  position: absolute;
  top: 73rpx;
  left: 35%;
  content: '';
  border: 15rpx solid transparent;
  border-top-color: #302417;
  opacity: 0.9;
}
.bubble{
position: absolute;
width: 392rpx;
height: 73rpx;
top: -95rpx;
left: -50rpx;
text-align: center;
line-height: 73rpx;
font-size: 24rpx;
font-family: PingFangSC-Semibold, PingFang SC;
font-weight: 600;
color: #FFDB9E;
background: rgba(0,0,0,0.72);
border-radius: 30rpx;
box-shadow: 0rpx 4rpx 8rpx 0rpx rgba(0,0,0,0.9);
/* background-color: rgba(#302417,#302417,#302417, 0.8); */
/* background-color: rgb(#302417, #302417, #302417,0.5); */
/* border: 2rpx solid #302417; */
}

/* 优惠券 */
.headBox{
  width: 100%;
  text-align: center;
  display: flex;font-size: 36rpx;
  font-family: PingFang-SC-Bold, PingFang-SC;
  font-weight: bold;
  color: #333333;
  position: relative;
  align-items: center;
  height: 110rpx;
  justify-content: center;
  line-height: 110rpx;
  background: linear-gradient(180deg, rgba(230,142,142,0.83) 0%, rgba(225,166,100,0) 100%);
 }
 .headBox image{
   width: 40rpx;
   height: 40rpx;
 }
 .couBox{
   width: 100%;
   height: 320rpx;
   overflow: hidden;
 }
 .btnBox{
   width: 100%;
   position: fixed;
   bottom: 0;
height: 148rpx;
justify-content: center;
display: flex;
align-items: center;
background: #FFFFFF;
 }
.btnview{
  width: 686rpx;
  height: 84rpx;font-size: 32rpx;
  font-family: PingFangSC-Semibold, PingFang SC;
  font-weight: 600;
  color: #8A511A;
  line-height: 84rpx;
  text-align: center;
  background: linear-gradient(90deg, #E6C58E 0%, #E1A664 100%);
  box-shadow: 0rpx 4rpx 8rpx 0rpx rgba(255,59,60,0.25);
  border-radius: 42rpx;
 }


 /*  */

 .yhq{
  width: 702rpx;
  height: 218rpx;
  display: flex;
  align-items: center;
  margin: 0 auto;  position: relative;
}
.yhq image{
  position: relative;
  width: 702rpx;
  height: 218rpx;
  display: flex;
  align-items: center;
  margin: 0 auto;
}

.copunBox{
  position: absolute;
  z-index: 2;
  top: 60rpx;
  margin-left: 58rpx;
}
.copunBox view:nth-child(1){
  font-size: 40rpx;
font-family: PingFang-SC-Heavy, PingFang-SC;
font-weight: 800;
color: #EE5514;
}
.copunBox view:nth-child(2){
  font-size: 22rpx;
  font-family: PingFangSC-Medium, PingFang SC;
  font-weight: 500;
  color: #EE5514;
}

.fwidth{
  width: 80%;
  margin: 0 auto;
}

Guess you like

Origin blog.csdn.net/qq_35695041/article/details/131664734