需求
- 模板轮播:swiper
- 制作模板:
- 可以选择图片,选择图片后可以对图片进行拖拽,缩放:vue-cropper
- 输入文案配置好海报后,可以生成图片进行保存: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>