电子签名

This commit is contained in:
binbin_pan 2025-01-08 18:42:45 +08:00
parent f90e3120e0
commit 6293881de0
5 changed files with 416 additions and 34 deletions

View File

@ -0,0 +1,167 @@
<template>
<view class="whole canvas-autograph flexc" @touchmove.prevent.stop @wheel.prevent.stop v-show="modelValue">
<canvas class="scroll-view" id="mycanvas" canvas-id="mycanvas"
@touchstart="touchstart"
@touchmove="touchmove"
@touchend="touchend"/>
<view class="fun-box">
<view class="fun-box-btn clear flex" @click="clear">
<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>
</template>
<script setup>
/*
使用如下
<canvas-autograph v-model="isCanvas" @complete="complete"/>
//
let isCanvas = ref(false)
//
const complete = e=>{
console.log(e)
}
*/
import { ref, reactive, watch, getCurrentInstance } from 'vue'
const emits = defineEmits(['update:modelValue','complete'])
const props = defineProps({
modelValue:Boolean,
})
const _this = getCurrentInstance()
watch(()=>props.modelValue,e=>{
// tabbar
//
},{
immediate:true, // false
})
let points = reactive([]) //
let canvaCtx = reactive(uni.createCanvasContext('mycanvas', _this)) //
//
canvaCtx.lineWidth = 4;
canvaCtx.lineCap = 'round'
canvaCtx.lineJoin = 'round'
//
const touchstart = e=>{
let startX = e.changedTouches[0].x
let startY = e.changedTouches[0].y
let startPoint = {X:startX,Y:startY}
points.push(startPoint);
//
canvaCtx.beginPath();
}
//
const touchmove = e=>{
let moveX = e.changedTouches[0].x
let moveY = e.changedTouches[0].y
let movePoint = {X:moveX,Y:moveY}
points.push(movePoint) //
let len = points.length
if(len>=2){
draw()
}
}
//
const draw = ()=> {
let point1 = points[0]
let point2 = points[1]
points.shift()
canvaCtx.moveTo(point1.X, point1.Y)
canvaCtx.lineTo(point2.X, point2.Y)
canvaCtx.stroke()
canvaCtx.draw(true)
}
//
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 confirm = ()=>{
uni.canvasToTempFilePath({ canvasId: 'mycanvas', }, _this, _this.parent)
.then(res=>{
console.log(res.tempFilePath);
emits('complete',res.tempFilePath)
cancel()
})
}
//
const cancel = ()=>{
clear().then(res=>emits('update:modelValue',false))
}
</script>
<style scoped lang="scss">
.canvas-autograph {
position: fixed;
z-index: 998;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
.scroll-view {
width: 100%;
height: 100%;
background-color: #FFFFFF;
}
.fun-box {
position: absolute;
height: 90rpx;
right: 0;
bottom: 0px;
height: auto;
display: flex;
flex-direction: column;
.fun-box-btn {
width: 100rpx;
height: 160rpx;
color: #FFFFFF;
border-radius: 20rpx;
border: 1rpx solid #C0C0C0;
display: flex;
align-items: center;
justify-content: center;
text {
transform: rotate(90deg);
}
}
.clear {
color: #909399;
background-color: #F4F4F5;
}
.confirm {
background-color: #409EFF;
}
.cancel {
background-color: #F67D7D;
}
}
}
</style>

View File

@ -57,7 +57,7 @@
"style": {
"navigationBarTitleText": "新购绑定"
}
},//
}, //
{
"path": "pages/new-purchase/bind/coding-scan",
"style": {
@ -130,19 +130,19 @@
"style": {
"navigationBarTitleText": "编码出库"
}
},// 3.
}, // 3.
{
"path": "pages/picking/outbound/code-outScan",
"style": {
"navigationBarTitleText": "编码出库"
}
},
{
"path": "pages/picking/outbound/codeOutScan",
"style": {
"navigationBarTitleText": "编码出库"
}
},
{
"path": "pages/picking/outbound/codeOutScan",
"style": {
"navigationBarTitleText": "编码出库"
}
},
// 4.
{
"path": "pages/picking/outbound/num-outbound",
@ -167,10 +167,10 @@
{
"path": "pages/back/addBack",
"style": {
"navigationStyle": "custom"
"navigationStyle": "custom"
// "navigationBarTitleText": "新增退料任务"
}
},
},
{
"path": "pages/back/backCodeAdd",
"style": {
@ -182,7 +182,8 @@
"style": {
"navigationBarTitleText": "退料编码"
}
},{
},
{
"path": "pages/back/backCodeScan",
"style": {
"navigationBarTitleText": "退料编码"
@ -207,25 +208,24 @@
}
},
//-------------------------------
{
"path": "pages/part/part-lease/index",
"style": {
"navigationBarTitleText": "配件领用"
}
},
{
"path": "pages/part/part-lease/applyList",
"style": {
"navigationBarTitleText": "配件领用记录"
}
},
"path": "pages/part/part-lease/index",
"style": {
"navigationBarTitleText": "配件领用"
}
},
{
"path": "pages/part/part-lease/applyDetail",
"style": {
"navigationBarTitleText": "配件领用详情"
}
},
"path": "pages/part/part-lease/applyList",
"style": {
"navigationBarTitleText": "配件领用记录"
}
},
{
"path": "pages/part/part-lease/applyDetail",
"style": {
"navigationBarTitleText": "配件领用详情"
}
},
/* */
// 1.
@ -281,7 +281,7 @@
{
"path": "pages/repair/testedInBound/index",
"style": {
"navigationStyle": "custom"
"navigationStyle": "custom"
// "navigationBarTitleText": "修试入库"
}
},
@ -307,12 +307,12 @@
}
},
{
"path": "pages/standardBox/index",
"path": "pages/standardBox/index",
"style": {
"navigationStyle": "custom"
// "navigationBarTitleText": "标准箱管理"
}
},
},
{
"path": "pages/standardBox/addBox",
"style": {
@ -324,7 +324,8 @@
"style": {
"navigationBarTitleText": "标准箱录入"
}
},{
},
{
"path": "pages/standardBox/codeView",
"style": {
"navigationBarTitleText": "标准箱查看"
@ -341,9 +342,13 @@
"style": {
"navigationBarTitleText": "标准箱接收"
}
},
{
"path": "pages/my/signature",
"style": {
"navigationBarTitleText": "电子签名"
}
}
],
"tabBar": {
"color": "#2c2c2c",

View File

@ -3,6 +3,7 @@
<view class="user-info"> 用户 {{ userInfo.nickName }} </view>
<view class="user-info"> 用户 {{ userInfo.userName }} </view>
<view class="user-info"> 手机号{{ userInfo.phonenumber || '13655555' }} </view>
<view class="user-info exit-btn" @click="onSignature"> 电子签名 </view>
<view class="user-info exit-btn" @tap="onExit"> 退出登录 </view>
</view>
</template>
@ -20,6 +21,11 @@ const onExit = () => {
memberStore.clearToken()
uni.navigateTo({ url: '/pages/login/index' })
}
//
const onSignature = () => {
uni.navigateTo({ url: '/pages/my/signature' })
}
</script>
<style scoped>

185
src/pages/my/signature.vue Normal file
View File

@ -0,0 +1,185 @@
<template>
<view>
<div class="img" :class="{ rotate: isRotate }" v-if="imgPath">
<image :src="imgPath" @click="preview"></image>
</div>
<div class="img" v-else>暂无数据</div>
<div class="tips">
个人电子签名维护,支持手机拍照上传图片以及手写签名推荐使用手写签名
</div>
<div class="btns">
<button class="btn" type="primary" @click="handlePhoto">拍照/上传</button>
<button class="btn" type="primary" @click="open">手写签名</button>
</div>
<miliu-autograph v-model="isCanvas" @complete="complete"></miliu-autograph>
</view>
</template>
<script setup>
import { pathToBase64, base64ToPath } from 'image-tools'
import { ref, reactive, onMounted } from 'vue'
import { getSign, updateSign } from '@/services/signature.js'
import { baseURL } from '@/utils/http'
let isCanvas = ref(false)
let imgPath = ref('')
let isRotate = ref(false)
let actionUrl = `${baseURL}/file/upload`
let signType = ref(0)
onMounted(() => {
getSignData()
})
//
const getSignData = () => {
getSign()
.then((res) => {
imgPath.value = res.data.signUrl
signType.value = res.data.signType
if (res.data.signType == 1) {
isRotate.value = false
} else {
isRotate.value = true
}
})
.catch((err) => {
console.log('🚀 ~ getSignData ~ err:', err)
})
}
//
const complete = (e) => {
console.log(e) // base
toBase64(e)
.then((res) => {
console.log('🚀 ~ .then ~ res:', res)
// imgPath.value = res
// isRotate.value = true
const params = {
signUrl: res,
signType: 0,
}
updateSign(params)
.then((res) => {
console.log('🚀 ~ .updateSign ~ res:', res)
getSignData()
})
.catch((err) => {
console.log('🚀 ~ .updateSign ~ err:', err)
})
})
.catch((err) => {
console.log(err)
})
}
// base64
const toBase64 = (filePath) => {
return new Promise((resolve, reject) => {
pathToBase64(filePath)
.then((res) => {
resolve(res)
})
.catch((err) => {
reject(err)
})
})
}
// base64
const toPath = (base64) => {
return new Promise((resolve, reject) => {
base64ToPath(base64)
.then((res) => {
resolve(res)
})
.catch((err) => {
reject(err)
})
})
}
//
const open = () => {
console.log('打开签名')
isCanvas.value = true
}
//
const preview = () => {
console.log('预览')
uni.previewImage({
urls: [imgPath.value],
})
}
// /
const handlePhoto = () => {
console.log('拍照/上传')
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
// imgPath.value = res.tempFilePaths[0]
// isRotate.value = false
uploadImg(res.tempFilePaths[0])
},
})
}
//
const uploadImg = (path) => {
uni.uploadFile({
url: actionUrl,
filePath: path,
name: 'file',
header: {
// Authorization: this.token,
},
success: (res) => {
// imgPath.value = JSON.parse(res.data).data.url
const params = {
signUrl: JSON.parse(res.data).data.url,
signType: 1,
}
updateSign(params)
.then((res) => {
console.log('🚀 ~ .then ~ res:', res)
getSignData()
})
.catch((err) => {
console.log('🚀 ~ .then ~ err:', err)
})
},
fail: (err) => {},
})
}
</script>
<style lang="scss" scoped>
.img {
height: 300px;
display: flex;
justify-content: center;
align-items: center;
}
.rotate {
transform: rotate(-90deg);
}
.tips {
padding: 15px;
font-size: 14px;
color: #e24f48;
background-color: #f5f5f5;
margin-bottom: 30px;
}
.btns {
display: flex;
justify-content: center;
.btn {
width: 30%;
&:first-child {
margin-right: 20px;
}
}
}
</style>

19
src/services/signature.js Normal file
View File

@ -0,0 +1,19 @@
import { http } from '@/utils/http'
// 获取签名
export const getSign = (data) => {
return http({
method: 'GET',
url: '/material/archives/getSign',
data,
})
}
// 更新签名
export const updateSign = (data) => {
return http({
method: 'POST',
url: '/material/archives/updateSign',
data,
})
}