編程日曆小程序,對小程序雲開發和(hé / huò)生成分享海報的(de)實踐 - 新聞資訊 - 雲南小程序開發|雲南軟件開發|雲南網站建設-昆明融晨信息技術有限公司

159-8711-8523

雲南網建設/小程序開發/軟件開發

知識

不(bù)管是(shì)網站,軟件還是(shì)小程序,都要(yào / yāo)直接或間接能爲(wéi / wèi)您産生價值,我們在(zài)追求其視覺表現的(de)同時(shí),更側重于(yú)功能的(de)便捷,營銷的(de)便利,運營的(de)高效,讓網站成爲(wéi / wèi)營銷工具,讓軟件能切實提升企業内部管理水平和(hé / huò)效率。優秀的(de)程序爲(wéi / wèi)後期升級提供便捷的(de)支持!

您當前位置>首頁 » 新聞資訊 » 小程序相關 >

編程日曆小程序,對小程序雲開發和(hé / huò)生成分享海報的(de)實踐

發表時(shí)間:2021-2-3

發布人(rén):融晨科技

浏覽次數:77

1、起源

朋友圈曬的(de)很多的(de)一本日曆書《了(le/liǎo)不(bù)起的(de)程序員 2021》,我也(yě)買了(le/liǎo),很厚,紙質書嘛,現在(zài)已經很少看了(le/liǎo),加上(shàng)這(zhè)是(shì)一本日曆書,希望是(shì)每天都打開看。可實際上(shàng)的(de)情況是(shì),要(yào / yāo)麽忘記看今天的(de)内容,要(yào / yāo)麽一口氣看了(le/liǎo)好幾天的(de)内容,然後剩下幾天又不(bù)看了(le/liǎo)。

後來(lái)《了(le/liǎo)不(bù)起的(de)程序員 2021》在(zài) Github 開源了(le/liǎo)。

于(yú)是(shì)乎!我就(jiù)想做一個(gè)小程序,因爲(wéi / wèi)手機每天打開的(de)頻率太高了(le/liǎo),碎片時(shí)間也(yě)很多,加上(shàng)小程序的(de)不(bù)用安裝用完即走的(de)優點,使用方便,不(bù)會有壓力感。

再加上(shàng)自己還沒有一款正兒八經的(de)小程序作品,對現在(zài)很火的(de)雲開發也(yě)沒怎麽用過,特别是(shì)小程序雲開發,他(tā)他(tā)到(dào)底用起來(lái)爽不(bù)爽呢?(很爽!)

于(yú)是(shì)乎!開幹!

2、産品設計

這(zhè)是(shì)最傷腦筋的(de)部分,小程序到(dào)底要(yào / yāo)做成什麽樣,畫個(gè)原型圖?作爲(wéi / wèi)一個(gè)『資深』程序員,從來(lái)沒正經畫過原型和(hé / huò)設計。手足無措,改用什麽工具?雖然我知道(dào)有 Sketch 這(zhè)個(gè)神器,還很多在(zài)線設計工具,比如磨刀,但從來(lái)沒用過啊,最後硬着頭皮用磨刀畫了(le/liǎo)畫原型,很簡陋的(de)原型,就(jiù)是(shì)線框圖級别。

這(zhè)個(gè)過程不(bù)斷有新的(de)想法,所以(yǐ)改來(lái)該去,産品設計花了(le/liǎo)好幾天,學習怎麽畫原型,實現腦子(zǐ)裏亂七八糟的(de)各種想法。

在(zài)這(zhè)個(gè)過程中我不(bù)斷的(de)給自己家需求,一度增加了(le/liǎo)什麽曆史上(shàng)的(de)今天、知乎日曆等等各種内容,最後還是(shì)被自己狠心一一斃掉了(le/liǎo),隻留下純粹的(de)編程日曆内容。

鑒于(yú)對産品和(hé / huò)設計不(bù)擅長,在(zài)此誠邀 UI、産品小夥伴,一起租一個(gè)團隊,有機會一起做一些産品,讓我們的(de)想法能落地(dì / de),生根發芽。

3、開發

産品設計階段和(hé / huò)開發階段占用的(de)時(shí)間比大(dà)概是(shì) 8:2 左右,有了(le/liǎo)原型開發很快,畢竟也(yě)沒什麽複雜的(de)東西。

下面重點說(shuō)一下分享海報功能的(de)實現吧。

3.1、選擇海報分享方案

在(zài)開發分享海報功能之(zhī)前我也(yě)看了(le/liǎo)下網上(shàng)大(dà)緻的(de)方案,最後我選擇了(le/liǎo)微信小程序自己的(de)擴展組件:wxml-to-canvas,小程序内通過靜态模闆和(hé / huò)樣式繪制 canvas ,導出(chū)圖片,可用于(yú)生成分享圖等場景。

我爲(wéi / wèi)什麽不(bù)用其他(tā)方案:

  • 手寫 canvas,太麻煩
  • 後端生成前端獲取,太麻煩,我這(zhè)個(gè)小程序很簡單沒必要(yào / yāo)
  • 開源小程序海報組件,嘗試過一個(gè),感覺也(yě)不(bù)太好用,有些沒文檔用起來(lái)吃力

上(shàng)圖,是(shì)騾子(zǐ)是(shì)馬拉出(chū)來(lái)遛遛,下圖的(de)的(de)海報就(jiù)是(shì)通過 wxml-to-canvas 動态繪制的(de)。

3.2、引入 wxml-to-canvas 組件

wxml-to-canvas 的(de)限制很多,第一次沒經驗的(de)話覺得很難用,如果再讓我做一次,我就(jiù)快很多了(le/liǎo)。

官方的(de)示例隻單純教你怎麽生成海報,缺乏上(shàng)下文和(hé / huò)怎麽整合進你的(de)項目及邏輯,需要(yào / yāo)費一下腦子(zǐ)。

Step1. npm 安裝,參考 小程序 npm 支持

npm install --save wxml-to-canvas
複制代碼

Step2. JSON 組件聲明

{
  "usingComponents": {
    "wxml-to-canvas": "wxml-to-canvas"
  }
}
複制代碼

Step3. wxml 引入組件

<view class="share-image-container">
  <wxml-to-canvas
    id="canvas"
    width="{{canvasWidth}}"
    height="{{canvasHeight}}"
  ></wxml-to-canvas>
</view>
複制代碼

3.3、海報分享邏輯說(shuō)明

點擊編程日曆小程序底部的(de)海報分享按鈕,在(zài)當前頁面生成 canvas 預覽圖,然後再生成圖片跳轉到(dào)海報圖片預覽和(hé / huò)保存頁面。

上(shàng)面的(de) .share-image-container 類如下:

.share-image-container {
  border: 1px solid red;
  position: absolute;
  transform: translateY(-1000%);
  bottom: 0;
  z-index: 0;
}
複制代碼

即在(zài)頁面外生成 canvas,也(yě)是(shì)在(zài)這(zhè)裏調試 wxml-to-canvas 組件效果的(de)地(dì / de)方,去掉該類的(de)樣子(zǐ)如下:

3.4、js 獲取實例

Step4. js 獲取實例

import RenderCodeToWXML from "./renderCodeWXML.js";

Page({
  data: {
    canvasWidth: 373,
    canvasHeight: 720,
    bannerImgHeight: 240,
    bannerImgWdith: 320,
  },
  renderToCanvas() {
    wx.showLoading({
      title: "處理中...",
    });
    this.canvas = this.selectComponent("#canvas");
    const {
      canvasWidth,
      canvasHeight,
      bannerImgWdith,
      bannerImgHeight,
    } = this.data;
    let renderToWXML = new RenderCodeToWXML(
      canvasWidth,
      canvasHeight,
      bannerImgWdith,
      bannerImgHeight
    );

    const wxml = renderToWXML.renderWXML();
    const style = renderToWXML.renderStyle();
    const p1 = this.canvas.renderToCanvas({ wxml, style });
    p1.then((res) => {
      // console.log('container', res.layoutBox)
      app.globalData.container = res;
      this.container = res;
      this.extraImage();
    }).catch((err) => {
      wx.hideLoading();
      console.log("err", err);
    });
  },
  extraImage() {
    const p2 = this.canvas.canvasToTempFilePath();
    p2.then((res) => {
      wx.hideLoading();
      // app.globalData.share = res
      wx.navigateTo({
        url: "../shareImage/shareImage",
        success: function(res2) {
          // 通過eventChannel向被打開頁面傳送數據
          res2.eventChannel.emit(
            "acceptDataFromOpenerPage",
            {
              share: res,
              container: app.globalData.container,
              tab: app.globalData.tab,
              date: app.globalData.dateInfo.strings,
            }
          );
        },
      });
    }).catch((err) => {
      wx.hideLoading();
      wx.showToast({
        title: err,
        icon: "none",
      });
    });
  },
});
複制代碼

這(zhè)裏主要(yào / yāo)就(jiù)是(shì)從 renderCodeWXML.js 中獲取 WXML 和(hé / huò) Style,然後調用 canvas 的(de) renderToCanvas 方法進行渲染:

const wxml = renderToWXML.renderWXML();
const style = renderToWXML.renderStyle();
const p1 = this.canvas.renderToCanvas({ wxml, style });
複制代碼

最後在(zài) p1.then 裏調用 this.extraImage(); 方法跳轉到(dào)下一個(gè)頁面,并通過 eventChannel.emit 方式傳遞參數。

來(lái)看看 renderCodeWXML.js 裏面有什麽:

const app = getApp();

export default class RenderDataToWXML {
  constructor(
    canvasWidth,
    canvasHeight,
    imgWidth,
    imgHeight
  ) {
    this.canvasWidth = canvasWidth;
    this.canvasHeight = canvasHeight;
    this.imgWidth = imgWidth;
    this.imgHeight = imgHeight;
  }

  renderWXML() {
    const { dateInfo, data, userInfo } = app.globalData;
    const openId = wx.getStorageSync("openId");
    let pData = http://www.wxapp-union.com/"";
    let pMore = "";
    let banner = "";

    if (data.data.event) {
      pData = http://www.wxapp-union.com/data.data.event.join("");
    }
    if (data.data.coding) {
      pData = http://www.wxapp-union.com/data.data.coding.join("");
    }
    if (data.data.landmark) {
      pData = http://www.wxapp-union.com/data.data.landmark.join("");
    }

    if (data.data.more) {
      pMore = data.data.more[0];
    } else if (data.data.people) {
      pMore = data.data.people[0].split(":").join(",");
    } else {
      pMore = "";
    }

    if (data.data.img) {
      banner = `
      <view class="banner">
        <image class="banner-image" mode="aspectFit" src="http://www.wxapp-union.com/${data.data.img.url}" />
      </view>`;
    }

    if (pData.length >= 156) {
      pData = http://www.wxapp-union.com/pData.substring(0, 152) + "...";
    }
    if (pMore.length >= 50) {
      pMore = pMore.substring(0, 48) + "...";
    }

    let avatar = "";
    if (userInfo && userInfo.avatarUrl) {
      avatar = `<view class="avatar">
        <image class="avatar-image" src="http://www.wxapp-union.com/${userInfo.avatarUrl}" />
        <text class="avatar-nikename">${userInfo.nickName}邀請你使用</text>
      </view>`;
    }

    let wxmlMore = pMore;
    if (wxmlMore) {
      wxmlMore = `
        <view>
          <text class="p-more">${pMore}</text>
        </view>
      `;
    }

    const wxml = `
      <view class="container">
        <view class="top">
          <view class="top-left">
            <view><text class="en">${dateInfo.date.monthEN}</text></view>
            <view><text class="cn">${dateInfo.lunarDate}</text></view>
          </view>
          <view><text class="top-center">${dateInfo.date.day}</text></view>
          <view class="top-right">
            <view><text class="en">${dateInfo.date.weekEN}</text></view>
            <view><text class="cn">${dateInfo.date.weekCN}</text></view>
          </view>
        </view>
        ${banner}
        <view class="middle">
          <view>
            <text class="p-data">${pData}</text>
          </view>
          ${wxmlMore}
        </view>
        <view class="qrcode">
          <view class="appinfo">
            ${avatar}
            <view><text class="appname">編程日曆</text></view>
            <view><text class="appdesc">程序員專屬日曆,最極客日曆</text></view>
          </view>
          <view class="qrcode-image">
            <image class="image" mode="aspectFit" src="https://7072-programming-calendar-3b8b7a7d082-1304448256.tcb.qcloud.la/qr/${openId}-qr.png?sign=b5a610dc6ae15c9427720ab617a2f18a&t=1609438339" />
          </view>
        </view>
      </view>
      `;
    return wxml;
  }

  // canvas樣式
  renderStyle() {
    const contentWidth = this.canvasWidth - 50;
    const mainColor = "#1296db";
    const style = {
      container: {
        width: this.canvasWidth,
        height: this.canvasHeight,
        backgroundColor: "#fff",
      },
      top: {
        width: this.canvasWidth,
        height: 82,
        backgroundColor: mainColor,
        flexDirection: "row",
        justifyContent: "space-around",
        alignItems: "center",
      },
      topLeft: {
        width: this.canvasWidth / 3,
        height: 82,
        textAlign: "center",
        alignItems: "center",
      },
      topCenter: {
        width: this.canvasWidth / 3,
        height: 82,
        lineHeight: 82,
        fontSize: 72,
        textAlign: "center",
        color: "#ffffff",
      },
      topRight: {
        width: this.canvasWidth / 3,
        height: 82,
      },
      en: {
        width: this.canvasWidth / 3,
        height: 30,
        fontSize: 20,
        textAlign: "center",
        color: "#ffffff",
        marginTop: 15,
      },
      cn: {
        width: this.canvasWidth / 3,
        height: 30,
        textAlign: "center",
        color: "#ffffff",
      },
      banner: {
        width: this.canvasWidth,
        flexDirection: "row",
        justifyContent: "center",
        marginTop: 20,
      },
      bannerImage: {
        width: this.imgWidth,
        height: this.imgHeight,
      },
      middle: {
        flexDirection: "column",
        justifyContent: "center",
        alignItems: "center",
        marginTop: 20,
      },
      pData: {
        width: contentWidth,
        height: 170,
        lineHeight: "1.8em",
      },
      pMore: {
        width: contentWidth,
        height: 60,
        lineHeight: "1.8em",
      },
      qrcode: {
        height: 130,
        flexDirection: "row",
        justifyContent: "space-between",
        backgroundColor: "#CCE6FF",
        paddingLeft: 20,
        paddingTop: 20,
      },
      qrcodeImage: {
        width: 90,
        height: 90,
        marginRight: 20,
        borderRadius: 45,
        flexDirection: "row",
        justifyContent: "center",
        alignItems: "center",
        backgroundColor: "#fff",
      },
      image: {
        width: 90,
        height: 90,
        scale: 0.9,
        borderRadius: 45,
      },
      appinfo: {
        flexDirection: "column",
        justifyContent: "flex-start",
        alignItems: "flex-start",
        height: 80,
      },
      avatar: {
        flexDirection: "row",
        justifyContent: "flex-start",
        width: this.canvasWidth / 1.8,
        height: 30,
      },
      avatarImage: {
        width: 30,
        height: 30,
        borderRadius: 15,
        marginRight: 5,
      },
      avatarNikename: {
        width: this.canvasWidth / 1.8,
        height: 22,
        lineHeight: 22,
        marginTop: 5,
      },
      appname: {
        width: this.canvasWidth / 2,
        height: 23,
        fontSize: 16,
        color: "#0081FF",
        marginTop: 8,
        marginLeft: 35,
      },
      appdesc: {
        width: this.canvasWidth / 2,
        height: 20,
        fontSize: 14,
        marginLeft: 35,
      },
    };
    return style;
  }

  // 省略不(bù)相關代碼
}
複制代碼

該文件就(jiù)是(shì)我們畫海報的(de)地(dì / de)方,就(jiù)是(shì)生成 WXML 和(hé / huò) Style 然後導出(chū) 。

3.5、wxml-to-canvas 組件的(de)注意事項

wxml-to-canvas 組件對 wxml 模闆支持有限 :

  • 支持 <view><text><image> 三種标簽,通過 class 匹配 style 對象中的(de)樣式。
  • 文字必須用 <text> 标簽包含,否則不(bù)顯示。并且必須設置寬高。文字寬度必須先确定,超出(chū)則會自動截斷。所以(yǐ)動态文字可以(yǐ)根據字數,動态設置寬度。

樣式方面:

  • 對象屬性值爲(wéi / wèi)對應 wxml 标簽的(de) cass 駝峰形式需爲(wéi / wèi)每個(gè)元素指定 width 和(hé / huò) height 屬性,否則會導緻布局錯誤
  • 存在(zài)多個(gè) className 時(shí),位置靠後的(de)優先級更高,子(zǐ)元素會繼承父級元素的(de)可繼承屬性。
  • 元素均爲(wéi / wèi) flex 布局。left/top 等 僅在(zài) absolute 定位下生效。

因爲(wéi / wèi)文字必須用 <text> 标簽包含,并且必須設置寬高,文字寬度必須先确定,超出(chū)則會自動截斷。所以(yǐ)動态文字可以(yǐ)根據字數,動态設置寬度。所以(yǐ)寫布局非常麻煩,我推薦大(dà)家爲(wéi / wèi)每一個(gè)元素設置背景,這(zhè)樣可以(yǐ)看到(dào)元素渲染的(de)範圍和(hé / huò)寬高。如下所示:

borderColor/marginBottom/marginTop 可使用,雖然微信文檔中沒寫。

3.6、海報預覽和(hé / huò)下載頁面

生成 canvas 并調用接口生成圖片後,我們攜帶參數跳轉到(dào)下一個(gè)頁面,先來(lái)看看 WXML,非常簡單:

<view>
  <view class="share-container">
    <image
      src="{{src}}"
      mode="widthFix"
      class="image"
      style="height: {{height}}px;"
    ></image>
  </view>
  <view class="save-button">
    <van-button
      bind:tap="saveImage"
      block
      round
      icon="down"
      size="large"
      type="info"
      >保存到(dào)手機</van-button
    >
  </view>
</view>
複制代碼

js 邏輯

const app = getApp();
Page({
  data: {
    src: "",
    date: "",
    width: "",
    height: "",
  },
  onLoad() {
    const eventChannel = this.getOpenerEventChannel();
    eventChannel.on("acceptDataFromOpenerPage", (data) => {
      // console.log("data", data)
      this.setData({
        showPopup: true,
        date: data.date,
        src: data.share.tempFilePath,
        width: data.container.layoutBox.width,
        height: data.container.layoutBox.height,
      });
    });
  },
  getDatestr() {
    const { strings } = app.globalData.dateInfo;
    return strings;
  },
  saveImage() {
    wx.showLoading({
      title: "處理中...",
    });
    const _this = this;
    wx.getSetting({
      success(res) {
        if (!res.authSetting["scope.writePhotosAlbum"]) {
          wx.authorize({
            scope: "scope.writePhotosAlbum",
            success() {
              _this.save();
            },
            fail() {
              wx.showToast({
                title: "授權失敗",
                icon: "none",
              });
            },
          });
        } else {
          _this.save();
        }
      },
    });
  },
  save() {
    wx.saveImageToPhotosAlbum({
      filePath: this.data.src,
      success() {
        wx.showToast({
          title: "保存成功",
          icon: "none",
        });
      },
      fail() {
        wx.showToast({
          title: "保存失敗",
          icon: "none",
        });
      },
    });
  },
});
複制代碼

以(yǐ)上(shàng)就(jiù)是(shì)我開發海報功能的(de)邏輯和(hé / huò)代碼,僅供參考吧,如果你有相關經驗歡迎讨論交流,留下你的(de)真知灼見吧。

4、編程日曆小程序頁面截圖

最後,分幾張小程序的(de)頁面截圖

預覽預覽

5、後續叠代計劃

增加用戶等級計劃、對應等級可以(yǐ)換一些禮物,其餘的(de)。。。你有什麽想法?歡迎交流!


作者:杭州程序員張張
來(lái)源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出(chū)處。

相關案例查看更多