文生图项目总结

文生图

image.png

功能点

  • 页面进来获取背景图url和图片宽高(根据比例和手机屏幕处理过的宽高)
  • 渲染图片(背景图最后生成图片模糊,换成img展示解决)
  • 添加多个文字,编辑文字内容,拖拽改变文字位置,删除文字
  • 编辑文字内容,颜色,字体,大小
  • 生成图片预览
  • 转发图片给好友、保存

bug点

  • 使用背景图最后生成图片模糊,换成img展示解决
  • 图片必须转成base64格式,最后才能保存,否则不显示(例如二维码和背景图)

拖拽插件

多个文字框时,点击改变isActive控制编辑状态框的显示隐藏

  • 只保留一个clicked 事件
  • 在外层添加时间.stop 重置数组激活状态为false

整个页面分为上中下三部分,中间可滚动

  • 整体100vh ,采用纵向flex布局
  • 上下区域高度不固定,下面样式设置直接写,不采用弹窗形式
  • 中间区域 overflow-y: auto;高度超出会自动有滚动条
  • 中间图片竖图顶部会遮挡,不能使用center布局即可

解决不同图片尺寸都要居中显示,切最后生成图片又得是图片区域

  • 采用父盒子 flex:center,内层盒子居中显示:横图可以,超高竖图顶部会有遮挡;所以判断竖图就定义宽90%,高度auto
  • 根据图片比例动态计算宽高,动态设置内层盒子的宽高;盒子里放图片和文字
  • 假如是9:16 的图片,固定一个高度,计算固定宽度;其他比例宽度为手机屏幕宽度,高度计算

删除其中一个位置会跑到上个位置

  • 拖拽left、top赋值,删除加个show变量false隐藏,不直接删除这一项

添加文字后,第一次点击聚焦拖拽框,文字会移动到左上角的位置

  • 添加文字,给了一个默认left、top值让文字显示在中间
  • 但是拖拽组件没有设置初始偏移量 x、y
  • 保持初始偏移量和添加文字默认位置一致即可解决

vcolorpicker颜色选取插件

切换字体字体包

  • vue获取文件夹下所有字体包 生成数组:字体包的名字和下拉选择名字保持一致
  • 类名定义为font-索引值
  • 这样下拉选中得到索引,动态绑定类名即可切换字体

vue瀑布流插件vue-masonry

第二版小程序

  • 第一版公众号无法实现转发图片给好友,只能分享公众号;所以修改为小程序
  • 小程序没有找到拖拽插件,所以打算用uniapp生成小程序
  • 结果uniapp也没有拖拽插件,最终使用uniapp 嵌入H5页面(编辑贺卡页面)
  • uniapp打包小程序弊端:使用uview插件占内存较大很快就超了2M
  • 于是采用分包,主包只放tabbar页面,分包可以有多个,但是总限制8M,每个包限制2M
  • 分包可以键自己的图片文件夹和插件
  • 上线要在编辑器上点击发行,才会运行在生产环境
let url = ''
let url1 = ''
let url2 = ''
if (process.env.NODE_ENV === 'development') {
	console.log('开发环境')
	url = 'http://192.168.0.19:9888'
	url1 = 'http://192.168.2.89:8080'
	url2 = 'http://192.168.0.19:9888'
} else {
	console.log('生产环境')
	url = 'https://wst.maitewang.com/stage-api'
	url1 = 'https://wst.maitewang.com'
	url2 = 'https://wst.maitewang.com/stage-api'
}

export const baseUrl = url;
export const h5Url = url1;
export const imgUrl = url2;
  • 小程序发布之后,H5会有缓存,大概5个小时左右会更新;小程序24小时会基本更新所有用户

uniapp 打包小程序并嵌入H5注意事项

小程序里边嵌入H5 使用 web-view

  • 上线需要配置业务域名,即H5访问地址
  • 域名必须使用https;域名不能带端口号;需要下载文件放服务器上
  • web-view会缓存,修改H5之后,线上不会变; 在过一段时间(时间不定,一天或者几小时,无明显规律)是可以进行缓存刷新的:有需要的解决
  • 可以ngix配置不缓存html文件,即可解决;还有静态资源的缓存比较麻烦
  • 将微信从后台退出再打开并重新进入小程序;

uniapp可以直接调取微信小程序转发图片api

  • 转发有发送朋友和保存相册功能
  • 发送朋友的图片下面可以带小程序的连接,但是对小程序类别有要求,不是所有的都可以
  • 如果类别不符合不能配置转发带链接,否则转发功能也会调不起来
  • 上传、下载功能需要配置上传下载接口域名

小程序和H5互相跳转并传参

  • 小程序跳转H5直接在链接之后?拼接参数
    -<web-view class="menu" v-if="query" :src="h5Url+'/#/detail?query='+ query"></web-view>
  • 跳转参数加密 encodeURIComponent(JSON.stringify(query))
  • H5在mounted接收参数并解密JSON.parse(decodeURIComponent(this.$route.query.query))
  • H5跳转小程序
 let path = `/page_subject/detail/share?imgURL=${path1}&width=${this.width}&height=${this.height}&id=${this.id}`
 wx.miniProgram.navigateTo({
    url: path,
    success() {
        Toast.clear();
    },
    fail() {
         Toast('跳转失败');
    }, //小程序地址
 });

小程序右上角三个点的分享小程序默认分享好友和朋友圈都是灰色的

  • 需要写代码开启
  • 如果每个页面都需要分享,写一个公共的js文件,采用MiXin 混入的方式

新建公共share.js文件

export default {
	data() {
		return {
			share: {
				title: '快来一起制作新年贺卡吧~',
				// bug:require本地图片分包之后图片就显示不出来了
				// 不设置图片,默认展示页面 
				// imageUrl: 'https://ossweb-img.qq.com/images/lol/web201310/skin/big10001.jpg',
				path: '/pages/home/index'
			}
		}
	},
	onLoad: function() {
		wx.showShareMenu({
			withShareTicket: true,
			menus: ["shareAppMessage", "shareTimeline"]
		})
	},
	onShareAppMessage(res) { //发送给朋友
		return {
			title: this.share.title,
			imageUrl: this.share.imageUrl,
			path: this.share.path
		}
	},
	onShareTimeline(res) { //分享到朋友圈
		return {
			title: this.share.title,
			imageUrl: this.share.imageUrl,
			path: this.share.path
		}
	},
}

main.js文件混入

import share from './common/share.js'
Vue.mixin(share)

页面内想单独覆盖分享的文字和图片,可在页面内重写data

data() {
		return {
			share: {
				title: '快来一起制作新年贺卡吧~',
				// bug:require本地图片分包之后图片就显示不出来了
				// 不设置图片,默认展示页面 
				// imageUrl: 'https://ossweb-img.qq.com/images/lol/web201310/skin/big10001.jpg',
				path: '/pages/home/index'
			}
		}
	},

H5禁止默认的长按转发事件

  • 给图片设置 img{ pointer-events:none; }
  • 如果图片有点击事件,点击事件不会触发,可以将点击事件加载img的外层盒子上

开启微信小程序图片默认长按事件

  • 小程序<image>默认没有长按识别事件
  • 在图片标签上添加属性 show-menu-by-longpress

图片列表使用骨架屏

1707040601850.png

  • 图片加载过程会出现没有高度,所以效果不是很好
  • 监听图片加载@load,加载之前显示灰色骨架屏,加载成功显示图片
  • bug:在下拉刷新,重新调取接口后@load不执行
  • 解决:下拉刷新,先清空数组,在重新调取接口
<view v-show="!item.isLoading"></view>
<image v-show="item.isLoading" :src="baseUrl + item.img" @load="imgLoad(index)"></image>
// load回调证明图片加载完成
imgLoad(index) {
    this.imgList[index].isLoading = true;
},

首页关注我们页面内任意拖动

image.png

  • movable-area 可以放在最外层
  • movable-view是movable-area的直接子元素
  • movable-area必须设置宽高,子元素只会在这个区域内移动
  • movable-area设置宽高会占位置,可以position: absolute就会不占位置
  • movable-view会随着页面向上滚动而上去,使用position: fixed即可不动
<!-- 可拖拽关注 -->
<movable-area :style="{ width:`${screenWidth - 52}px`, height: `${screenHeight - 50}px`}" class="focusBox">
    <movable-view :x="screenWidth - 52" :y="screenHeight / 2" direction="all" @click="isFocus = true" class="focus">
	<image src="../../static/icon/focus.png" alt="" class="focusImg"></image>
    </movable-view>
</movable-area>


.focusBox {
    position: absolute;
    .focus {
	 position: fixed;
	 z-index: 999;
	 .focusImg {
		 width: 112rpx;
		 height: 108rpx;
	 }
   }

小程序编辑页面

http://192.168.2.89:8080/#/detail?query=%7B%22url%22%3A%22%2Fprofile%2Fupload%2F2024%2F01%2F25%2F123_20240125204643A031.jpg%22,%22id%22%3A22%7D

<template>
	<view>
		<web-view class="menu" v-if="query" :src="h5Url+'/#/detail?query='+ query"></web-view>
	</view>
</template>

<script>
	import {
		h5Url,
	} from '../../common/http.js'
	export default {
		data() {
			return {
				query: '',
				h5Url: '',
			}
		},
		onLoad(options) {
			console.log('query', JSON.parse(decodeURIComponent(options.query)))
			this.query = options.query
			this.h5Url = h5Url
			console.log(1111, this.h5Url + '/#/detail?query=' + this.query)
			// this.getWidth(url).finally(() => {
			// 	this.query = encodeURIComponent(JSON.stringify({
			// 		url: url,
			// 		width: this.width,
			// 		height: this.height
			// 	}))
			// 	console.log(55555, {
			// 			url: url,
			// 			width: this.width,
			// 			height: this.height
			// 		},
			// 		this.query, h5Url + '/#/detail?query=' + this.query)
			// })
		},
		methods: {
			// 获取图片宽高
			getWidth(url1) {
				let _this = this
				return new Promise((resolve, reject) => {
					let screenWidth = wx.getSystemInfoSync().windowWidth; // 获取当前窗口的宽度
					uni.getImageInfo({
						src: url,
						success(image) {
							console.log(222222, image.width, image.height);
							let imgRate = image.width / image.height;
							console.log(33333, Number((screenWidth / imgRate).toFixed()));

							if (imgRate <= 0.5625) {
								_this.width = 315;
								_this.height = 560;
							} else {
								_this.width = screenWidth;
								_this.height = Number((screenWidth / imgRate).toFixed());
							}
							resolve()
						},
						fail(err) {
							console.log(44444, err)
							_this.width = 310;
							_this.height = 551;
							reject()
						}
					});
				})
			},
		}
	}
</script>

<style scoped lang="scss">

</style>

H5编辑页面

<template>
  <div class="w100 vh100 flex flex-column bg-black">
    <div class="flex_r white bg-black w100 pd-30">
      <img src="../assets/img/share.png" alt="" style="width: 18px;margin-right: 6px;">
      <div @click="preview">转发</div>
    </div>
    <div @click.stop="onDeactivated" style="width: 100%; overflow-y: auto;" class="bg-black flex-1">
      <!-- :style="{ width: `${width}px`, height: `${height}px` }" -->
      <div style="width: 85%;" class="imgBox relative" :class="height > 550 ? 'top' : 'middle'">
        <img :src="url" alt="" class="w100 h100" style="display: block;pointer-events:none">

        <div v-for="(item, i) in arr" :key="i">
          <!-- clicked - 单击组件时
        activated - 单击组件时调用,以显示句柄
        deactivated - 用户单击组件外部的任何位置时调用,以便将其停用
        resizing - 组件调整大小时
        resizestop - 组件停止调整大小时
        dragging - 拖动组件时
        dragstop - 组件停止拖动时调用
         @deactivated="onDeactivated"
          :parentLimitation="true" 
    -->
          <VueDragResize v-if="item.show" :sticks="['mr', 'br', 'bm', 'bl',]" :isActive="item.isEdit" :w="190" :h="80"
            :x="70" :y="200" :z="999" @resizing="resize" @dragging="resize" @clicked="onActivated(i)" contentClass="box"
            :style="{ 'left': item.left + 'px', 'top': item.top + 'px', }">
            <p class="absolute gushi"
              :style="{ 'color': item.color, 'font-size': item.size + 'px', 'opacity': item.opacity / 100, 'font-family': item.family, 'font-weight': item.bold, }">
              {{ item.text }}</p>
          </VueDragResize>
        </div>
        <!-- 二维码 -->
        <img :src="code" alt="" class="absolute code" :class="width < height ? 'center' : 'right'">
      </div>
    </div>

    <div class="w100 bg-white">
      <div class="flex bg pd-30 plr-60" :class="isIos ? 'pb-50' : ''" v-show="!index">
        <div @click="add" class="flex_l">
          <img src="../assets/img/add.png" alt="" style="width: 16px;height: 16px;">
          <div class="ml-6">藏头诗</div>
        </div>
        <div @click="add1" class="flex_l"><img src="../assets/img/add1.png" alt="" style="width: 22px;height: 22px;">
          <div class="ml-6">添加祝福语</div>
        </div>
        <div @click="changeImg" class="flex_l"><img src="../assets/img/add2.png" alt="" style="width: 22px;height: 22px;">
          <div class="ml-6">更换背景</div>
        </div>
      </div>
      <div class="flex bg pd-30" v-show="index">
        <div @click="del" class="mr-20 flex_l">
          <img src="../assets/img/del.png" alt="" style="width: 18px;height: 18px;">
          <div class="ml-6">删除</div>
        </div>
        <div @click="action = 2" :class="action == 2 ? 'red' : ''" class="mr-20 flex_l">
          <img v-if="action == 2" src="../assets/img/edit-1.png" alt="" style="width: 18px;height: 18px;">
          <img v-else src="../assets/img/edit.png" alt="" style="width: 18px;height: 18px;">
          <div class="ml-6">编辑</div>
        </div>
        <div @click="action = 3" :class="action == 3 ? 'red' : ''" class="mr-20 flex_l">
          <img v-if="action == 3" src="../assets/img/color-1.png" alt="" style="width: 18px;height: 18px;">
          <img v-else src="../assets/img/color.png" alt="" style="width: 18px;height: 18px;">
          <div class="ml-6">颜色</div>
        </div>
        <div @click="action = 4" :class="action == 4 ? 'red' : ''" class="mr-20 flex_l">
          <img v-if="action == 4" src="../assets/img/size-1.png" alt="" style="width: 18px;height: 18px;">
          <img v-else src="../assets/img/size.png" alt="" style="width: 18px;height: 18px;">
          <div class="ml-6">样式</div>
        </div>
        <div v-if="action" @click="finish" class="flex_l">
          <img src="../assets/img/finish.png" alt="" style="width: 18px;height: 18px;">
          <div class="ml-6">完成</div>
        </div>
      </div>
      <div class="pd-30 pb-40" v-if="action == 1">
        <!-- show-word-limit -->
        <van-field v-model.trim="message" placeholder="输入藏头词:例如新年快乐(10字以内)" clearable type="textarea" autosize rows="1"
          maxlength="10">
          <template #button>
            <van-button size="small" type="info" class="solo" @click="getPoetry">生成</van-button>
          </template>
        </van-field>
      </div>
      <div class="pd-30 pb-40" v-if="action == 2 || action == 5">
        <van-field v-model="arr[index].text" placeholder="" clearable type="textarea" autosize rows="1" />
      </div>
      <div class="pd-30 pb-40" v-if="action == 3">
        <div class="flex mr-30 mb-30">
          <div>透明度</div>
          <van-slider v-model="arr[index].opacity" :min="0" :max="100" style="width: 80%;" active-color="#E94467" />
        </div>
        <div class="flex flex-wrap">
          <div class="mlr-12 mt-4">
            <colorPicker v-model="arr[index].color" />
          </div>
          <div v-for="(  color, indexs  ) in   colors  " :key="indexs" class="color-option"
            :style="{ backgroundColor: color, border: '1px solid #fff5f6' }" @click="selectColor(color)"
            :class="{ active: color === arr[index].color, }"></div>
        </div>
      </div>
      <div class="pd-30 pb-40" v-if="action == 4">
        <div class="flex mb-30">
          <div>字体大小</div>
          <van-slider v-model="arr[index].size" :min="10" :max="80" class="flex-1 mlr-30" active-color="#E94467" />
          <div @click="changeBold(arr[index].bold)" :class="{ 'bold': arr[index].bold == 700 }" class="red">加粗</div>
        </div>
        <div class="flex_l flex-top">
          <div class="mr-20">选择字体</div>
          <div style="max-height: 100px;overflow-y: auto;" class="flex-1">
            <div v-for="(item, i ) in fonts" :key="i" :style="{ 'font-family': 'font-' + (i + 1) }" @click="selectFont(i)"
              :class="{ 'size-42 red': 'font-' + (i + 1) === arr[index].family }" class="mb-10">
              {{
                item }}</div>
          </div>
        </div>
      </div>
      <div class="plr-30  pb-40" v-if="action == 5">
        <div class="flex_l flex-top">
          <div style="max-height: 100px;overflow-y: auto;" class="flex-1">
            <div v-for="(  item, i  ) in   texts  " :key="i" @click="selectText(item)"
              :class="{ 'size-36 red': item === arr[index].text }" class="mb-16">{{
                item }}</div>
          </div>
        </div>
      </div>
    </div>

    <!-- 更换背景图组件 -->
    <changeImg ref="changeImgRef" @getImg="getImg"></changeImg>
  </div>
</template>

<script>
import { Toast, Dialog } from 'vant';
import wx from 'weixin-js-sdk';
import VueDragResize from 'vue-drag-resize';
import html2canvas from "html2canvas";
import changeImg from '../components/changeImg.vue';
export default {
  name: 'app',
  components: {
    VueDragResize,
    changeImg,
  },
  data() {
    return {
      id: '', //图片id
      url: '', // 背景图
      width: 315,
      height: 560,

      message: "", //藏头诗提示语
      arr: [], // 文字
      index: '', // 当前编辑的文字索引
      action: null, // 1 藏头诗  2 编辑文字  3 颜色  4 样式  5 添加祝福语

      //  ['#fff', '#000000', '#F9E0C6', '#F4C68C', '#F08400', '#F08080', '#FFA07A', '#F44336', '#E91E63', '#F012BE', '#9C27B0', '#673AB7', '#7B68EE', '#3F51B5', '#2196F3',
      //   '#03A9F4', '#00BCD4', '#009688', '#2ECC40', '#4CAF50', '#8BC34A', '#FFEB3B',
      //   '#FFFF00', '#FFDC00', '#FFC107', '#FF851B','#FF5722', '#795548', '#9E9E9E', '#607D8B'],
      colors:
        ['#000000', '#FFFFFF', '#F9E0C6', '#FDF6C0', '#F4C68C', '#FFA07A', '#F08080', '#F08400', '#E85500', '#FEC140', '#E6FF00', '#879D13', '#37A75D', '#2ECC40', '#149125',
          '#009688', '#29C2BA', '#298CFE', '#3F51B5', '#7B68EE', '#9C27B0', '#E91E63',
          '#FC0015', '#C00503', '#8A1F01', '#940304',],
      fonts: [],
      texts: ['新年快乐', '新春到了,愿你快乐,幸福平安!', '祝你新年快乐,喜事连连!', '恭祝万事顺,幸福不止步', '春节到来之际,送上我最真挚的祝福:新年快乐!',
        '新的一年,祝你永怀善意,清澈明朗,无忧无虑,平安顺遂,吉祥圆满。',
        '新年新气象,祝福财气旺,祝君事业有成,合家欢乐,步步高升!',
        '岁杪春临,辞旧迎新,专此恭祝:吾师尊长,兄姊好友,新岁维祺,万事胜意。',
        '新的一年,愿你永怀善意,清澈明朗,无忧无虑,平安顺遂,吉祥如意。',
        '岁末将至,敬颂冬绥 。平安喜乐,万事胜意。岁岁常欢愉,年年皆胜意。',
        '日迈月征,朝暮轮转,愿新年胜旧年,欢愉且胜意,万事尽可期。',
        '大财,小财,意外财,财源滚滚;亲情,友情,爱情,情情如意官运;福运,财运,桃花运,运运亨通。',
        '新的一年,愿你将遇之人皆为挚友,愿你去往之地皆为热土。天高地阔,万事胜意。人海沧沧,顺遂无忧。',
        '祝你在新的一年里 无疾病侵扰,无岁月白头,无枯木羁绊。四时春秋平安度过,万千山水得以一游。',
        '新年到,放鞭炮:一响鸿运照;二响忧愁抛;三响烦恼消;四响财运到;五响收入高;六响身体好;七响心情妙;八响平安罩;九响幸福绕;十响事业节节高!',
        '祈家事平和,花朝月夕多胜意。愿人间芳华,岁岁年年长安康。乐亲朋,岁无忧,新春快乐。',
        '裁雪为画,揉风作诗。愿岁月温柔,点点滴滴成全人间悲喜;愿你毕生欢愉,朝朝暮暮造就星河奔腾。'],

      imgURL: '',
      w: 0,
      h: 0,
      code: '', // 二维码

      isIos: false,
    }
  },
  mounted() {
    console.log(this.appSource())
    this.isIos = Boolean(this.appSource() == 'ios')
    console.log(JSON.parse(decodeURIComponent(this.$route.query.query)))
    let query = JSON.parse(decodeURIComponent(this.$route.query.query))
    this.url = '/stage-api' + query.url
    this.id = query.id
    this.getBase64(this.url)
    let code = require('../assets/img/code.png')
    this.getBase64Code(code)

    // 字体
    //参数:1.路径;2.是否遍历子目录;3.正则表达式
    const files = require.context("../assets/fonts", true).keys();
    this.fonts = files.map(item => item.split('-')[1].split('.')[0])
    console.log(files.map(item => item.split('-')[1].split('.')[0]));
    // ['思源黑体', '包图小白体', '峰广明锐体', '豆豆字体', '站酷庆科黄油体', '锐字真言体', '卓健橄榄简体', '演示悠然小楷', '优设标题黑', '沐瑶随心手写体', '青柳隶书', '演示佛系体', '优设好身体', '优设鲨鱼菲特健康体', '阿朱泡泡体', '阿里巴巴普惠体', '庞门正道标题体', '传奇特战体', '思源宋体', '清松手写体', '龚帆免费体', '851力量体', '赤薔薇灰姑娘', '可愛少女体', '胖胖猪肉体', '千图小兔体']
  },
  methods: {
    //判断安卓和ios
    appSource() {
      const u = navigator.userAgent;
      const isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
      if (isiOS) {
        return "ios";
      } else {
        return "android";
      }
    },
    getBase64(src) {
      var image = new Image();
      image.crossOrigin = '*'; // 支持跨域图片
      image.src = src + '?v=' + Math.random(); // 处理缓存
      image.onload = () => {
        var base64 = this.getBase64Image(image);
        this.url = base64
      }
    },
    getBase64Image(img) {
      var canvas = document.createElement("canvas");
      canvas.width = img.width;
      canvas.height = img.height;
      var ctx = canvas.getContext("2d");
      ctx.drawImage(img, 0, 0, img.width, img.height);
      var dataURL = canvas.toDataURL("image/png"); // 可选其他值 image/jpeg
      return dataURL;
    },
    //  本地二维码转base64
    getBase64Code(imgUrl) {
      window.URL = window.URL || window.webkitURL;
      const xhr = new XMLHttpRequest();
      xhr.open("get", imgUrl, true);
      xhr.responseType = "blob";
      xhr.onload = () => {
        if (xhr.status == 200) {
          //得到一个blob对象
          const blob = xhr.response;
          const oFileReader = new FileReader();
          oFileReader.readAsDataURL(blob);
          oFileReader.onloadend = e => {
            this.code = e.target.result;//base64编码 
          };
        }
      };
      xhr.send();
    },
    add() {
      this.action = 1
    },
    getPoetry() {
      if (!this.message) {
        this.$toast({
          message: '请输入藏头词'
        })
      } else {
        Toast.loading({
          message: '生成中...',
          forbidClick: true,
        });
        this.$axios.get('/stage-api/system/management/getMessages?messages=' + this.message).then(res => {
          console.log(res.msg)
          if (!res.msg) {
            Toast.clear();
            Dialog.alert({
              title: '温馨提示',
              message: '未获取到相关藏头,请更换提示语重新生成',
            }).then(() => {
              // on close
            });
          } else {
            this.message = ''
            this.arr.push({
              text: res.msg,
              color: '#FFFFFF',
              size: 24,
              opacity: 100,
              family: 'font-3',
              bold: 400,
              isEdit: false,
              show: true,
              top: 200,
              left: 70,
            })
            this.index = (this.arr.length - 1) + ''
            this.action = 2
            Toast.clear();
          }
        })
      }
    },
    add1() {
      this.arr.push({
        text: '新年快乐',
        color: '#FFFFFF',
        size: 28,
        opacity: 100,
        family: 'font-12',
        bold: 400,
        isEdit: false,
        show: true,
        top: 200,
        left: 70,
      })
      this.index = (this.arr.length - 1) + ''
      this.action = 5
    },
    changeImg() {
      this.$refs.changeImgRef.init()
    },
    getImg(query) {
      this.url = '/stage-api' + query.url
      this.id = query.id
      this.getBase64(this.url)
    },
    finish() {
      this.onDeactivated()
    },
    del() {
      this.$set(this.arr[this.index], 'show', false)
      this.onDeactivated()
    },
    selectColor(color) {
      this.$set(this.arr[this.index], 'color', color)
    },
    selectFont(i) {
      this.$set(this.arr[this.index], 'family', 'font-' + (i + 1))
    },
    selectText(item) {
      this.$set(this.arr[this.index], 'text', item)
    },
    changeBold(flag) {
      this.$set(this.arr[this.index], 'bold', flag == 700 ? 400 : 700)
    },

    // 拖拽时可以确定元素位置
    resize(newRect) {
      this.w = newRect.width;
      this.h = newRect.height;
      this.$set(this.arr[this.index], 'top', newRect.top)
      this.$set(this.arr[this.index], 'left', newRect.left)
    },
    onActivated(i) {
      console.log('点击了' + i,)
      this.arr.forEach((el, index) => {
        if (index == i) {
          this.index = i + ''
          this.$set(this.arr[index], 'isEdit', true)
          this.action = 2
        } else {
          this.$set(this.arr[index], 'isEdit', false)
        }
      });
    },
    onDeactivated() {
      this.index = null
      this.action = null
      this.arr.forEach((el, index) => {
        this.$set(this.arr[index], 'isEdit', false)
      });
    },
    preview() {
      this.onDeactivated()
      Toast.loading({
        message: '生成中...',
        forbidClick: true,
        loadingType: 'spinner',
      });
      setTimeout(() => {
        html2canvas(document.querySelector('.imgBox'), {
          backgroudColor: null,
          scale: 3, // 1125
        }).then(canvas => {

          let imgUrl = canvas.toDataURL('image/png');
          // console.log(imgUrl);
          this.imgURL = imgUrl;   //base64格式

          // 构建FormData对象,通过该对象存储要上传的文件
          const formData = new FormData();
          // 遍历当前临时文件List,将上传文件添加到FormData对象中
          formData.append('baseName', this.imgURL);
          this.$axios.post('/stage-api/system/management/upload', formData)
            .then(res => {
              console.log(88877, res.fileName)
              let path1 = res.fileName
              let path = `/page_subject/detail/share?imgURL=${path1}&width=${this.width}&height=${this.height}&id=${this.id}`
              wx.miniProgram.navigateTo({
                url: path,
                success() {
                  Toast.clear();
                },
                fail() {
                  Toast('跳转失败');
                }, //小程序地址
              });
            }).catch(err => {
              console.log(999, err)
            })
        })
      }, 100);
    },
  }
}
</script>
<style scoped lang="scss">
.imgBox {
  // margin-left: 30px;
  background: #000 url('') no-repeat center center;
  background-size: 100% 100%;

  &.top {
    left: 50%;
    top: 0%;
    transform: translateX(-50%);
  }

  &.middle {
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
  }
}

.color-option {
  width: 60px;
  height: 60px;
  margin: 8px;
  border-radius: 50%;
}

.color-option.active {
  border: 2px solid #FFFFFF;
}

::v-deep .m-colorPicker .colorBtn {
  width: 50px;
  height: 50px;
}

::v-deep .box .vdr-stick {
  width: 35px !important;
  height: 35px !important;
  // background: #E94467;
  border: 1px solid #fff;
  border-radius: 50%;

  background: linear-gradient(221.12deg, #FB3568 0%, #FF8B6B 100%);

  &.vdr-stick-br {
    bottom: -16px !important;
    right: -15px !important;
  }

  &.vdr-stick-mr {
    right: -15px !important;
  }

  &.vdr-stick-bm {
    bottom: -16px !important;
  }

  &.vdr-stick-bl {
    bottom: -16px !important;
  }
}

img.code {
  width: 100px;
  height: 100px;
  bottom: 20px;

  &.right {
    width: 100px;
    height: 100px;
    right: 20px;
  }

  &.center {
    left: 50%;
    transform: translateX(-50%);
  }
}

.van-cell {
  border: 1px solid #ccc;
}

::v-deep.m-colorPicker {
  border: 1px solid #ccc;
}

.gushi {
  animation: test 2s;
  left: 0;
  top: 0;
}

@keyframes test {
  0% {
    left: 0px;
    top: 1000px;
  }

  50% {
    transform: scale(1.5);
    left: 0px;
    bottom: 300px;
  }

  // 100% {
  //   left: 0;
  //   top: 0;
  // }
}

::v-deep .solo.van-button--info {
  color: #fff;
  background-color: #E94467;
  border: 1px solid #E94467;
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/414179.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络:IP

引言&#xff1a; IP协议是互联网协议族中的核心协议之一&#xff0c;负责为数据包在网络中传输提供路由寻址。它定义了数据包如何在互联网上从源地址传输到目的地址的规则和流程。IP协议使得各种不同类型的网络设备能够相互通信&#xff0c;实现了全球范围内的信息交换。 目录…

HTML-基础标签

1. HTML初识 1.1 什么是HTML HTML&#xff08;英文Hyper Text Markup Language的缩写&#xff09;中文译为“超文本标签语言”&#xff0c;是用来描述网页的一种语言。所谓超文本&#xff0c;因为它可以加入图片、声音、动画、多媒体等内容&#xff0c;不仅如此&#xff0c;它还…

nginx---------------重写功能 防盗链 反向代理 (五)

一、重写功能 rewrite Nginx服务器利用 ngx_http_rewrite_module 模块解析和处理rewrite请求&#xff0c;此功能依靠 PCRE(perl compatible regular expression)&#xff0c;因此编译之前要安装PCRE库&#xff0c;rewrite是nginx服务器的重要功能之一&#xff0c;重写功能(…

数据结构(C语言)代码实现(九)——迷宫探路表达式求值

目录 参考资料 迷宫探路 顺序栈头文件SqStack.h 顺序栈函数实现SqStack.cpp 迷宫探路主函数 表达式求值 链式顺序栈头文件LinkStack.h 链式顺序栈函数实现LinkStack.cpp 表达式求值主函数 测试结果 参考资料 数据结构严蔚敏版 2021-9-22【数据结构/严蔚敏】【顺序…

Django学习笔记-django使用pandas将上传的数据存到MySQL

1.models中创建与excel表结构相同模型 2.模型映射 python manage.py makemigrations myapp01,python manage.py migrate 3.创建index,添加form,enctype使用multipart/form-data 4.urls中导入views,填写路由 5.views中创建index 6.如果为GET请求,直接返回index.html,如果为PO…

历史新知网:寄快递寄个电脑显示器要多少钱?

以下文字信息由&#xff08;新史知识网&#xff09;编辑整理发布。 让我们赶紧来看看吧&#xff01; 问题1&#xff1a;快递寄电脑显示器要多少钱&#xff1f; 此物有多重&#xff1f; 顺丰寄就可以了&#xff0c;但是必须是原包装的&#xff0c;不然不好寄。 问题2&#xff1…

阿里云中小企业扶持权益,助力企业开启智能时代创业新范式

在数字化浪潮的推动下&#xff0c;中小企业正面临着转型升级的重要关口。阿里云深知中小企业的挑战与机遇&#xff0c;特别推出了一系列中小企业扶持权益&#xff0c;旨在帮助企业以更低的成本、更高的效率拥抱云计算&#xff0c;开启智能时代创业的新范式。 一、企业上云权益…

光伏预测 | Matlab基于CNN-SE-Attention-ITCN的多特征变量光伏预测

光伏预测 | Matlab基于CNN-SE-Attention-ITCN的多特征变量光伏预测 目录 光伏预测 | Matlab基于CNN-SE-Attention-ITCN的多特征变量光伏预测预测效果基本描述模型简介程序设计参考资料 预测效果 基本描述 Matlab基于CNN-SE-Attention-ITCN的多特征变量光伏预测 运行环境: Matla…

【初中生讲机器学习】12. 似然函数和极大似然估计:原理、应用与代码实现

创建时间&#xff1a;2024-02-23 最后编辑时间&#xff1a;2024-02-24 作者&#xff1a;Geeker_LStar 你好呀~这里是 Geeker_LStar 的人工智能学习专栏&#xff0c;很高兴遇见你~ 我是 Geeker_LStar&#xff0c;一名初三学生&#xff0c;热爱计算机和数学&#xff0c;我们一起加…

Day04:APP架构小程序H5+Vue语言Web封装原生开发Flutter

目录 常见APP开发架构 APP-开发架构-原生态-IDEA APP-开发架构-Web封装-平台 APP-开发架构-H5&Vue-HBuilderX WX小程序-开发架构-Web封装-平台 WX小程序-开发架构-H5&Vue-HBuilderX 思维导图 章节知识点&#xff1a; 应用架构&#xff1a;Web/APP/云应用/三方服…

[CISCN 2019华东南]Web11

打开题目 看到xff就应该想到抓包 看回显也是127.0.0.1&#xff0c;我们盲猜是不是ssti模板注入 输入{{7*7}}显示49 可以看的出来flag在根目录下 输入{system(‘cat /flag’)} 得到flag 知识点&#xff1a; 漏洞确认 一般情况下输入{$smarty.version}就可以看到返回的smarty…

nebula容器方式安装:docker 安装nebula到windows

感谢阅读 基础环境安装安装docker下载nebula 安装数据库命令行安装查询network nebula-docker-compose_nebula-net并初始化查询安装初始使用root&#xff08;God用户类似LINUX的root&#xff09; 关闭服务 安装UI 基础环境安装 安装docker 点我下载docker 下载nebula 数据…

Python 实现Excel自动化办公(中)

在上一篇文章的基础上进行一些特殊的处理&#xff0c;这里的特殊处理主要是涉及到了日期格式数据的处理&#xff08;上一篇文章大家估计也看到了日期数据的处理是不对的&#xff09;以及常用的聚合数据统计处理&#xff0c;可以有效的实现你的常用统计要求。代码如下&#xff1…

Spring Boot项目误将Integer类型写成int来进行传参

在处理项目中Idea中无报错&#xff1a; 问题&#xff1a; localhost:8080/param/m2在浏览器中输入&#xff1a;localhost:8080/param/m2 产生报错&#xff1a; This application has no explicit mapping for /error, so you are seeing this as a fallback. Tue Feb 27 20:55…

MATLAB_ESP32有限脉冲响应FIR无限脉冲响应IIR滤波器

要点 ESP32闪烁LED&#xff0c;计时LEDESP32基础控制&#xff1a;温控输出串口监控&#xff0c;LCD事件计数器&#xff0c;SD卡读写&#xff0c;扫描WiFi网络&#xff0c;手机控制LED&#xff0c;经典蓝牙、数字麦克风捕捉音频、使用放大器和喇叭、播放SD卡和闪存MP3文件、立体…

使用 kubeadm 部署k8s集群

一、所有节点系统初始化 1、常规初始化 2、内核版本升级以及内核限制文件参数修改 还可以考虑将旧版本的内核卸载 二、准备nginx负载均衡器和keepalived nginx四层代理&#xff1a; keepalived配置&#xff1a; nginx检测脚本&#xff1a; 三、所有节点部署docker&#xff0c…

2023年06月CCF-GESP编程能力等级认证Scratch图形化编程二级真题解析

一、单选题(共10题,共30分) 第1题 高级语言编写的程序需要经过以下( )操作,可以生成在计算机上运行的可执行代码。 A:编辑 B:保存 C:调试 D:编译 答案:D 第2题 默认小猫角色,执行下列程序,说法错误的是?( ) A:不按下空格键,小猫会随机移动 B:不按下空格…

高防IP简介

高防IP可以防御的有包括但不限于以下类型&#xff1a; SYN Flood、UDP Flood、ICMP Flood、IGMP Flood、ACK Flood、Ping Sweep 等攻击。高防IP专注于解决云外业务遭受大流量DDoS攻击的防护服务。支持网站和非网站类业务的DDoS、CC防护&#xff0c;用户通过配置转发规则&#x…

STM32F103学习笔记(六) RTC实时时钟(应用篇)

目录 1. RTC 实时时钟的应用场景 2. RTC 的配置与初始化 2.1 设置 RTC 时钟源 2.2 初始化 RTC 寄存器 2.3 中断配置 2.4 备份寄存器配置 2.5 校准 RTC 3. 实例演示代码 4. 总结 1. RTC 实时时钟的应用场景 实时时钟&#xff08;RTC&#xff09;在嵌入式系统中具有广泛…

html5盒子模型

1.边框的常用属性 border-color 属性 说明 示例 border-top-color 上边框颜色 border-top-color:#369; border-right-color 右边框颜色 border-right-color:#369; border-bottom-color 下边框颜色 border-bottom-color:#fae45b; border-left-color 左边框颜色…