This commit is contained in:
hongchao 2025-08-29 11:22:52 +08:00
commit 1228bebe04
3 changed files with 216 additions and 208 deletions

View File

@ -1,228 +1,229 @@
<template> <template>
<view class="whole canvas-autograph flexc" @touchmove.prevent.stop @wheel.prevent.stop v-show="modelValue"> <view
<canvas class="scroll-view" id="mycanvas" canvas-id="mycanvas" class="whole canvas-autograph flexc"
@touchstart="touchstart" @touchmove.prevent.stop
@touchmove="touchmove" @wheel.prevent.stop
@touchend="touchend" v-show="modelValue"
disable-scroll="true"/> >
<view class="fun-box"> <canvas
<div class="hint"> class="scroll-view"
<text class="rotate">请在下方空白区域横向书写签名</text> id="mycanvas"
</div> canvas-id="mycanvas"
<view class="fun-box-btn clear flex" @click="clear"> @touchstart="touchstart"
<text>清空</text> @touchmove="touchmove"
</view> @touchend="touchend"
<view class="fun-box-btn confirm flex" @click="confirm"> disable-scroll="true"
<text>确认</text> />
</view> <view class="fun-box">
<view class="fun-box-btn cancel flex" @click="cancel"> <div class="hint">
<text>取消</text> <text class="rotate">请在下方空白区域横向书写签名</text>
</view> </div>
</view> <view class="fun-box-btn clear flex" @click="clear">
</view> <text>清空</text>
</view>
<view class="fun-box-btn confirm flex" @click="confirm">
<text>确认</text>
</view>
<view class="fun-box-btn cancel flex" @click="cancel">
<text>取消</text>
</view>
</view>
<!-- 横向提示 -->
<view v-if="showToast" class="rotate-toast">
<text class="rotate-text">{{ toastMsg }}</text>
</view>
</view>
</template> </template>
<script setup> <script setup>
/* import { ref, reactive, watch, getCurrentInstance } from 'vue'
使用如下
<canvas-autograph v-model="isCanvas" @complete="complete"/>
// const emits = defineEmits(['update:modelValue', 'complete'])
let isCanvas = ref(false) const props = defineProps({
// modelValue: Boolean,
const complete = e=>{ })
console.log(e) const _this = getCurrentInstance()
}
*/ let points = reactive([]) //
import { ref, reactive, watch, getCurrentInstance } from 'vue' let canvaCtx = reactive(uni.createCanvasContext('mycanvas', _this)) //
canvaCtx.lineWidth = 6
canvaCtx.lineCap = 'round'
canvaCtx.lineJoin = 'round'
const emits = defineEmits(['update:modelValue','complete']) //
let hasDrawn = ref(false)
const props = defineProps({ //
modelValue:Boolean, const showToast = ref(false)
}) const toastMsg = ref('')
const _this = getCurrentInstance() const showRotateToast = (msg) => {
watch(()=>props.modelValue,e=>{ toastMsg.value = msg
// tabbar showToast.value = true
// setTimeout(() => (showToast.value = false), 2000)
},{ }
immediate:true, // false
})
let points = reactive([]) // //
const touchstart = (e) => {
let canvaCtx = reactive(uni.createCanvasContext('mycanvas', _this)) // let startX = e.changedTouches[0].x
// let startY = e.changedTouches[0].y
canvaCtx.lineWidth = 6; let startPoint = { X: startX, Y: startY }
canvaCtx.lineCap = 'round' points.push(startPoint)
canvaCtx.lineJoin = 'round' canvaCtx.beginPath()
hasDrawn.value = true //
}
// //
const touchstart = e=>{ const touchmove = (e) => {
let startX = e.changedTouches[0].x let moveX = e.changedTouches[0].x
let startY = e.changedTouches[0].y let moveY = e.changedTouches[0].y
let startPoint = {X:startX,Y:startY} let movePoint = { X: moveX, Y: moveY }
points.push(startPoint); points.push(movePoint)
// if (points.length >= 2) {
canvaCtx.beginPath(); draw()
} }
// }
const touchmove = e=>{ //
let moveX = e.changedTouches[0].x const draw = () => {
let moveY = e.changedTouches[0].y if (points.length < 2) return
let movePoint = {X:moveX,Y:moveY} canvaCtx.beginPath()
points.push(movePoint) // canvaCtx.moveTo(points[0].X, points[0].Y)
let len = points.length for (let i = 1; i < points.length; i++) {
if(len>=2){ canvaCtx.lineTo(points[i].X, points[i].Y)
draw() }
} canvaCtx.stroke()
} canvaCtx.draw(true)
// points = [points[points.length - 1]]
// const draw = ()=> { }
// let point1 = points[0] //
// let point2 = points[1] const touchend = () => {
// points.shift() points = []
// canvaCtx.moveTo(point1.X, point1.Y) }
// canvaCtx.lineTo(point2.X, point2.Y) //
// canvaCtx.stroke()
// canvaCtx.draw(true)
// }
const draw = () => {
if (points.length < 2) return;
canvaCtx.beginPath();
canvaCtx.moveTo(points[0].X, points[0].Y);
for (let i = 1; i < points.length; i++) {
canvaCtx.lineTo(points[i].X, points[i].Y);
}
canvaCtx.stroke();
canvaCtx.draw(true);
//
points = [points[points.length - 1]]; //
}
//
const touchend = e=>{
points = [];
}
//
// const clear = ()=>{
// return uni.getSystemInfo()
// .then(res=>{
// canvaCtx.clearRect(0, 0, res.windowWidth, res.windowHeight);
// canvaCtx.draw(true);
// return res
// })
// .catch(err=>{
// // console.log(err);
// })
// }
const clear = () => { const clear = () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
uni.createSelectorQuery() uni
.createSelectorQuery()
.in(_this) .in(_this)
.select('#mycanvas') // id canvas .select('#mycanvas')
.fields({ size: true, rect: true }, data => { .fields({ size: true, rect: true }, (data) => {
if (!data) { if (!data) {
reject('Canvas not found') reject('Canvas not found')
return return
} }
const { width, height } = data const { width, height } = data
// canvas
canvaCtx.clearRect(0, 0, width, height) canvaCtx.clearRect(0, 0, width, height)
canvaCtx.draw(true) canvaCtx.draw(true)
hasDrawn.value = false
resolve() resolve()
}) })
.exec() .exec()
}) })
} }
//
// const confirm = () => {
const confirm = ()=>{ if (!hasDrawn.value) {
uni.canvasToTempFilePath({ canvasId: 'mycanvas', }, _this, _this.parent) showRotateToast('请先签名再确认')
.then(res=>{ return
console.log(res.tempFilePath); }
emits('complete',res.tempFilePath) uni.canvasToTempFilePath({ canvasId: 'mycanvas' }, _this, _this.parent).then((res) => {
cancel() emits('complete', res.tempFilePath)
}) cancel()
} })
// }
const cancel = ()=>{ //
clear().then(res=>emits('update:modelValue',false)) const cancel = () => {
} clear().then(() => emits('update:modelValue', false))
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.canvas-autograph { .canvas-autograph {
position: fixed; position: fixed;
z-index: 998; z-index: 998;
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
top: 0; top: 0;
left: 0; left: 0;
background-color: #fff; background-color: #fff;
.scroll-view { .scroll-view {
width: calc(100% - 120rpx); width: calc(100% - 120rpx);
height: 99.6%; height: 99.6%;
background-color: #FFFFFF; background-color: #ffffff;
border: 2px solid #333; border: 2px solid #333;
} }
.fun-box { .fun-box {
position: absolute; position: absolute;
height: 90rpx; right: 0;
right: 0; bottom: 0px;
bottom: 0px; display: flex;
height: auto; flex-direction: column;
display: flex; .fun-box-btn {
flex-direction: column; width: 100rpx;
.fun-box-btn { height: 160rpx;
width: 100rpx; color: #ffffff;
height: 160rpx; border-radius: 20rpx;
color: #FFFFFF; border: 1rpx solid #c0c0c0;
border-radius: 20rpx; display: flex;
border: 1rpx solid #C0C0C0; align-items: center;
display: flex; justify-content: center;
align-items: center; text {
justify-content: center; transform: rotate(90deg);
text { }
transform: rotate(90deg); }
} .clear {
} color: #909399;
.clear { background-color: #f4f4f5;
color: #909399; }
background-color: #F4F4F5; .confirm {
} background-color: #409eff;
.confirm { margin: 3px 0;
background-color: #409EFF; }
margin: 3px 0; .cancel {
} background-color: #f67d7d;
.cancel { }
background-color: #F67D7D; .hint {
} width: 100rpx;
.hint { min-height: 240rpx;
width: 100rpx; display: flex;
min-height: 240rpx; align-items: center;
display: flex; justify-content: center;
align-items: center; color: #f67d7d;
justify-content: center; background: transparent;
color: #F67D7D; margin-bottom: 150rpx;
background: transparent; .rotate {
margin-bottom: 150rpx; display: inline-block;
transform: rotate(90deg);
white-space: nowrap;
font-size: 33rpx;
}
}
}
}
.rotate { /* 横向提示样式 */
display: inline-block; .rotate-toast {
transform: rotate(90deg); position: fixed;
transform-origin: center; top: 50%;
white-space: nowrap; left: 50%;
font-size: 33rpx; transform: translate(-50%, -50%);
} background: rgba(0, 0, 0, 0.75);
} padding: 20rpx;
} border-radius: 24rpx;
z-index: 999;
} /* 重点调整 */
width: 100rpx; /* 宽度只需少量,留给旋转文字 */
min-height: 400rpx; /* 高度拉长,保证横向文字能完整显示 */
display: flex;
align-items: center;
justify-content: center;
}
.rotate-text {
color: #fff;
font-size: 36rpx;
transform: rotate(90deg);
display: inline-block;
white-space: nowrap; /* 保证一行显示 */
}
</style> </style>

View File

@ -186,7 +186,9 @@ export default {
isOverToday: false, isOverToday: false,
isShow: false, isShow: false,
message: '', message: '',
position: 'bottom' // 'top' | 'bottom' position: 'bottom',
screenHeight: null,
screenWidth: null,
} }
}, },
onBackPress(options) { onBackPress(options) {
@ -244,6 +246,11 @@ export default {
const systemInfo = uni.getSystemInfoSync(); const systemInfo = uni.getSystemInfoSync();
console.log('设备信息:', systemInfo); console.log('设备信息:', systemInfo);
this.systemInfo = systemInfo; this.systemInfo = systemInfo;
//
const screenHeight = systemInfo.screenHeight; // 1000
const screenWidth = systemInfo.screenWidth; // 600
this.screenHeight = screenHeight;
this.screenWidth = screenWidth;
} catch (error) { } catch (error) {
console.error('获取设备信息失败:', error); console.error('获取设备信息失败:', error);
} }
@ -510,7 +517,7 @@ export default {
// //
console.log('拍照前聚焦...'); console.log('拍照前聚焦...');
console.log('开始拍照...'); console.log('开始拍照...');
CameraPreview.takePicture({width:640, height:540, quality: 50}, async (base64PictureData) => { CameraPreview.takePicture({width:this.screenWidth, height:this.screenHeight, quality: 50}, async (base64PictureData) => {
console.log('拍照返回数据',base64PictureData); console.log('拍照返回数据',base64PictureData);
await this.processImage(base64PictureData); await this.processImage(base64PictureData);
}); });

View File

@ -8,7 +8,7 @@
个人电子签名维护,支持手机拍照上传图片以及手写签名推荐使用手写签名请横向书写签名 个人电子签名维护,支持手机拍照上传图片以及手写签名推荐使用手写签名请横向书写签名
</div> </div>
<div class="btns"> <div class="btns">
<button class="btn" type="primary" @click="handlePhoto">拍照/上传</button> <!-- <button class="btn" type="primary" @click="handlePhoto">拍照/上传</button> -->
<button class="btn" type="primary" @click="open">手写签名</button> <button class="btn" type="primary" @click="open">手写签名</button>
</div> </div>
<miliu-autograph v-model="isCanvas" @complete="complete"></miliu-autograph> <miliu-autograph v-model="isCanvas" @complete="complete"></miliu-autograph>
@ -327,10 +327,10 @@ const uploadSignUrl = async (url) => {
margin: 10rpx; margin: 10rpx;
border-radius: 20rpx; border-radius: 20rpx;
.btn { .btn {
width: 40%; width: 100%;
&:first-child { /* &:first-child {
margin-right: 20px; margin-right: 20px;
} } */
} }
} }
.signature-page { .signature-page {