先说明下项目需求:
? ? ? ? 1、销货清单 需要门店老板 签名
? ? ? ? 2、签名后 将签名以及铺货清单整体转为图片上传到服务器
图片展示:
?图片说明:1:页面结构 上面为清单 下面为客户签名展示区域
? ? ? ? ? ? ? ? ? ? 2:点击图1 客户签名区域 弹出签名面板
? ? ? ? ? ? ? ? ? ? 3: 图2 签名完成后? 页面展示签名;点击保存后? 将整个页面转为图片上传到服务器?
? ? ? ? ? ? ? ? ? ? ?4: 上传到服务器后返回的图片
项目使用插件说明:
? ? ? ? 1、客户签名? 使用?handwriting??
????????????????小程序插件-手写签名 - 简书
? ? ? ? 2、页面转图片? ?使用??wxml-to-canvas
????????????????wxml-to-canvas | 微信开放文档
?代码展示:
看代码之前先熟悉下两个插件,两个插件的代码没有贴上来。
整个功能主要就是两个插件的引用。
?wxml:
<view class="page-title">
<view class="h1-view">***配送中心</view>
<view class="h3-view">{{sale.type === 10 ?'销货':'退货'}}清单</view>
</view>
<view class="shop-name-view marginB10 flex flex_LT flex_column_center">
<view>客户:{{sale.distributor_name}}</view>
<view>{{sale.created_at_text}}</view>
</view>
<view class="page-main">
<view class="table-row flex flex_LT flex_column_center">
<view class="idx-view">序号</view>
<view class="name-view">名称及规格</view>
<view class="unit-view">单位</view>
<view class="qty-view">数量</view>
<view class="price-view">单价</view>
<view class="amount-view">金额</view>
</view>
<view>
<block wx:for="{{sale.goods}}" wx:key="index">
<view class="table-row flex flex_LT flex_column_center">
<view class="idx-view">{{index+1}}</view>
<view class="name-view">{{item.goods_name}}</view>
<view class="unit-view">{{item.unit_name}}</view>
<view class="qty-view">{{item.qty}}</view>
<view class="price-view">{{item.price_text}}</view>
<view class="amount-view">{{item.amount_text}}</view>
</view>
</block>
<block wx:if="{{sale.goods.length < 6}}">
<block wx:for="{{6 -sale.goods.length}}" wx:key="index">
<view class="table-row flex flex_LT flex_column_center">
<view class="idx-view">{{6-(6-sale.goods.length)+1+index}}</view>
<view class="name-view"></view>
<view class="unit-view"></view>
<view class="qty-view"></view>
<view class="price-view"></view>
<view class="amount-view"></view>
</view>
</block>
</block>
</view>
<view class="count-view table-row flex flex_column_center">
<text class="count-label">合计:</text>
<text class="count-amount">{{sale.sum_amount_text}}</text>
<text class="count-end-label">元</text>
</view>
</view>
<view class="page-footer">
<view class="address">地址:******************</view>
<view class="created-by">制单: {{sale.created_by}}</view>
<view class="customer-name flex">
<text class="customer-label">客户签名:</text>
<view class="handwriting">
<handwriting bindOnEdit="onHnadwritingEdit" bindOnClose="onHnadwritingClose" bindOnComplete="onHnadwritingComplete" />
</view>
</view>
</view>
<view class="footer" >
<button bind:tap="onHnadwritingToggle" class="btn-radius footer-btn app-bg-color">保 存</button>
</view>
<wxml-to-canvas hidden class="widget" width="{{width}}" height="{{height}}"></wxml-to-canvas>
wxss:
page {
background: #fff;
padding: 20rpx;
}
.footer {
position: fixed;
left: 0;
bottom: 0;
width: 710rpx;
padding: 20rpx;
height: 100rpx;
background: #fff;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
box-sizing: content-box;
box-shadow: 0 0 10rpx #ddd;
}
.btn-radius-block {
width: 80%;
}
.footer-btn {
border: 0;
outline: 0;
font-size: 30rpx;
border-radius: 80rpx;
height: 80rpx;
color: #fff;
}
.page-title {
padding-bottom: 20rpx;
}
.h1-view {
font-size: 50rpx;
text-align: center;
margin-bottom: 20rpx;
}
.h3-view {
text-align: center;
margin-bottom: 20rpx;
font-size: 40rpx;
padding: 0 20rpx;
}
.page-main {
width: 710rpx;
}
.table-row {
border-bottom: 1px solid #999;
}
.table-row view {
text-align: center;
line-height: 60rpx;
height: 60rpx;
font-size:24rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-wrap: normal;
}
.idx-view {
width: 61rpx;
}
.name-view {
width: 324rpx;
}
.unit-view {
width: 61rpx;
}
.qty-view {
width: 61rpx;
}
.price-view {
width: 101rpx;
}
.amount-view {
width: 100rpx;
}
.count-view {
padding: 10rpx 0;
}
.count-label {
margin-left: 20rpx;
margin-right: 20rpx;
}
.count-amount {
width: 200rpx;
height: 50rpx;
margin-right: 20rpx;
text-align: center;
line-height: 50rpx;
color: #f00;
font-weight: bold;
}
.address {
margin-bottom: 30rpx;
margin-top: 10rpx;
}
.customer-label {
width: 150rpx;
}
.handwriting {
width: 560rpx;
}
.customer-name {
margin-top: 30rpx;
}
.page-footer {
padding-bottom: 200rpx;
}
.widget-view {
position: fixed;
left: 0;
top: 0;
width: 750rpx;
height: 100%;
background: rgba(0, 0, 0, 0.6);
}
.widget {
position: absolute;
left: 0;
top: 0;
width:0;
height:0;
z-index: -1;
opacity: 0;
}
.flex{
display: flex
}
.flex-item{
flex:1;
}
.flex_column{
flex-direction:column
}
.flex_LT{
justify-content : space-between
}
.flex_R{
justify-content : flex-end
}
.flex_PJ{
justify-content : space-around
}
.flex_center{
justify-content : center
}
.flex_column_center{
align-items : center
}
json:
{
"usingComponents": {
"handwriting": "/component/handwriting/index",
"wxml-to-canvas": "wxml-to-canvas"
},
"navigationBarTitleText": "***配送中心"
}
js:
const app = getApp();
const api = require("../../../../utils/api");
const utils = require("../../../../utils/util");
const sMD5 = require('../../../../utils/spark-md5.js')
let {
temp,
style
} = require('./wxmlAndStyle.js')
Page({
data: {
sale: {},
shopId: '',
showFooterBtn: true,
writingImge: '',
width: wx.getSystemInfoSync().windowWidth,
height: wx.getSystemInfoSync().windowHeight,
},
onLoad: function (options) {
let id = options.id;
this.data.shopId = options.shopId
this.initData(id);
},
//id为销售单的id 这里用来查询出数据 显示在页面上 可以自己定义一个json代替数据测试
initData: function (id) {
wx.promise.request({
url: api.API_ADMINS_SALES_SHOW,
data: {
id: id
},
header: app.defaultAdminHeader()
}).then(res => {
this.setData({
sale: this.formatData(res)
})
}).catch(err => {
utils.showMsg("加载销售单详情失败:" + err.errMsg)
})
},
formatData: function (data) {
data.created_at_text = utils.formatDate_3(data.created_at);
data.state_text = this.getStateText(data.state);
data.last_receipt_at_text = utils.formatDate_(data.last_receipt_at);
data.receipt_amount_text = utils.formatAmount_(data.receipt_amount, 0.01);
data.sum_amount_text = utils.formatAmount_(data.sum_amount, 0.01);
data.receipt_state_text = data.receipt_state === 0 ? '未结清' : '已结清';
data.type_text = data.type === 10 ? '销售单' : '退货单';
data.goods = this.formatGoods(data.goods);
return data;
},
formatGoods: function (goods) {
goods.forEach(item => {
item.price_text = utils.formatAmount_(item.price, 0.01);
item.amount_text = utils.formatAmount_(item.amount, 0.01);
})
return goods;
},
getStateText: function (state) {
switch (state) {
case -1:
return '已删除';
case 0:
return '已开单';
case 1:
return '已确认';
case 2:
return '已出库';
case 3:
return '已退货'
default:
return '';
}
},
onBackOrder: function () {
wx.showModal({
title: '温馨提示',
content: '确定返单吗?',
showCancel: true,
confirmColor: "#84bc25",
success: (res) => {
if (res.confirm) {
this.onCancelOut()
}
}
})
},
onCancelOut: function () {
wx.promise.request({
url: api.API_SALES_CANCEL_OUT_APP,
method: 'POST',
data: {
id: this.data.sale.id
},
header: app.defaultAdminHeader()
}).then(res => {
wx.showToast({
title: '操作成功!',
icon: 'none',
duration: 2000
})
this.initData(this.data.sale.id)
}).catch(err => {
utils.showMsg("返单失败:" + err.errMsg)
})
},
onHnadwritingComplete: function (e) {
console.log(e.detail)
this.setData({
showFooterBtn: true,
})
wx.getImageInfo({
src: e.detail,
success: (e) => {
console.log(e)
this.uploadImg(e)
}
})
},
uploadImg: function (tempFile) {
wx.getFileSystemManager().readFile({
filePath: tempFile.path, //选择图片返回的相对路径
// encoding: 'binary', //编码格式
success: res => {
let spark = new sMD5.ArrayBuffer();
spark.append(res.data);
let hexHash = spark.end(false);
wx.promise.uploadFile({
url: api.API_ADMINS_UPLOAD_IMAGES_CREATE,
filePath: tempFile.path,
name: 'image',
formData: {
'image': tempFile,
'md5': hexHash
},
header: {
'content-type': 'application/x-www-form-urlencoded',
'Authorization': app.globalData.adminToken
}
}).then(res => {
console.log(res);
if (res.code === 20000 && res.data) {
this.setData({
writingImge: res.data.url
})
}
}).catch(err => {
utils.showMsg("上传图片失败:" + err.errMsg)
})
}
})
},
onHnadwritingEdit: function () {
this.setData({
showFooterBtn: false
})
},
onHnadwritingClose: function () {
this.setData({
showFooterBtn: true
})
},
onHnadwritingToggle: function () {
if (!this.data.writingImge) {
utils.showMsg("请客户签名后再提交!");
return;
}
this.widget = this.selectComponent('.widget')
if (this.data.writingImge) {
temp.setUrl(this.data.writingImge)
}
temp.init(this.data.sale.id);
wx.showLoading({
title: '正在加载...',
})
setTimeout(() => {
wx.hideLoading()
this.renderToCanvas();
}, 1000)
},
renderToCanvas() {
let wxml = temp.getWxml();
const p1 = this.widget.renderToCanvas({
wxml,
style
})
p1.then((res) => {
console.log('container', res.layoutBox)
this.container = res;
this.extraImage();
})
},
extraImage() {
const p2 = this.widget.canvasToTempFilePath()
p2.then(res => {
console.log(res);
wx.getImageInfo({
src: res.tempFilePath,
success: (e) => {
console.log(e)
this.onUploadApplyImage(e)
}
})
})
},
onUploadApplyImage: function (tempFile) {
wx.getFileSystemManager().readFile({
filePath: tempFile.path, //选择图片返回的相对路径
// encoding: 'binary', //编码格式
success: res => {
let spark = new sMD5.ArrayBuffer();
spark.append(res.data);
let hexHash = spark.end(false);
wx.showLoading({
title: '正在上传图片...',
})
wx.promise.uploadFile({
url: api.API_ADMINS_UPLOAD_IMAGES_CREATE,
filePath: tempFile.path,
name: 'image',
formData: {
'image': tempFile,
'md5': hexHash
},
header: {
'content-type': 'application/x-www-form-urlencoded',
'Authorization': app.globalData.adminToken
}
}).then(res => {
wx.hideLoading()
console.log(res);
if (res.code === 20000 && res.data) {
this.onUploadApply(res.data.id)
}
}).catch(err => {
wx.hideLoading()
utils.showMsg("上传图片失败:" + err.errMsg)
})
}
})
},
onUploadApply: function (image_id) {
wx.showLoading({
title: '正在保存图片...',
})
wx.promise.request({
url: api.API_ADMINS_SALE_APPLY_IMAGE,
method: 'POST',
data: {
id: this.data.sale.id,
image_id: image_id
},
header: app.defaultAdminHeader()
}).then(res => {
wx.hideLoading()
wx.showToast({
title: '保存图片成功!',
icon: 'none',
duration: 2000
})
wx.navigateBack()
}).catch(err => {
wx.hideLoading()
utils.showMsg("保存图片失败:" + err.errMsg)
})
}
})
wxmlAndStyle.js:
const app = getApp();
const api = require("../../../../utils/api");
const utils = require("../../../../utils/util");
let wWidth = wx.getSystemInfoSync().windowWidth;
let wHeight = wx.getSystemInfoSync().windowHeight ;
let style = {
pageview: {
backgroundColor: "#fff",
padding: 10,
paddingTop:40,
width: wWidth,
height: wHeight
},
pagetitle: {
width: wWidth,
height: 60,
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center',
},
h1view: {
width: wWidth,
height: 30,
textAlign: 'center',
},
h3view: {
width: wWidth,
height: 30,
textAlign: 'center',
},
t1: {
width: wWidth,
height: 30,
fontSize: 22,
lineHeight: 30,
fontWeight: 'bold',
color: "#000000"
},
t2: {
width: wWidth,
height: 30,
fontSize: 18,
lineHeight: 30,
fontWeight: 'bold',
color: "#000000",
borderBottom: '1px solid #000'
},
shopnameview: {
width: wWidth,
height: 14,
},
distributorName: {
width: wWidth / 2.1,
height: 14,
fontSize:12,
lineHeight: 14,
color: "#999",
},
pagemain: {
width: wWidth-20,
backgroundColor:"#999"
},
tablerow: {
width: wWidth-10,
backgroundColor:"#fff",
marginBottom:1,
},
rowitem: {
textAlign: 'center',
lineHeight: 28,
height: 28,
fontSize:12,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
wordWrap: 'normal',
},
idxview: {
width: (wWidth -20) * (1/8) //30
},
nameview: {
width: (wWidth -20) * (3/8)//165
},
unitview: {
width: (wWidth -20) * (1/8)//40
},
qtyview: {
width: (wWidth -20) * (1/8)//40
},
priceview: {
width: (wWidth -20) * (1/8)//40,
},
amountview: {
width: (wWidth -20) * (1/8)//40,
},
countlabel: {
width: 60
},
countamount: {
width: 100,
height: 25,
textAlign: "center",
lineHeight: 25,
color: "red",
fontWeight: "bold",
},
address: {
width:wWidth,
lineHeight: 28,
height: 28,
fontSize:13,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
wordWrap: 'normal',
borderWidth: 1
},
customerlabel: {
width: 75,
lineHeight: 28,
height: 28,
fontSize:13,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
wordWrap: 'normal',
borderWidth: 1
},
customername: {
width:wWidth,
},
customerImg:{
width:200,
height:100
},
pagefooter: {
width:wWidth
},
createdby:{
width:wWidth,
textAlign:'left',
lineHeight: 30,
height: 30,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
wordWrap: 'normal',
borderWidth: 1
},
flexcolumn: {
flexDirection: 'column'
},
flexlt: {
flexDirection: 'row',
justifyContent: 'space-between'
},
flexr: {
flexDirection: 'row',
justifyContent: 'flex-end'
},
flexl: {
flexDirection: 'row',
justifyContent: 'flex-start'
},
flexpj: {
flexDirection: 'row',
justifyContent: 'space-around'
},
flexcenter: {
flexDirection: 'row',
justifyContent: 'center'
},
flexcolumncenter: {
alignItems: 'center'
},
marginB10: {
marginBottom: 10
},
textR: {
textAlign: 'right'
}
}
let temp = {
url: '',
wxml: '',
sale: {},
init: function (id) {
wx.promise.request({
url: api.API_ADMINS_SALES_SHOW,
data: {
id: id
},
header: app.defaultAdminHeader()
}).then(res => {
temp.sale = temp.formatData(res)
temp.initWxml(temp.sale)
}).catch(err => {
utils.showMsg("加载销售单详情失败:" + err.errMsg)
})
},
setUrl: function (url) {
temp.url = url
},
formatData: function (data) {
data.created_at_text = utils.formatDate_3(data.created_at);
data.state_text = temp.getStateText(data.state);
data.last_receipt_at_text = utils.formatDate_(data.last_receipt_at);
data.receipt_amount_text = utils.formatAmount_(data.receipt_amount, 0.01);
data.sum_amount_text = utils.formatAmount_(data.sum_amount, 0.01);
data.receipt_state_text = data.receipt_state === 0 ? '未结清' : '已结清';
data.type_text = data.type === 10 ? '销售单' : '退货单';
data.goods = temp.formatGoods(data.goods);
return data;
},
formatGoods: function (goods) {
goods.forEach(item => {
item.price_text = utils.formatAmount_(item.price, 0.01);
item.amount_text = utils.formatAmount_(item.amount, 0.01);
})
return goods;
},
getStateText: function (state) {
switch (state) {
case -1:
return '已删除';
case 0:
return '已开单';
case 1:
return '已确认';
case 2:
return '已出库';
case 3:
return '已退货'
default:
return '';
}
},
initWxml: function (sale) {
const title = `
<view class="pagetitle flexcolumn" >
<view class="h1view flexcenter"><text class="t1">****配送中心</text></view>
<view class="h3view"><text class="t2">${sale.type === 10 ? '销售' : '退货'}清单</text></view>
</view>
`;
const shopName = `
<view class="marginB10 flexl flexcolumncenter shopnameview">
<text class="distributorName">客户:${sale.distributor_name}</text>
<text class="distributorName textR">${sale.created_at_text}</text>
</view>
`;
const tableHeader = `
<view class="tablerow flexl flexcolumncenter">
<text class="idxview rowitem">序号</text>
<text class="nameview rowitem">名称及规格</text>
<text class="unitview rowitem">单位</text>
<text class="qtyview rowitem">数量</text>
<text class="priceview rowitem">单价</text>
<text class="amountview rowitem">金额</text>
</view>
`;
let tableBody = ''
sale.goods.forEach((item, index) => {
tableBody += `
<view class="tablerow flexl flexcolumncenter">
<text class="idxview rowitem">${index+1}</text>
<text class="nameview rowitem">${item.goods_name}</text>
<text class="unitview rowitem">${item.unit_name}</text>
<text class="qtyview rowitem">${item.qty}</text>
<text class="priceview rowitem">${item.price_text}</text>
<text class="amountview rowitem">${item.amount_text}</text>
</view>
`
})
if (sale.goods.length < 6) {
let count = 6 - sale.goods.length;
for (let i = 0; i < count; i++) {
tableBody += `
<view class="tablerow flexl flexcolumncenter">
<text class="idxview rowitem">${15-(15-sale.goods.length)+1+i}</text>
<text class="nameview rowitem"></text>
<text class="unitview rowitem"></text>
<text class="qtyview rowitem"></text>
<text class="priceview rowitem"></text>
<text class="amountview rowitem"></text>
</view>
`
}
}
let tableFooter = `
<view class="tablerow flexl flexcolumncenter">
<text class="countlabel rowitem">合计:</text>
<text class="countamount rowitem">${sale.sum_amount_text}</text>
<text class="countlabel rowitem">元</text>
</view>
`;
let imgWxml = temp.url ? `<image class="customerImg" src="${temp.url}"></image>` : '';
let pageFooter = `
<view class="pagefooter flexcolumn">
<text class="address">地址:**********************</text>
<text class="createdby">制单: ${sale.created_by}</text>
<view class="customername flexl">
<text class="customerlabel">客户签名:</text>
${imgWxml}
</view>
</view>
`
let tableWxml = `<view class="pagemain">${tableHeader}${tableBody}${tableFooter}</view>`
this.wxml = `<view class="pageview">${title}${shopName}${tableWxml}${pageFooter}</view>`;
},
getWxml: function () {
return this.wxml
},
}
module.exports = {
temp,
style
}
|