vue+swiper+vue-cropper+html2canvas+微信分享

236 阅读1分钟

需求

  • 模板轮播:swiper
  • 制作模板:
  1. 可以选择图片,选择图片后可以对图片进行拖拽,缩放:vue-cropper
  2. 输入文案配置好海报后,可以生成图片进行保存:html2canvas

安装依赖:

"dependencies": {
    "html2canvas": "^1.4.1",
    "swiper": "^8.0.0",
    "vue-cropper": "^1.0.5",
    "weixin-js-sdk": "^1.6.0"
}

代码

<template>
  <div class="detail" :class="{ noscroll: type === 4 }">
    <template v-if="type < 3">
      <img class="banner" src="https://ingcare.oss-cn-beijing.aliyuncs.com/activity/poster/banner.png" />
      <swiper
        :centeredSlides="true"
        :slidesPerView="1.54"
        :spaceBetween="30"
        :modules="modules"
        :pagination="{ clickable: true }"
        :loop="true"
        :noSwiping="true"
        @slideChange="activeIndexChange"
        ref="mySwiper"
      >
        <swiper-slide>
          <div class="inner inner_1">
            <img class="cropper" src="https://ingcare.oss-cn-beijing.aliyuncs.com/activity/poster/pic1.png" />
            <div class="text">
              <div>
                <p>我是名<span>康复师</span></p>
                <p>今日被孩子打两次,咬一次,<br>还常常不被周围人理解,<br>累到想摆烂,但不敢,也不甘!</p>
              </div>
            </div>
            <img class="qrcode" :src="`https://ingcare.oss-cn-beijing.aliyuncs.com/activity/poster/qr${source}.png`" />
          </div>
        </swiper-slide>
        <swiper-slide>
          <div class="inner inner_2">
            <img class="cropper" src="https://ingcare.oss-cn-beijing.aliyuncs.com/activity/poster/pic2.png" />
            <div class="text">
              <div>
                <p>我是名<span>康复师</span></p>
                <p>拒绝标签,拒绝被定义,<br>我的热爱,我自会全力以赴!</p>
              </div>
            </div>
            <img class="qrcode" :src="`https://ingcare.oss-cn-beijing.aliyuncs.com/activity/poster/qr${source}.png`" />
          </div>
        </swiper-slide>
        <swiper-slide>
          <div class="inner inner_3">
            <img class="cropper" src="https://ingcare.oss-cn-beijing.aliyuncs.com/activity/poster/pic3.png" />
            <div class="text">
              <div>
                <p>我是名<span>康复师</span></p>
                <p>我的专业,不是年纪说了算。<br>铸就实力,打破质疑!</p>
              </div>
            </div>
            <img class="qrcode" :src="`https://ingcare.oss-cn-beijing.aliyuncs.com/activity/poster/qr${source}.png`" />
          </div>
        </swiper-slide>
        <swiper-slide>
          <div class="inner inner_4">
            <img class="cropper" src="https://ingcare.oss-cn-beijing.aliyuncs.com/activity/poster/pic4.png" />
            <div class="text">
              <div>
                <p>我是名<span>孤独症孩子的家长</span></p>
                <p>老一辈说“没事,长大就好了”<br>我知道,不能赌,也不敢赌<br>我更相信科学</p>
              </div>
            </div>
            <img class="qrcode" :src="`https://ingcare.oss-cn-beijing.aliyuncs.com/activity/poster/qr${source}.png`" />
          </div>
        </swiper-slide>
        <swiper-slide>
          <div class="inner inner_5">
            <img class="cropper" src="https://ingcare.oss-cn-beijing.aliyuncs.com/activity/poster/pic5.png" />
            <div class="text">
              <div>
                <p>我是名<span>孤独症孩子的家长</span></p>
                <p>焦虑是我,无措是我,坚持是我,<br>不放弃,绝不认输也是我<br>努力生活,和孩子一起变得更好</p>
              </div>
            </div>
            <img class="qrcode" :src="`https://ingcare.oss-cn-beijing.aliyuncs.com/activity/poster/qr${source}.png`" />
          </div>
        </swiper-slide>
        <swiper-slide>
          <div class="inner inner_6">
            <img class="cropper" src="https://ingcare.oss-cn-beijing.aliyuncs.com/activity/poster/pic6.png" />
            <div class="text">
              <div>
                <p>我是名<span>孤独症孩子的家长</span></p>
                <p>谁还一直在新手村,孩子在成长<br>我也在成长</p>
              </div>
            </div>
            <img class="qrcode" :src="`https://ingcare.oss-cn-beijing.aliyuncs.com/activity/poster/qr${source}.png`" />
          </div>
        </swiper-slide>
      </swiper>
      <div class="btns">
        <button
          @click="handleShowLogin"
          v-if="type === 1"
        >制作我的态度海报</button>
        <button
          v-if="type === 2"
        >制作我的态度海报</button>
        <input
          type="file"
          accept="image/png, image/jpeg"
          @change="handleUploadIntercept"
          v-if="type === 2"
        />
      </div>
    </template>
    <template v-if="type === 3">
      <div class="inner big" :class="`inner_${index}`">
        <img class="bg" :src="`https://ingcare.oss-cn-beijing.aliyuncs.com/activity/poster/t${index}.png`" alt="">
        <vueCropper
          class="swiper-no-swiping"
          ref="cropper"
          :img="option.img"
          :mode="option.mode"
          :outputSize="option.size"
          :outputType="option.outputType"
          :centerBox="option.centerBox"
          :canScale="option.canScale"
        ></vueCropper>
        <div class="text">
          <div>
            <p>我是名<span>{{ poster.data.name }}</span></p>
            <p v-html="fildata(poster.data.content)"></p>
          </div>
        </div>
        <img class="qrcode" :src="`https://ingcare.oss-cn-beijing.aliyuncs.com/activity/poster/qr${source}.png`" />
      </div>
      <div class="menu">
        <button @click="handleBack">返回</button>
        <button @click="handleCreateImage">保存</button>
      </div>
    </template>
    <template v-if="type === 4">
      <div class="inner big canvas">
        <div class="poster">
          <img :src="imgUrl" alt="">
        </div>
        <div class="tips">长按图片保存到相册</div>
      </div>
    </template>

    <van-dialog
      :show="login.show"
      title="手机号验证"
      show-cancel-button
      confirm-button-text="确定"
      confirm-button-color="#576B95"
      @confirm="handleLoginConfirm"
      @cancel="handleCancel"
    >
      <van-field
        v-model="login.data.phone"
        required
        maxlength="11"
        placeholder="请输入您的手机号"
        :formatter="formatter"
        type="digit"
      />
      <van-field
        v-model="login.data.sms"
        center
        required
        maxlength="6"
        placeholder="请输入验证码"
        :formatter="formatter"
        type="digit"
      >
        <template #button>
          <div
            v-if="!login.onSms"
            class="time"
            @click="handleGetSms"
          >获取验证码</div>
          <van-count-down
            :time="login.time"
            format="ss"
            @finish="login.onSms = false"
            v-else
          >
            <template #default="timeData">
              <div class="time">{{ timeData.seconds }}</div>
            </template>
          </van-count-down>
        </template>
      </van-field>
    </van-dialog>
    <van-dialog
      :show="poster.show"
      title="填写海报内容"
      show-cancel-button
      confirm-button-text="确定"
      confirm-button-color="#576B95"
      @confirm="handlePosterConfirm"
      @cancel="handleCancel"
    >
      <van-field
        v-model="poster.data.name"
        label="我是名"
        :label-width="48"
        maxlength="8"
        placeholder="请输入身份"
        show-word-limit
      />
      <van-field
        v-model="poster.data.content"
        rows="4"
        autosize
        type="textarea"
        maxlength="40"
        placeholder="请输入内容"
        show-word-limit
        @blur="handleBlurCheck"
      />
    </van-dialog>
  </div>
</template>

<script>
import "swiper/swiper.min.css";
import "swiper/swiper-bundle.min.css";
import 'vue-cropper/dist/index.css'
import html2canvas from 'html2canvas';
import { Swiper, SwiperSlide } from "swiper/vue/swiper-vue";
import { Pagination } from 'swiper';
import { VueCropper }  from 'vue-cropper';
import { Toast } from 'vant';
import wx from'weixin-js-sdk';

const serialId = String(new Date().getTime())
export default {
  name: 'Poster',
  components: {
    Swiper,
    SwiperSlide,
    VueCropper
  },
  data() {
    return {
      type: 1,
      index: 1,
      login: {
        show: false,
        time: 60000,
        onSms: false,
        data: {
          sms: "",
          phone: "",
        }
      },
      poster: {
        show: false,
        onCheck: false,
        data: {
          name: "",
          content: "",
        }
      },
      option: {
        img: "https://swiperjs.com/demos/images/nature-1.jpg",
        size: 1,
        mode: "cover",
        centerBox: false,
        canScale: true,
        outputType: "jpeg"
      },
      imgUrl: ""
    }
  },
  setup() {
    const formatter = (value) => value.replace(/\D/g, '');

    function fildata(value) {
      console.log(value);
      return value.replaceAll("\n", "<br>")
    }

    return {
      fildata,
      formatter,
      modules: [Pagination],
    };
  },
  computed: {
    // 渠道:0-其他,1-微信,2-微博,3-抖音,4-小红书,5-培训部
    source() {
      return this.$route.query.source || 0
    }
  },
  mounted() {
    this.handleWxInit()
  },
  methods: {
    handleWxInit() {
      var ua = navigator.userAgent.toLowerCase();
      var isWeixin = ua.indexOf('micromessenger') != -1;
      console.log(isWeixin);
      if (isWeixin) {
        this.$axios({
          url: `/poster/get`,
          method: 'POST',
          data: {
            serialId,
            channel: this.source,
            mobile: this.login.data.phone,
          }
        }).then(res => {
          if (res.code == 200) {
            this.handleSetWxShare()
          }
        })
      }
    },
    handleSetWxShare(params) {
      const shareAppMessage = {
        title:"年少未必轻狂?遇事才见真章!",
        desc: "资历≠能力,年轻≠言轻",
        link: `http://10.10.15.189:10199/detail?source=${this.source}`,
        imgUrl: "https://ingcare.oss-cn-beijing.aliyuncs.com/activity/poster/wxPost.png",
        success() {
          console.log("分享成功")
        }
      }
      const jsApiList = [ // 需要使用的JS接口列表
        "onMenuShareTimeline", //朋友圈
        "onMenuShareAppMessage", //微信朋友
        "onMenuShareQQ", // QQ
        "onMenuShareWeibo", //微博
        "onMenuShareQZone" //QQ空间
      ]

      wx.config({
        debug:true,// 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
        appId:params.appId,// 必填,公众号的唯一标识
        timestamp:params.timestamp,// 必填,生成签名的时间戳
        nonceStr:params.nonceStr,// 必填,生成签名的随机串
        signature:params.signature,// 必填,签名
        jsApiList// 必填,需要使用的JS接口列表
      });

      wx.ready(function () {
        wx.checkJsApi({
          jsApiList,
          success(res) {
            console.log("checkJsApi: " + JSON.stringify(res));
          }
        })
        wx.onMenuShareTimeline(shareAppMessage);
        wx.onMenuShareAppMessage(shareAppMessage);
        wx.onMenuShareQQ(shareAppMessage);
        wx.onMenuShareWeibo(shareAppMessage);
        wx.onMenuShareQZone(shareAppMessage);
      })
    },
    handleBlurCheck() {
      if (this.poster.data.content.split("\n").length > 4) {
        console.log("blur");
        this.poster.onCheck = true
        Toast('换行符最多只能输入三次')
        setTimeout(() => {
          this.poster.onCheck = false
        }, 2000);
        return false
      }
      return true
    },
    activeIndexChange(swiper) {
      this.index = (swiper.activeIndex - 1) || 6
    },
    handleShowLogin() {
      this.login.show = true
    },
    handleGetSms() {
      this.$axios({
        url: `/poster/smsCode`,
        method: 'POST',
        data: {
          serialId,
          channel: this.source,
          mobile: this.login.data.phone,
        }
      }).then(res => {
        console.log(res)
        if (res.code == 200) {
          Toast('发送成功')
          this.login.onSms = true
        }
      })
    },
    handleLoginConfirm() {
      this.$axios({
        url: `/poster/valid`,
        method: 'POST',
        data: {
          serialId,
          channel: this.source,
          mobile: this.login.data.phone,
          smsCode: this.login.data.sms,
        }
      }).then(res => {
        if (res.code == 200) {
          Toast('登录成功')
          console.log(this.login.data)
          this.type = 2
          this.handleCancel()
        }
      })
    },
    handlePosterConfirm() {
      if (!this.poster.data.name || !this.poster.data.content) {
        Toast('内容不能为空')
        return
      }
      if (this.poster.onCheck) {
        return
      } else if (!this.handleBlurCheck()) {
        return
      }
      this.$axios({
        url: `/poster/generate`,
        method: 'POST',
        data: {
          serialId,
          channel: this.source,
          mobile: this.login.data.phone,
        }
      }).then(res => {
        if (res.code == 200) {
          console.log(this.poster.data)
          this.type = 3
          this.handleCancel()
        }
      })
    },
    handleCancel() {
      this.login.show = false
      this.poster.show = false
    },
    handleBack() {
      // 已登陆状态
      this.type = 2
      this.index = 1
      this.poster = {
        show: false,
        onCheck: false,
        data: {
          name: "",
          content: "",
        }
      }
      this.option.img = ""
      this.imgUrl = ""
    },
    // 选择图片
    handleUploadIntercept (e) {
      const files = e.target.files
      if (files && files.length) {
        let reader = new FileReader()
        reader.readAsDataURL(files[0])
        reader.onload = () => {
          this.option.img = reader.result
          this.poster.show = true
        }
      }
    },
    // 生成海报图片
    handleCreateImage() {
      const me = this
      setTimeout(() => {
        // const ratio = window.devicePixelRatio < 3 ? window.devicePixelRatio : 2
        html2canvas(document.getElementsByClassName('inner')[0], {
          useCORS: true, // 【重要】开启跨域配置
          scale: 8,
          dpi: window.devicePixelRatio * 8,
          allowTaint: true, // 允许跨域图片
        }).then(function(canvas) {
          me.type = 4
          me.$nextTick(function() {
            let dataURL = canvas.toDataURL("image/png")
            me.imgUrl = dataURL
          })
        });
      }, 1000);
    }
  }
}
</script>