技术文档(3)首次小程序开发的功能记录(2)

1,裁剪头像

  1. 点击头像进行选择图片。
<u-cell-item title="头像" @tap="hanAvarEdit">
	<img :src="avatarUrl" class="avatar" />
</u-cell-item>

跳转至crop页面

hanAvarEdit() {
	uni.chooseImage({
		count: 1,
		success: (res) => {
			const path = res.tempFilePaths[0]
			uni.navigateTo({
				url: `/pages/profile/crop?src=${encodeURIComponent(path)}`
			})
		}
	})
},
  1. 拿到图片后进行裁剪或重新选择图片(crop页面)。
<template>
  <view class="container">
    <uni-cropper
      :src="src"
      :crop-width="700"
      :crop-height="1000"
      @update:src="updateSrc"
      @upload-success="handleUploadSuccess"
    />
  </view>
</template>

<script>
import uniCropper from '@/components/uni-cropper/uni-cropper.vue'

export default {
  components: { uniCropper },
  data() {
    return { src: '' }
  },
  onLoad(options) {
    if (options.src) {
      this.src = decodeURIComponent(options.src)
    } else {
      uni.showToast({ title: '图片路径丢失', icon: 'none' })
      uni.navigateBack()
    }
  },
  methods: {
    updateSrc(newSrc) {
      this.src = newSrc
    },
    handleUploadSuccess(res) {
      uni.showToast({ title: '上传成功', icon: 'none' })
      uni.navigateBack()
    }
  }
}
</script>

<style scoped>
.container {
  padding: 20rpx;
}
</style>

  1. 组件文件,uni-cropper.vue
<template>
	<view class="uni-cropper-container">
		<canvas canvas-id="cropperCanvas" id="cropperCanvas" class="cropper-canvas" :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }" @touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd"></canvas>
		<view class="crop-frame" :style="{
        width: cropWidth + 'rpx',
        height: cropHeight + 'rpx',
        top: cropTop + 'rpx'
      }"></view>
		<view class="button-group">
			<u-button type="primary" class="cropper-btn" @click="getCrop">裁剪</u-button>
			<u-button class="cropper-btn" @click="reSelectImage">重新选择</u-button>
		</view>
	</view>
</template>
<script>
	import global from '../../utils/global.js'
	
	export default {
		name: 'uni-cropper',
		props: {
			src: String,
			cropWidth: {
				type: Number,
				default: 300
			},
			cropHeight: {
				type: Number,
				default: 300
			}
		},
		data() {
			return {
				localSrc: '',
				imgInfo: null,
				canvasWidth: 0,
				canvasHeight: 0,
				position: {
					x: 0,
					y: 0
				},
				scale: 1,
				touches: [],
				initialDistance: 0,
				initialScale: 1,
				cropTop: 0
			}
		},
		watch: {
			src(newVal) {
				if (newVal) {
					this.localSrc = newVal
					this.loadImageInfo(newVal)
				}
			}
		},
		mounted() {
			uni.createSelectorQuery()
				.in(this)
				.select('.uni-cropper-container')
				.boundingClientRect(rect => {
					this.canvasWidth = rect.width
					this.canvasHeight = rect.height
					this.cropTop = (this.canvasHeight - uni.upx2px(this.cropHeight)) / 2
					if (this.src) {
						this.localSrc = this.src
						this.loadImageInfo(this.src)
					}
				})
				.exec()
		},
		methods: {
			loadImageInfo(path) {
				uni.getImageInfo({
					src: path,
					success: res => {
						this.imgInfo = res
						this.initCanvas()
					},
					fail: () => {
						uni.showToast({
							title: '图片加载失败',
							icon: 'none'
						})
					}
				})
			},
			initCanvas() {
				const imgW = this.imgInfo.width
				const imgH = this.imgInfo.height
				const cropW = uni.upx2px(this.cropWidth)
				const cropH = uni.upx2px(this.cropHeight)
	
				const scaleW = cropW / imgW
				const scaleH = cropH / imgH
				this.scale = Math.min(scaleW, scaleH)
	
				const drawW = imgW * this.scale
				const drawH = imgH * this.scale
	
				this.position.x = (this.canvasWidth - drawW) / 2
				this.position.y = (this.canvasHeight - drawH) / 2
	
				this.drawImage()
			},
			drawImage() {
				if (!this.imgInfo) return
				const ctx = uni.createCanvasContext('cropperCanvas', this)
				ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
	
				const imgW = this.imgInfo.width * this.scale
				const imgH = this.imgInfo.height * this.scale
				const cutLeft = (this.canvasWidth - uni.upx2px(this.cropWidth)) / 2
				const cutTopPx = this.cropTop
	
				ctx.save()
				ctx.beginPath()
				ctx.rect(cutLeft, cutTopPx, uni.upx2px(this.cropWidth), uni.upx2px(this.cropHeight))
				ctx.clip()
	
				ctx.drawImage(this.localSrc, this.position.x, this.position.y, imgW, imgH)
				ctx.restore()
				ctx.draw()
			},
			getDistance(p1, p2) {
				const dx = p2.x - p1.x
				const dy = p2.y - p1.y
				return Math.sqrt(dx * dx + dy * dy)
			},
			onTouchStart(e) {
				this.touches = e.touches
				if (e.touches.length === 2) {
					this.initialDistance = this.getDistance(e.touches[0], e.touches[1])
					this.initialScale = this.scale
				}
			},
			onTouchMove(e) {
				if (e.touches.length === 1 && this.touches.length === 1) {
					const dx = e.touches[0].x - this.touches[0].x
					const dy = e.touches[0].y - this.touches[0].y
					this.position.x += dx
					this.position.y += dy
					this.touches = e.touches
					this.drawImage()
				} else if (e.touches.length === 2) {
					const newDistance = this.getDistance(e.touches[0], e.touches[1])
					const scaleChange = newDistance / this.initialDistance
					this.scale = Math.max(0.5, Math.min(5, this.initialScale * scaleChange))
					this.drawImage()
				}
			},
			onTouchEnd() {
				this.touches = []
			},
			getCrop() {
				uni.canvasToTempFilePath({
					canvasId: 'cropperCanvas',
					width: this.canvasWidth,
					height: this.canvasHeight,
					success: (res) => {
						console.log('裁剪成功', res.tempFilePath)
	
						uni.uploadFile({
							url: global.uploadUrl,
							filePath: res.tempFilePath,
							name: 'file',
							success: uploadRes => {
								const response = JSON.parse(uploadRes.data)
								if (response.status === '200') {
									const param = {
										mobile: uni.getStorageSync("compInfo").mobile,
										profilePictureFileName: response.body.fileName,
										profilePictureFileSize: response.body.fileSize,
										profilePictureFileToken: response.body.fileToken
									}
									this.$global.request('/crm/api/edc/wechat/personalCenter/updateUserInfo', 'post', param)
										.then(resDa => {
											if (resDa.data.status === '200') {
												uni.navigateBack()
											}
										}).catch(err => console.log(err))
								}
							}
						})
					},
					fail: (err) => {
						console.error('裁剪失败', err)
						uni.showToast({
							title: '裁剪失败',
							icon: 'none'
						})
					}
				}, this)
			},
			reSelectImage() {
				uni.chooseImage({
					count: 1,
					success: res => {
						this.$emit('update:src', res.tempFilePaths[0])
					}
				})
			}
		}
	}
</script>
<style scoped>
	.uni-cropper-container {
		position: relative;
		width: 100%;
		height: 400px;
		/* 这里可改为动态高度或父容器撑开 */
	}
	
	.cropper-canvas {
		width: 100%;
		height: 100%;
		background: #000;
	}
	
	.crop-frame {
		position: absolute;
		left: 50%;
		transform: translateX(-50%);
		border: 2px dashed #fff;
		pointer-events: none;
		width: auto;
		/* 通过动态绑定宽高 */
		height: auto;
	}
	
	.button-group {
		display: flex;
		flex-direction: column;
		align-items: center;
		margin-top: 40rpx;
	}
	
	.cropper-btn {
		width: 100%;
		margin-bottom: 20rpx;
		background-color: #007aff;
		color: #fff;
		text-align: center;
		border-radius: 10rpx;
		border: none;
	}
</style>

2,微信签名

思路:使用canvas先画出一个画布,然后再画布上签名,形成一个图片,最后把图片通过接口传向后端。

pages/personal/signing.vue

<template>
	<view class="servi-agreem">
		<Signature @confirm="onConfirm" @clear="onClear" />
	</view>
</template>
<script>
	import Signature from "@/components/Signature/index.vue";
	import global from '../../utils/global.js'
	
	export default {
		data() {
			return {
				detailDTO:{
					bizCode:'',
					signFileToken:'',
				},
			}
		},
		onLoad: function(option) {
			// console.log(option);
			this.detailDTO.bizCode = option.bizCode;
		},
		components: {
			Signature
		},
		methods: {
			onConfirm(filePath) {
				console.log("签名图片路径:", filePath);
				// 可以调用上传、保存等操作
				wx.uploadFile({
					url: global.uploadUrl,
					filePath: filePath,
					name: 'file', // 后端接收文件字段名
					success: (res) => {
						if(res.statusCode == '200'){
							console.log('上传成功', res);
							let resData = JSON.parse(res.data)
							this.detailDTO.signFileToken = resData.body.fileToken;
							
							this.$global.request('/crm/api/edc/wechat/personalCenter/signAgreement', 'post',this.detailDTO).then(
								(resp) => {
									// console.log(resp.data.body)
									uni.switchTab({
										url: '/pages/personal/personal'
									});
									
								}).catch((err) => {
								
							});
						}
					},
					fail: (err) => {
						console.error('上传失败', err);
					}
				});
			},
			onClear() {
				console.log("签名已清除");
			},
	
		}
	}
</script>
<style scoped lang="scss">
	.servi-agreem {
		background: #f1f1f1;
		width: 750rpx;
		height: 100%;
		min-height: 100vh;
	}
</style>

组件页,@/components/Signature/index.vue

<template>
	<view class="signature-wrapper">
		<canvas canvas-id="signatureCanvas" id="signatureCanvas" class="signature-canvas" disable-scroll="true" @touchstart="startDraw" @touchmove="moveDraw" @touchend="endDraw" />
		<view class="btn-group">
			<button @tap="clearCanvas">清除</button>
			<button @tap="exportSignature">保存</button>
		</view>
	</view>
</template>
<script>
	export default {
	  name: "Signature",
	  emits: ["confirm", "clear"],
	  data() {
	    return {
	      ctx: null,
	      isDrawing: false,
	      canvasId: "signatureCanvas",
	      canvasWidth: 0,
	      canvasHeight: 0
	    };
	  },
	  mounted() {
	    this.initCanvas();
	  },
	  methods: {
	    initCanvas() {
	      this.$nextTick(() => {
	        uni.createSelectorQuery()
	          .in(this)
	          .select("#signatureCanvas")
	          .boundingClientRect(rect => {
	            this.canvasWidth = rect.width;
	            this.canvasHeight = rect.height;
	            this.ctx = uni.createCanvasContext(this.canvasId, this);
	            this.ctx.setStrokeStyle("#000");
	            this.ctx.setLineWidth(8);
	            this.ctx.setLineCap("round");
	            this.ctx.setLineJoin("round");
	          })
	          .exec();
	      });
	    },
	
	    startDraw(e) {
	      const { x, y } = e.touches[0];
	      this.ctx.beginPath();
	      this.ctx.moveTo(x, y);
	      this.isDrawing = true;
	    },
	
	    moveDraw(e) {
	      if (!this.isDrawing) return;
	        const { x, y } = e.touches[0];
	        this.ctx.lineTo(x, y);
	        this.ctx.stroke();
	    },
	
	    endDraw() {
	      this.ctx.draw(true);  // 在 touchend 时绘制整段线
	        this.isDrawing = false;
	    },
	
	    clearCanvas() {
	      this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
	      this.ctx.draw();
	      this.$emit("clear");
	    },
	
	    exportSignature() {
	      uni.canvasToTempFilePath(
	        {
	          canvasId: this.canvasId,
	          success: res => {
	            this.$emit("confirm", res.tempFilePath);
	          },
	          fail: err => {
	            uni.showToast({ title: "保存失败", icon: "none" });
	          }
	        },
	        this
	      );
	    }
	  }
	};
</script>
<style scoped>
	.signature-wrapper {
	  display: flex;
	  flex-direction: column;
	  align-items: center;
	}
	.signature-canvas {
	  width: 100%;
	  height: 300px;
	  border: 1px solid #ccc;
	  background-color: #fff;
	}
	.btn-group {
	  margin-top: 20rpx;
	  display: flex;
	  gap: 30rpx;
	}
</style>

3,页面下拉刷新

  1. 配置页面开启下拉刷新
// pages/your-page/your-page.json
{
  "enablePullDownRefresh": true
}
  1. 页面逻辑实现
// 监听页面下拉刷新
onPullDownRefresh() {
	Promise.all([
		this.autoUpdate(),
		this.init(),
		this.initData(),
	]).then(() => {
		uni.stopPullDownRefresh();
	});
},
created() {
	
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

酒鼎

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值