123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781 |
- <template>
- <view class="sign">
- <view class="imgBox">
- <view class="nom_img" v-if="!showImg" @click="signModShow=true">
- <image v-if="!showImg" src="/static/other/signs.png" style="width: 34px;height: 34px;">
- </image>
- </view>
- <view class="across_img" v-if="showImg">
- <view v-if="showImg" class="delete_icon" @click.stop="deleteImg">
- x
- </view>
- <image v-if="showImg" :src="showImg" style="width: 140px;height: 80px;" @click="previewImg(showImg)">
- </image>
- </view>
- </view>
- <umask :show="signModShow" @click="signModShow=false" :duration="0">
- <view class="warp">
- <view class="signBox" @tap.stop>
- <view class="wrapper">
- <view class="handBtn">
- <!-- #ifdef MP-WEIXIN -->
- <image @click="selectColorEvent('black','#1A1A1A')"
- :src="selectColor === 'black' ? '/static/other/color_black_selected.png' : '/static/other/color_black.png'"
- class="black-select"></image>
- <image @click="selectColorEvent('red','#ca262a')"
- :src="selectColor === 'red' ? '/static/other/color_red_selected.png' : '/static/other/color_red.png'"
- class="red-select"></image>
- <!-- #endif -->
- <!-- #ifndef MP-WEIXIN -->
- <view class="color_pic" :style="{background:lineColor}" @click="showPickerColor=true">
- </view>
- <!-- #endif -->
- <button @click="clear" class="delBtn">清空</button>
- <button @click="saveCanvasAsImg" class="saveBtn">保存</button>
- <button @click="previewCanvasImg" class="previewBtn">预览</button>
- <button @click="subCanvas" class="subBtn">完成</button>
- <button @click="undo" class="undoBtn">撤销</button>
- <span class="emptyInfo" style="color: red;" v-if="emptyShow">你还没有绘制任何东西哦</span>
- </view>
- <view class="handCenter" :style="{left:canvasLeft+'px'}">
- <canvas :disable-scroll="true" @touchstart="uploadScaleStart" @touchmove="uploadScaleMove"
- @touchend="uploadScaleEnd" :style="{width:'100%',height:'calc(85vh - 8rpx)'}"
- :canvas-id="canvasId"></canvas>
- </view>
- <view class="handCenters">
- <canvas :canvas-id="canvasIds"
- :style="{width:outSignWidth+'px',height:outSignHeight+'px'}"></canvas>
- </view>
- <view class="handRight">
- <view class="handTitle">请签名
- </view>
- </view>
- </view>
- </view>
- </view>
- </umask>
- <pickerColor :isShow="showPickerColor" :bottom="0" @callback='getPickerColor' />
- </view>
- </template>
- <script>
- import umask from "./u-mask/u-mask.vue"
- import pickerColor from "./pickerColor.vue"
- export default {
- components: {
- umask,
- pickerColor
- },
- data() {
- return {
- canvasLeft: 10000,
- emptyShow: false,
- signModShow: false,
- showImg: "",
- showPickerColor: false,
- ctx: '',
- ctxs: '',
- canvasWidth: 0,
- canvasHeight: 0,
- selectColor: 'black',
- lineColor: '#1A1A1A',
- points: [],
- historyList: [],
- canAddHistory: true,
- getImagePath: () => {
- let that = this
- return new Promise((resolve) => {
- uni.canvasToTempFilePath({
- canvasId: that.canvasId,
- fileType: 'png',
- quality: 1, //图片质量
- success: res => resolve(res.tempFilePath)
- }, this)
- })
- },
- requestAnimationFrame: void 0,
- };
- },
- watch: {
- signModShow(newValue, oldValue) {
- newValue ? this.canvasLeft = 74 : this.canvasLeft = 10000
- }
- },
- props: { //可用于修改的参数放在props里 也可单独放在外面做成组件调用 传值
- action: {
- type: String,
- default: ''
- },
- canvasId: {
- type: String,
- default: 'canvasDr'
- },
- canvasIds: {
- type: String,
- default: 'canvasRo'
- },
- header: {
- type: Object,
- default: {}
- },
- outSignWidth: {
- type: Number,
- default: 54
- },
- outSignHeight: {
- type: Number,
- default: 24
- },
- minSpeed: { //画笔最小速度
- type: Number,
- default: 1.5
- },
- minWidth: { //线条最小粗度
- type: Number,
- default: 3,
- },
- maxWidth: { //线条最大粗度
- type: Number,
- default: 10
- },
- openSmooth: { //开启平滑线条(笔锋)
- type: Boolean,
- default: true
- },
- maxHistoryLength: { //历史最大长度
- type: Number,
- default: 20
- },
- maxWidthDiffRate: { //最大差异率
- type: Number,
- default: 20
- },
- undoScan: { //撤销重新渲染偏移缩放校准
- type: Number,
- default: 0.83
- },
- bgColor: { //背景色
- type: String,
- default: ''
- },
- },
- mounted() {
- if (!this.ctx) {
- this.ctx = uni.createCanvasContext(this.canvasId, this);
- }
- if (!this.ctxs) {
- this.ctxs = uni.createCanvasContext(this.canvasIds, this);
- }
- let that = this
- this.$nextTick(() => {
- uni.createSelectorQuery().in(this).select('.handCenter').boundingClientRect(rect => {
- that.canvasWidth = rect.width;
- that.canvasHeight = rect.height;
- that.drawBgColor()
- })
- .exec();
- })
- },
- methods: {
- getPickerColor(color) {
- this.showPickerColor = false;
- if (color) {
- this.lineColor = color;
- }
- },
- // 笔迹开始
- uploadScaleStart(e) {
- this.canAddHistory = true
- this.ctx.setStrokeStyle(this.lineColor)
- this.ctx.setLineCap("round") //'butt'、'round'、'square'
- },
- // 笔迹移动
- uploadScaleMove(e) {
- let temX = e.changedTouches[0].x
- let temY = e.changedTouches[0].y
- this.initPoint(temX, temY)
- this.onDraw()
- },
- /**
- * 触摸结束
- */
- uploadScaleEnd() {
- this.canAddHistory = true;
- this.points = [];
- },
- /**
- * 记录点属性
- */
- initPoint(x, y) {
- var point = {
- x: x,
- y: y,
- t: Date.now()
- };
- var prePoint = this.points.slice(-1)[0];
- if (prePoint && (prePoint.t === point.t || prePoint.x === x && prePoint.y === y)) {
- return;
- }
- if (prePoint && this.openSmooth) {
- var prePoint2 = this.points.slice(-2, -1)[0];
- point.distance = Math.sqrt(Math.pow(point.x - prePoint.x, 2) + Math.pow(point.y - prePoint.y, 2));
- point.speed = point.distance / (point.t - prePoint.t || 0.1);
- point.lineWidth = this.getLineWidth(point.speed);
- if (prePoint2 && prePoint2.lineWidth && prePoint.lineWidth) {
- var rate = (point.lineWidth - prePoint.lineWidth) / prePoint.lineWidth;
- var maxRate = this.maxWidthDiffRate / 100;
- maxRate = maxRate > 1 ? 1 : maxRate < 0.01 ? 0.01 : maxRate;
- if (Math.abs(rate) > maxRate) {
- var per = rate > 0 ? maxRate : -maxRate;
- point.lineWidth = prePoint.lineWidth * (1 + per);
- }
- }
- }
- this.points.push(point);
- this.points = this.points.slice(-3);
- },
- /**
- * @param {Object}
- * 线宽
- */
- getLineWidth(speed) {
- var minSpeed = this.minSpeed > 10 ? 10 : this.minSpeed < 1 ? 1 : this.minSpeed; //1.5
- var addWidth = (this.maxWidth - this.minWidth) * speed / minSpeed;
- var lineWidth = Math.max(this.maxWidth - addWidth, this.minWidth);
- return Math.min(lineWidth, this.maxWidth);
- },
- /**
- * 绘画逻辑
- */
- onDraw() {
- if (this.points.length < 2) return;
- this.addHistory();
- var point = this.points.slice(-1)[0];
- var prePoint = this.points.slice(-2, -1)[0];
- let that = this
- var onDraw = function onDraw() {
- if (that.openSmooth) {
- that.drawSmoothLine(prePoint, point);
- } else {
- that.drawNoSmoothLine(prePoint, point);
- }
- };
- if (typeof this.requestAnimationFrame === 'function') {
- this.requestAnimationFrame(function() {
- return onDraw();
- });
- } else {
- onDraw();
- }
- },
- //添加历史图片地址
- addHistory() {
- if (!this.maxHistoryLength || !this.canAddHistory) return;
- this.canAddHistory = false;
- if (!this.getImagePath) {
- this.historyList.length++;
- return;
- }
- //历史地址 (暂时无用)
- let that = this
- that.getImagePath().then(function(url) {
- if (url) {
- that.historyList.push(url)
- that.historyList = that.historyList.slice(-that.maxHistoryLength);
- }
- });
- },
- //画平滑线
- drawSmoothLine(prePoint, point) {
- var dis_x = point.x - prePoint.x;
- var dis_y = point.y - prePoint.y;
- if (Math.abs(dis_x) + Math.abs(dis_y) <= 2) {
- point.lastX1 = point.lastX2 = prePoint.x + dis_x * 0.5;
- point.lastY1 = point.lastY2 = prePoint.y + dis_y * 0.5;
- } else {
- point.lastX1 = prePoint.x + dis_x * 0.3;
- point.lastY1 = prePoint.y + dis_y * 0.3;
- point.lastX2 = prePoint.x + dis_x * 0.7;
- point.lastY2 = prePoint.y + dis_y * 0.7;
- }
- point.perLineWidth = (prePoint.lineWidth + point.lineWidth) / 2;
- if (typeof prePoint.lastX1 === 'number') {
- this.drawCurveLine(prePoint.lastX2, prePoint.lastY2, prePoint.x, prePoint.y, point.lastX1, point
- .lastY1, point.perLineWidth);
- if (prePoint.isFirstPoint) return;
- if (prePoint.lastX1 === prePoint.lastX2 && prePoint.lastY1 === prePoint.lastY2) return;
- var data = this.getRadianData(prePoint.lastX1, prePoint.lastY1, prePoint.lastX2, prePoint.lastY2);
- var points1 = this.getRadianPoints(data, prePoint.lastX1, prePoint.lastY1, prePoint.perLineWidth / 2);
- var points2 = this.getRadianPoints(data, prePoint.lastX2, prePoint.lastY2, point.perLineWidth / 2);
- this.drawTrapezoid(points1[0], points2[0], points2[1], points1[1]);
- } else {
- point.isFirstPoint = true;
- }
- },
- //画不平滑线
- drawNoSmoothLine(prePoint, point) {
- point.lastX = prePoint.x + (point.x - prePoint.x) * 0.5;
- point.lastY = prePoint.y + (point.y - prePoint.y) * 0.5;
- if (typeof prePoint.lastX === 'number') {
- this.drawCurveLine(prePoint.lastX, prePoint.lastY, prePoint.x, prePoint.y, point.lastX, point.lastY,
- this.maxWidth);
- }
- },
- //画线
- drawCurveLine(x1, y1, x2, y2, x3, y3, lineWidth) {
- lineWidth = Number(lineWidth.toFixed(1));
- this.ctx.setLineWidth && this.ctx.setLineWidth(lineWidth);
- this.ctx.lineWidth = lineWidth;
- this.ctx.beginPath();
- this.ctx.moveTo(Number(x1.toFixed(1)), Number(y1.toFixed(1)));
- this.ctx.quadraticCurveTo(Number(x2.toFixed(1)), Number(y2.toFixed(1)), Number(x3.toFixed(1)), Number(y3
- .toFixed(1)));
- this.ctx.stroke();
- this.ctx.draw && this.ctx.draw(true);
- },
- //画梯形
- drawTrapezoid(point1, point2, point3, point4) {
- this.ctx.beginPath();
- this.ctx.moveTo(Number(point1.x.toFixed(1)), Number(point1.y.toFixed(1)));
- this.ctx.lineTo(Number(point2.x.toFixed(1)), Number(point2.y.toFixed(1)));
- this.ctx.lineTo(Number(point3.x.toFixed(1)), Number(point3.y.toFixed(1)));
- this.ctx.lineTo(Number(point4.x.toFixed(1)), Number(point4.y.toFixed(1)));
- this.ctx.setFillStyle && this.ctx.setFillStyle(this.lineColor);
- this.ctx.fillStyle = this.lineColor;
- this.ctx.fill();
- this.ctx.draw && this.ctx.draw(true);
- },
- //获取弧度
- getRadianData(x1, y1, x2, y2) {
- var dis_x = x2 - x1;
- var dis_y = y2 - y1;
- if (dis_x === 0) {
- return {
- val: 0,
- pos: -1
- };
- }
- if (dis_y === 0) {
- return {
- val: 0,
- pos: 1
- };
- }
- var val = Math.abs(Math.atan(dis_y / dis_x));
- if (x2 > x1 && y2 < y1 || x2 < x1 && y2 > y1) {
- return {
- val: val,
- pos: 1
- };
- }
- return {
- val: val,
- pos: -1
- };
- },
- //获取弧度点
- getRadianPoints(radianData, x, y, halfLineWidth) {
- if (radianData.val === 0) {
- if (radianData.pos === 1) {
- return [{
- x: x,
- y: y + halfLineWidth
- }, {
- x: x,
- y: y - halfLineWidth
- }];
- }
- return [{
- y: y,
- x: x + halfLineWidth
- }, {
- y: y,
- x: x - halfLineWidth
- }];
- }
- var dis_x = Math.sin(radianData.val) * halfLineWidth;
- var dis_y = Math.cos(radianData.val) * halfLineWidth;
- if (radianData.pos === 1) {
- return [{
- x: x + dis_x,
- y: y + dis_y
- }, {
- x: x - dis_x,
- y: y - dis_y
- }];
- }
- return [{
- x: x + dis_x,
- y: y - dis_y
- }, {
- x: x - dis_x,
- y: y + dis_y
- }];
- },
- /**
- * 背景色
- */
- drawBgColor() {
- if (!this.bgColor) return;
- this.ctx.setFillStyle && this.ctx.setFillStyle(this.bgColor);
- this.ctx.fillStyle = this.bgColor;
- this.ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
- this.ctx.draw && this.ctx.draw(true);
- },
- //图片绘制
- drawByImage(url) {
- this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
- try {
- this.ctx.drawImage(url, 0, 0, this.canvasWidth * this.undoScan, this.canvasHeight * this.undoScan);
- this.ctx.draw && this.ctx.draw(true);
- } catch (e) {
- this.historyList.length = 0;
- }
- },
- /**
- * 清空
- */
- clear() {
- this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
- this.ctx.draw && this.ctx.draw();
- this.drawBgColor();
- this.historyList.length = 0;
- },
- //撤消
- undo() {
- if (!this.getImagePath || !this.historyList.length) return;
- var pngURL = this.historyList.splice(-1)[0];
- this.drawByImage(pngURL);
- if (this.historyList.length === 0) {
- this.clear();
- }
- },
- //是否为空
- isEmpty() {
- return this.historyList.length === 0;
- },
- /**
- * @param {Object} str
- * @param {Object} color
- * 选择颜色
- */
- selectColorEvent(str, color) {
- this.selectColor = str;
- this.lineColor = color;
- this.ctx.setStrokeStyle(this.lineColor)
- },
- //完成
- subCanvas() {
- let that = this
- if (that.isEmpty()) {
- that.emptyShow = true
- setTimeout(function() {
- that.emptyShow = false
- }, 1000)
- return
- }
- uni.canvasToTempFilePath({
- canvasId: that.canvasId,
- fileType: 'png',
- quality: 1, //图片质量
- success(res) {
- that.ctxs.translate(0, that.outSignHeight);
- that.ctxs.rotate(-90 * Math.PI / 180)
- that.ctxs.drawImage(res.tempFilePath, 0, 0, that.outSignHeight, that.outSignWidth)
- that.ctxs.draw()
- setTimeout(() => {
- uni.canvasToTempFilePath({
- canvasId: that.canvasIds,
- fileType: 'png',
- quality: 1, //图片质量
- success: function(res1) {
- if (that.action) {
- uni.showLoading()
- uni.uploadFile({
- url: that.action, //图片上传post请求的地址
- filePath: res1.tempFilePath,
- name: "file",
- header: that.header,
- success: (uploadFileRes) => {
- uni.hideLoading()
- that.showImg = res1.tempFilePath
- that.$emit('signToUrl',
- uploadFileRes)
- that.signModShow = false
- that.clear()
- },
- fail: (error) => {
- uni.hideLoading()
- }
- });
- } else {
- that.showImg = res1.tempFilePath
- that.$emit('signToUrl', {
- error_code: "201",
- msg: "请配置上传文件接口参数action"
- })
- that.signModShow = false
- that.clear()
- }
- },
- fail: (err) => {}
- }, that)
- }, 200);
- }
- }, this);
- },
- //保存到相册
- saveCanvasAsImg() {
- uni.canvasToTempFilePath({
- canvasId: this.canvasId,
- fileType: 'png',
- quality: 1, //图片质量
- success(res) {
- uni.saveImageToPhotosAlbum({
- filePath: res.tempFilePath,
- success(res) {
- uni.showToast({
- title: '已保存到相册',
- duration: 2000
- });
- }
- });
- }
- }, this);
- },
- //预览
- previewCanvasImg() {
- uni.canvasToTempFilePath({
- canvasId: this.canvasId,
- fileType: 'jpg',
- quality: 1, //图片质量
- success(res) {
- uni.previewImage({
- urls: [res.tempFilePath] //预览图片 数组
- });
- }
- }, this);
- },
- deleteImg() {
- this.showImg = ""
- },
- previewImg(img) {
- uni.previewImage({
- urls: [img] //预览图片 数组
- });
- },
- }
- };
- </script>
- <style lang="scss">
- page {
- background: #d9d9d9;
- height: auto;
- overflow: hidden;
- }
- .imgBox {
- width: 140px;
- height: 80px;
- position: relative;
- .nom_img {
- border-radius: 8px;
- border: 1px dashed;
- border-color: #a3a3a3;
- overflow: hidden;
- display: flex;
- justify-content: center;
- align-items: center;
- height: 80px;
- width: 140px;
- }
- .nom_img:hover {
- border-color: #008ef6 !important;
- }
- .across_img {
- border: 1px dashed #a3a3a3;
- border-radius: 8px;
- height: 80px;
- width: 140px;
- .delete_icon {
- position: absolute;
- top: -12px;
- right: -12px;
- width: 24px;
- height: 24px;
- overflow: hidden;
- color: #ffffff;
- font-size: 24px;
- text-align: center;
- line-height: 20px;
- background: #ff3c0c;
- border-radius: 25px;
- z-index: 1;
- }
- }
- }
- .warp {
- width: 100%;
- height: 100vh;
- display: flex;
- justify-content: center;
- align-items: center;
- .signBox {
- width: 85vw;
- height: 85vh;
- background: #ffffff;
- border-radius: 8px;
- }
- }
- .wrapper {
- width: 85vw;
- height: 85vh;
- overflow: hidden;
- display: flex;
- align-content: center;
- flex-direction: row;
- justify-content: center;
- font-size: 28rpx;
- }
- .handRight {
- display: inline-flex;
- align-items: center;
- }
- .handCenter {
- position: fixed;
- border: 4rpx dashed #e9e9e9;
- flex: 5;
- margin-top: 4rpx;
- overflow: hidden;
- box-sizing: border-box;
- width: calc(100% - 84rpx - 200rpx);
- height: calc(85vh - 8rpx)
- }
- .handCenters {
- position: fixed;
- top: 0;
- left: 10000rpx;
- flex: 5;
- overflow: hidden;
- box-sizing: border-box;
- }
- .handTitle {
- transform: rotate(90deg);
- flex: 1;
- color: #666;
- }
- .handBtn button {
- font-size: 28rpx;
- }
- .handBtn {
- height: 85vh;
- display: inline-flex;
- flex-direction: column;
- justify-content: space-between;
- align-content: space-between;
- flex: 1;
- }
- .delBtn {
- position: absolute;
- top: 380rpx;
- left: 46rpx;
- transform: rotate(90deg);
- color: #666;
- }
- .subBtn {
- position: absolute;
- bottom: 158rpx;
- left: 46rpx;
- display: inline-flex;
- transform: rotate(90deg);
- background: #008ef6;
- color: #fff;
- text-align: center;
- justify-content: center;
- }
- /*Peach - 新增 - 保存*/
- .saveBtn {
- position: absolute;
- top: 650rpx;
- left: 46rpx;
- transform: rotate(90deg);
- color: #666;
- }
- .previewBtn {
- position: absolute;
- top: 516rpx;
- left: 46rpx;
- transform: rotate(90deg);
- color: #666;
- }
- .undoBtn {
- position: absolute;
- top: 780rpx;
- left: 46rpx;
- transform: rotate(90deg);
- color: #666;
- }
- .emptyInfo {
- position: absolute;
- bottom: 418rpx;
- left: -56rpx;
- transform: rotate(90deg);
- color: #666;
- }
- .color_pic {
- width: 70rpx;
- height: 70rpx;
- border-radius: 25px;
- position: absolute;
- top: 200rpx;
- left: 62rpx;
- border: 1px solid #ddd;
- }
- /*Peach - 新增 - 保存*/
- .black-select {
- width: 60rpx;
- height: 60rpx;
- position: absolute;
- top: 150rpx;
- left: 70rpx;
- }
- .red-select {
- width: 60rpx;
- height: 60rpx;
- position: absolute;
- top: 260rpx;
- left: 70rpx;
- }
- </style>
|