pref#hmc_签字组件、福利

This commit is contained in:
Paidax
2025-12-19 14:54:26 +08:00
parent 1f5e3828c4
commit 57c469bcd6
14 changed files with 698 additions and 473 deletions
@@ -9,6 +9,7 @@ import com.alibaba.fastjson.JSONObject;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import io.v.nutz.base.enums.Env; import io.v.nutz.base.enums.Env;
import io.v.nutz.web.commons.base.Globals; import io.v.nutz.web.commons.base.Globals;
import io.v.nutz.web.commons.slog.annotation.SLog;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.nutz.integration.jedis.RedisService; import org.nutz.integration.jedis.RedisService;
import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.Inject;
@@ -60,6 +61,7 @@ public class MsgApi {
* @author zhf * @author zhf
* @description * @description
*/ */
@SLog(type = "api", tag = "消息发送", msg = "推送钉钉消息", param = true, result = true)
public void sendMsg(List<String> channels, String loginNameStr, Integer mtype, String title, String content, String imageUrl,String link) { public void sendMsg(List<String> channels, String loginNameStr, Integer mtype, String title, String content, String imageUrl,String link) {
try { try {
if (!Globals.MyConfig.getBoolean("SendMsg")) { if (!Globals.MyConfig.getBoolean("SendMsg")) {
@@ -82,7 +84,6 @@ public class MsgApi {
if (Lang.isNotEmpty(loginNamesList)) { if (Lang.isNotEmpty(loginNamesList)) {
List<List<String>> splitList = ListUtil.split(loginNamesList, 100); List<List<String>> splitList = ListUtil.split(loginNamesList, 100);
for (List<String> sublist : splitList) { for (List<String> sublist : splitList) {
// 获取token // 获取token
String msgToken = getMsgToken(); String msgToken = getMsgToken();
@@ -118,6 +119,7 @@ public class MsgApi {
* @author zhf * @author zhf
* @description * @description
*/ */
@SLog(type = "api", tag = "消息发送", msg = "推送钉钉消息", param = true, result = true)
public void sendMsg(List<String> channels, List<String> loginNameList, Integer mtype, String title, String content, String imageUrl,String link) { public void sendMsg(List<String> channels, List<String> loginNameList, Integer mtype, String title, String content, String imageUrl,String link) {
try { try {
if (!Globals.MyConfig.getBoolean("SendMsg")) { if (!Globals.MyConfig.getBoolean("SendMsg")) {
@@ -4,6 +4,7 @@ import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import cn.wizzer.framework.base.Result; import cn.wizzer.framework.base.Result;
import io.v.nutz.sys.models.Sys_file; import io.v.nutz.sys.models.Sys_file;
import io.v.nutz.web.commons.utils.ShiroUtil;
import org.apache.shiro.authz.annotation.RequiresAuthentication; import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.nutz.boot.starter.ftp.FtpService; import org.nutz.boot.starter.ftp.FtpService;
import org.nutz.dao.Dao; import org.nutz.dao.Dao;
@@ -84,15 +85,18 @@ public class SignatureController {
/** /**
* 获取base64 * 获取base64
* *
* @param prefix 前缀 * @param user_id 前缀
* @param id 唯一id * @param id 唯一id
* @return * @return
*/ */
@At @At
@Ok("json") @Ok("json")
@RequiresAuthentication @RequiresAuthentication
public Object getBase64(String prefix, String id) { public Object getBase64(String user_id, String id) {
return redisService.get(prefix + ":" + id); if (user_id == null) {
user_id = ShiroUtil.getUserId();
}
return redisService.get(user_id + ":" + id);
} }
@@ -1,17 +1,27 @@
package io.v.nutz.zhgh.mobile.common; package io.v.nutz.zhgh.mobile.common;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import io.v.nutz.base.result.Result;
import io.v.nutz.base.service.ViService; import io.v.nutz.base.service.ViService;
import io.v.nutz.sys.models.Sys_file;
import io.v.nutz.web.commons.utils.ShiroUtil; import io.v.nutz.web.commons.utils.ShiroUtil;
import io.v.nutz.zhgh.zgfw.model.UserSign; import io.v.nutz.zhgh.zgfw.model.UserSign;
import org.nutz.boot.starter.ftp.FtpService;
import org.nutz.dao.Chain; import org.nutz.dao.Chain;
import org.nutz.dao.Cnd; import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.integration.jedis.RedisService;
import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings; import org.nutz.lang.Strings;
import org.nutz.lang.random.R;
import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Ok;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List; import java.util.List;
/** /**
@@ -24,38 +34,96 @@ import java.util.List;
@Ok("json:full") @Ok("json:full")
public class updateSign { public class updateSign {
@Inject
private Dao dao;
@Inject("UserSign") @Inject("UserSign")
private ViService<UserSign> userSignService; private ViService<UserSign> userSignService;
@Inject
private FtpService ftpService;
@Inject
private RedisService redisService;
@At @At
public void update(String sign, String user_id) { public Result update(String sign, String user_id) {
if (StrUtil.isBlank(user_id)) { if (StrUtil.isBlank(user_id)) {
user_id = (String) ShiroUtil.getPrincipalProperty("id"); user_id = ShiroUtil.getUserId();
} }
String filePath = this.uploadSignData(sign);
if (Strings.isNotBlank(getMySign(null))) { if (Strings.isNotBlank(getMySign(null))) {
userSignService.update(Chain.make("data", sign), Cnd.where("user_id", "=", user_id)); userSignService.update(Chain.make("data", filePath), Cnd.where("user_id", "=", user_id));
} else { } else {
userSignService.insert(new UserSign(user_id, sign)); userSignService.insert(new UserSign(user_id, filePath));
} }
return Result.success();
} }
@At @At
public void update(String sign, String user_id, String prefix) { public Result pcUpdate(String sign, String user_id, String id) {
if (StrUtil.isBlank(user_id)) { if (StrUtil.isBlank(user_id)) {
user_id = (String) ShiroUtil.getPrincipalProperty("id"); user_id = ShiroUtil.getUserId();
} }
if (Strings.isNotBlank(getMySign(prefix))) { String filePath = this.uploadSignData(sign);
userSignService.update(Chain.make("data", sign), Cnd.where("user_id", "=", user_id)); if (Strings.isNotBlank(getMySign(null))) {
userSignService.update(Chain.make("data", filePath), Cnd.where("user_id", "=", user_id));
} else { } else {
userSignService.insert(new UserSign(user_id, sign, prefix)); userSignService.insert(new UserSign(user_id, filePath));
} }
redisService.setex(user_id + ":" + id, 60 * 10, filePath);
return Result.success();
}
@At
public Result update(String sign, String user_id, String prefix) {
if (StrUtil.isBlank(user_id)) {
user_id = ShiroUtil.getUserId();
}
String filePath = this.uploadSignData(sign);
if (Strings.isNotBlank(getMySign(prefix))) {
userSignService.update(Chain.make("data", filePath), Cnd.where("user_id", "=", user_id));
} else {
userSignService.insert(new UserSign(user_id, filePath, prefix));
}
return Result.success();
} }
@At @At
public String getMySign(String prefix) { public String getMySign(String prefix) {
List<UserSign> userSigns = userSignService.query(Cnd.NEW().and("user_id", "=", ShiroUtil.getPrincipalProperty("id")) List<UserSign> userSigns = userSignService.query(Cnd.NEW().and("user_id", "=", ShiroUtil.getUserId())
.andEX("prefix", "=", prefix)); .andEX("prefix", "=", prefix));
return userSigns.isEmpty() ? null : userSigns.get(0).getData(); return userSigns.isEmpty() ? null : userSigns.get(0).getData();
} }
public String uploadSignData(String data){
String today = DateUtil.today();
String[] split = today.split("-");
String todayPath = "/signature/" + split[0] + "/" + split[1] + "/" + split[2];
String fileName = R.UU32() + ".png";
String fullPath = todayPath + "/" + fileName;
String replaceData = data.replaceAll("data:image/png;base64,", "");
try (InputStream is = new ByteArrayInputStream(Base64.decode(replaceData))) {
boolean uploadRes = ftpService.upload(todayPath, fileName, is);
if (uploadRes) {
//上传成功记录到文件表
Sys_file sys_file = new Sys_file();
sys_file.setFilename(fileName);
sys_file.setFilepath(fullPath);
sys_file.setSource(null);
dao.insert(sys_file);
} else {
throw new RuntimeException("上传文件失败");
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("签字数据上传失败");
}
return fullPath;
}
} }
+5 -3
View File
@@ -11,7 +11,7 @@ jetty.page.500=/error/500.html
#结合ftp使用,或用nginx代理ftp路径 #结合ftp使用,或用nginx代理ftp路径
jetty.staticPath=D://files jetty.staticPath=D://files
#开发模式静态资源 #开发模式静态资源
jetty.staticPathLocal=E:/project/company/zhgh_hmc/src/main/resources/static jetty.staticPathLocal=G:/zhgh/zhgh_hmc/src/main/resources/static
nutz.mvc.ignore=^(.+[.])(jsp|png|gif|jpg|js|css|jspx|jpeg|html|mp3|mp4|ico|svg)$ nutz.mvc.ignore=^(.+[.])(jsp|png|gif|jpg|js|css|jspx|jpeg|html|mp3|mp4|ico|svg)$
nutz.mvc.exclusions=/favicon/*,/assets/*,/druid/*,/upload/* nutz.mvc.exclusions=/favicon/*,/assets/*,/druid/*,/upload/*
ftp.enabled=true ftp.enabled=true
@@ -35,10 +35,12 @@ redis.pool.minIdle=10
redis.mode=normal redis.mode=normal
#redis.nodes=192.168.6.31:6377,192.168.6.31:6378,192.168.6.28:6377,192.168.6.28:6378,192.168.6.34:6377,192.168.6.34:6378 #redis.nodes=192.168.6.31:6377,192.168.6.31:6378,192.168.6.28:6377,192.168.6.28:6378,192.168.6.34:6377,192.168.6.34:6378
jdbc.url=jdbc:mysql://192.168.21.204:3306/zhgh_hmc?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true jdbc.url=jdbc:mysql://192.168.21.204:3306/zhgh_hmc?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
#jdbc.url=jdbc:mysql://10.112.101.15:3306/zhgh_hmc?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
#jdbc.driver=dm.jdbc.driver.DmDriver #jdbc.driver=dm.jdbc.driver.DmDriver
#jdbc.url=jdbc:dm://127.0.0.1:5236?databaseProductName=dm mysql&compatibleMode=mysql #jdbc.url=jdbc:dm://127.0.0.1:5236?databaseProductName=dm mysql&compatibleMode=mysql
jdbc.username=root jdbc.username=root
jdbc.password=123456 jdbc.password=wqnmd2331
#jdbc.password=mysql_Cb5XrP
jdbc.validationQuery=select 1 jdbc.validationQuery=select 1
jdbc.maxActive=500 jdbc.maxActive=500
jdbc.testWhileIdle=true jdbc.testWhileIdle=true
@@ -75,7 +77,7 @@ shiro.url.login=/platform
shiro.url.logout_redirect=/platform shiro.url.logout_redirect=/platform
shiro.url.unauth=/platform shiro.url.unauth=/platform
#开发时设置本地路径,便于调试 #开发时设置本地路径,便于调试
beetl.RESOURCE.rootLocal=E:/project/company/zhgh_hmc/src/main/resources/views/ beetl.RESOURCE.rootLocal=G:/zhgh/zhgh_hmc/src/main/resources/views/
beetl.RESOURCE_LOADER=io.v.nutz.web.commons.ext.beetl.BeetlCustomResourceLoader beetl.RESOURCE_LOADER=io.v.nutz.web.commons.ext.beetl.BeetlCustomResourceLoader
beetl.RESOURCE.root=views/ beetl.RESOURCE.root=views/
beetl.DELIMITER_STATEMENT_START=<!--# beetl.DELIMITER_STATEMENT_START=<!--#
File diff suppressed because one or more lines are too long
@@ -0,0 +1,192 @@
<template>
<div class="signature-wrap">
<canvas id="canvas"></canvas>
<div class="action-buttons">
<button @click="clear" class="action-button-danger" type="button">清空</button>
<button @click="undo" class="action-button-warning" type="button">撤销</button>
<button @click="save" class="action-button-primary" type="button">确定</button>
</div>
</div>
</template>
<script>
// function a() {
// // 检查当前屏幕方向
// if (window.orientation === 0 || window.orientation === 180) {
// // 竖屏状态
// console.log("竖屏状态")
// alert("当前不支持竖屏,请将设备调整为横屏!")
// } else {
// // 横屏状态,执行禁止旋转的操作
// alert("当前不支持横屏,请将设备调整为竖屏!")
// // 可以在这里使内容固定在竖屏方向
// const innerWidth = window.innerWidth
// const innerHeight = window.innerHeight
// alert(innerHeight)
// alert(innerWidth)
// }
// }
//
// window.addEventListener("orientationchange", a)
// a()
let signature = null
module.exports = {
name: "sysSignature",
data() {
return {
content: null,
initialized: false
}
},
methods: {
clear() {
signature.clear()
},
undo() {
signature.undo()
},
rotate() {
signature.getRotateCanvas(90)
},
save() {
if (signature.isEmpty()) {
alert("请签字后再保存")
return
}
// const png = signature.getPNG()
//旋转一下 横过来
const rotateCanvas = signature.getRotateCanvas(-90)
this.$emit("save", rotateCanvas.toDataURL())
},
initSignature() {
if (this.initialized) return;
const canvas = document.getElementById("canvas");
if (!canvas) return;
// 检查 canvas 是否已有有效尺寸
const rect = canvas.getBoundingClientRect();
if (rect.width <= 0 || rect.height <= 0) {
// 尺寸无效,稍后重试
setTimeout(() => this.initSignature(), 50);
return;
}
const padding = 30;
const width = window.innerWidth - padding;
const height = window.innerHeight - padding;
// 设置 CSS 尺寸
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
// 高清绘制
const dpr = window.devicePixelRatio || 1;
canvas.width = width * dpr;
canvas.height = height * dpr;
// 销毁旧实例(防止重复)
if (signature) {
signature.clear();
}
signature = new SmoothSignature(canvas, {
width: width,
height: height,
scale: dpr,
minWidth: 4,
maxWidth: 10,
color: "#000000"
});
this.initialized = true;
}
},
mounted() {
// 延迟初始化,确保 DOM 渲染完成
this.$nextTick(() => {
this.initSignature();
});
},
beforeDestroy() {
signature = null;
this.initialized = false;
}
}
</script>
<style scoped>
.signature-wrap {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 15px;
background: #ffffff;
}
.signature-wrap .action {
width: 50px;
display: flex;
justify-content: center;
align-items: center;
}
.signature-wrap .action-buttons {
white-space: nowrap;
transform: rotate(90deg);
display: flex;
column-gap: 10px;
position: fixed;
bottom: 0px;
left: 60px;
transform: rotate(90deg);
display: flex;
flex-direction: column;
row-gap: 20px;
}
.action-buttons button {
color: #ffffff;
position: relative;
display: inline-block;
box-sizing: border-box;
height: auto;
padding: 3px 12px;
margin: 0;
font-size: 15px;
line-height: 1.4;
text-align: center;
border-radius: 4px;
cursor: pointer;
transition: opacity 0.15s ease;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.action-button-primary {
background-color: #1677ff;
border: 1px solid #1677ff;
}
.action-button-danger {
background-color: #ff3141;
border: 1px solid #ff3141;
}
.action-button-warning {
background-color: #ff8f1f;
border: 1px solid #ff8f1f;
}
.signature-wrap canvas {
flex: 1;
border-radius: 10px;
border: 2px dashed #ccc;
}
</style>
@@ -1,175 +1,136 @@
<template> <template>
<div style="width: 100%"> <div class="h5-signature">
<van-image :src="imageUrl" v-if="!signDrawingBoardShow" <div v-if="!showSignaturePanel" class="main-panel">
style="width: 100%;height: 150px;border: 1px dashed"></van-image> <slot v-if="$slots.default"></slot>
<div style="text-align: right"> <template v-else>
<van-button plain type="danger" hairline size="small" @click="openSignDrawingBoard" native-type="button"> <van-button @click="openSignature" class="open-button" size="middle" plain native-type="button">打开签字板</van-button>
打开签字版 <van-image :src="signatureContent">
</van-button> <template v-slot:error>请点击下方打开签字板按钮进行签字</template>
</van-image>
</template>
</div>
<van-popup v-model="showSignaturePanel" :style="{ height: '100vh', width: '100vw', display: 'flex', 'align-items': 'center' }">
<signature v-if="showSignaturePanel" @save="save"></signature>
</van-popup>
</div> </div>
<van-popup v-model="signDrawingBoardShow" position="bottom" :style="{ height: '60vh' }" get-container="body">
<div id="canvas" style="position: relative;width: 100%;height:calc(100% - 50px)"></div>
<div class="button-extra"
style="height: 50px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
column-gap:20px">
<van-button plain type="danger" block hairline @click="reset" native-type="button">清空重签</van-button>
<van-button plain type="primary" block hairline @click="confirm" native-type="button">确定</van-button>
</div>
</van-popup>
</div>
</template> </template>
<script> <script>
module.exports = { module.exports = {
props: { name: "h5",
my_sign: { components: {
type: String signature: httpVueLoader("/components/sign/index.vue?v=" + new Date().getTime())
}, },
value: { props: {
type: String value: {
}, type: String,
is_value_base64: { default: ""
type: Boolean,
default: true
},
prefix: {
type: String,
default: ''
},
},
data() {
return {
signDrawingBoardShow: false,
signData: null,
imageUrl: "",
}
},
watch: {
value: {
handler: function (val) {
if (val) {
this.getImageUrl(val)
} }
},
immediate: true
}
},
methods: {
//打开签字画板
openSignDrawingBoard() {
this.signDrawingBoardShow = true
this.$nextTick(() => {
// 删除之前的canvas
$("#canvas").empty()
$("#canvas").jSignature({
width: '100%',
height: '100%',
"decor-color": "transparent",
lineWidth: '3'
})
})
}, },
reset() { watch: {
$('#canvas').jSignature('reset') value: {
this.$emit("update:my_sign", null) handler(val) {
}, if (!val) {
this.signatureContent = val
confirm() { return
const isNull = $('#canvas').jSignature('getData', 'native').length === 0 }
if (isNull) { if (val.startsWith("/signature/getSignData?") || val.startsWith('data:image/png;')) {
this.$modal.msgError("请签字后再确定") this.signatureContent = val
return } else {
} this.signatureContent = '/signature/getSignData?path=' + val
this.signData = $('#canvas').jSignature('getData') }
this.signDrawingBoardShow = false },
this.uploadSignData() immediate: true
},
async uploadSignData() {
if (this.is_value_base64) {
this.$emit('input', this.signData)
this.$emit("update:my_sign", this.signData)
this.getImageUrl(this.signData)
} else {
const {data, code, msg} = await $.post('/signature/uploadSignData', {data: this.signData})
if (code === 0) {
console.log(data)
this.getImageUrl(data)
this.$emit('input', data)
this.$emit("update:my_sign", data)
} else {
this.$modal.msgError(msg)
} }
} },
data() {
return {
signatureContent: null,
showSignaturePanel: false
}
},
methods: {
openSignature() {
this.showSignaturePanel = true
},
save(signature) {
$.post("/mobile/common/signing/update", {sign: signature})
.then((res) => {
if (res.code === 0) {
this.$toast.success("保存成功")
this.showSignaturePanel = false
this.getMySign()
} else {
this.$toast.fail("保存失败")
}
})
},
base64ToFile(base64Data, filename) {
// 将base64的数据部分提取出来
const parts = base64Data.split(";base64,")
const contentType = parts[0].split(":")[1]
const raw = window.atob(parts[1])
const rawLength = raw.length
const uInt8Array = new Uint8Array(rawLength)
}, for (let i = 0; i < rawLength; ++i) {
getImageUrl(value) { uInt8Array[i] = raw.charCodeAt(i)
if (this.is_value_base64) { }
this.imageUrl = value
} else {
this.imageUrl = '/signature/getSignData?path=' + value
}
this.$emit('input', value)
},
checkNull() {
return $('#canvas').jSignature('getData', 'native').length === 0
},
submitSign() {
this.$emit("update:my_sign", $('#canvas').jSignature('getData'))
},
async updateSign() {
const data = await $.post("/mobile/common/signing/update", {
sign: $('#canvas').jSignature('getData'),
prefix: this.prefix
})
},
async getMySign() {
const data = await $.get("/mobile/common/signing/getMySign", {prefix: this.prefix})
if (data) {
this.getImageUrl(data)
}
}
},
created() {
this.getMySign()
},
async mounted() {
} // 使用Blob对象创建File对象
const blob = new Blob([uInt8Array], { type: contentType })
blob.lastModifiedDate = new Date()
blob.name = filename
return new File([blob], filename, { type: contentType })
},
async getMySign() {
const data = await $.get("/mobile/common/signing/getMySign")
if (data) {
if (data.startsWith("/signature/getSignData?") || data.startsWith('data:image/png;')) {
this.signatureContent = data
} else {
this.signatureContent = '/signature/getSignData?path=' + data
}
this.$emit("input", data)
}
}
},
async created() {
await this.getMySign()
},
} }
</script> </script>
<style>
<style scoped> .h5-signature {
.clearBtn { width: 100%;
position: absolute !important;
bottom: 5px;
right: 5px;
color: red;
z-index: 99;
} }
#canvas { .main-panel {
height: calc(60vh - 100px); position: relative;
width: 100vw; flex-direction: column-reverse;
display: flex;
row-gap: 10px;
} }
.button-extra { .main-panel .van-image {
height: 100px; min-height: 150px;
display: flex; display: block;
justify-content: space-between; width: 90%;
align-items: center; margin: 0 auto;
padding: 0 20px; background: white;
border-radius: 8px;
} }
.button-extra button { .main-panel .open-button {
width: 40%; margin: 0 auto;
width: 90%;
height: 36px;
border-radius: 12px;
color: white;
background: rgb(33, 109, 179);
border-color: rgb(33, 109, 179);
font-size: 14px;
} }
</style> </style>
+177 -169
View File
@@ -1,28 +1,28 @@
<template> <template>
<el-card class="box-card" shadow="never"> <el-card class="box-card" shadow="never">
<div class="signature-body"> <div class="signature-body">
<el-collapse-transition> <el-collapse-transition>
<div v-show="showQrCode" class="qr-code-div"> <div v-show="showQrCode" class="qr-code-div">
<el-link type="primary" :underline="false">{{ scan_title }}</el-link> <el-link type="primary" :underline="false">{{ scan_title }}</el-link>
<QrCode v-if="postAddress" :options="{ width: 126 }" :value="postAddress"></QrCode> <QrCode v-if="postAddress" :options="{ width: 126 }" :value="postAddress"></QrCode>
</div>
</el-collapse-transition>
<el-image fit="contain" v-show="!showQrCode" v-loading="loading"
style="height: 100px;width: 100%" :src="imageUrl">
<div slot="error" class="image-slot">
<i class="el-icon-picture-outline"></i>
</div>
</el-image>
<el-link type="danger" style="font-size: 12px;margin-top: 20px" @click="refresh();clear()"
icon="el-icon-edit"
v-show="!showQrCode">重签
</el-link>
</div> </div>
</el-collapse-transition>
<el-image fit="contain" v-show="!showQrCode" v-loading="loading"
style="height: 100px;width: 100%" :src="imageUrl">
<div slot="error" class="image-slot">
<i class="el-icon-picture-outline"></i>
</div>
</el-image>
<el-link type="danger" style="font-size: 12px;margin-top: 20px" @click="refresh();clear()"
icon="el-icon-edit"
v-show="!showQrCode">重签
</el-link>
</div>
</el-card> </el-card>
</template> </template>
<script> <script>
@@ -30,174 +30,182 @@
window.signatureInterval = null window.signatureInterval = null
module.exports = { module.exports = {
name: "Signature", name: "Signature",
props: { props: {
scan_title: { scan_title: {
type: String, type: String,
default: '请使用【手机钉钉】扫描二维码进行签字' default: '请使用【手机钉钉】扫描二维码进行签字'
},
prefix: {
type: String,
default: 'DEFAULT'
},
qz: {
type: String,
default: ''
},
value: {
type: String,
default: ''
},
is_value_base64: {
type: Boolean,
default: true
}
}, },
prefix: { model: {
type: String, prop: 'qz',
default: 'DEFAULT' event: 'change'
}, },
qz: { data() {
type: String, return {
default: '' showQrCode: false,
loading: true,
postAddress: '',
imageUrl: '',
}
}, },
value: { methods: {
type: String, refresh() {
default: '' this.qz = ''
}, },
is_value_base64: { getImageUrl(data) {
type: Boolean, if (!data) {
default: true return
}
},
model: {
prop: 'qz',
event: 'change'
},
data() {
return {
showQrCode: false,
loading: true,
postAddress: '',
imageUrl: '',
}
},
methods: {
refresh() {
this.qz = ''
},
getImageUrl(value) {
if (this.is_value_base64) {
this.imageUrl = value
} else {
this.imageUrl = '/signature/getSignData?path=' + value
}
},
startRequest() {
this.imageUrl = ''
clearInterval(signatureInterval)
const ts = new Date().getTime() + (Math.floor(Math.random() * (1000 - 1 + 1)) + 1).toString()
console.log(ts)
// this.postAddress = location.protocol + '//' + location.host + '/signature?prefix=' + this.prefix + '&id=' + ts + "&is_value_base64=" + this.is_value_base64
this.postAddress = 'https://zhgh.hmc.edu.cn/signature?prefix=' + this.prefix + '&id=' + ts + "&is_value_base64=" + this.is_value_base64
this.showQrCode = true
console.log(this.postAddress)
window.signatureInterval = setInterval(() => {
$.get("/signature/getBase64?prefix=" + this.prefix + "&id=" + ts).then(res => {
if (res != null) {
this.$emit("change", res)
this.$emit("update:qz", res)
this.getImageUrl(res)
this.qz = res
this.loading = false
this.save()
clearInterval(signatureInterval)
}
})
}, 1000 * 3)
},
async save() {
if (this.is_value_base64) {
localStorage.setItem("signature", this.qz)
} else {
return localStorage.setItem("signatureWelfare", this.qz)
}
},
clear() {
if (this.is_value_base64) {
localStorage.removeItem("signature")
} else {
localStorage.removeItem("signatureWelfare")
}
},
getSignature() {
if (this.is_value_base64) {
return localStorage.getItem("signature")
} else {
return localStorage.getItem("signatureWelfare")
}
},
async getMySign() {
setTimeout(() => {
if (this.qz) {
this.getImageUrl(this.qz)
} else {
$.get("/mobile/common/signing/getMySign", {prefix: this.prefix}).then(data => {
if (data) {
this.getImageUrl(data)
this.$emit("change", data)
this.$emit("update:qz", data)
this.qz = data
} }
}) if (data.startsWith("/signature/getSignData?") || data.startsWith('data:image/png;')) {
this.imageUrl = data
} else {
this.imageUrl = '/signature/getSignData?path=' + data
}
localStorage.setItem("signature", data)
localStorage.setItem("signatureWelfare", data)
clearInterval(signatureInterval)
},
startRequest() {
this.imageUrl = ''
clearInterval(signatureInterval)
const ts = new Date().getTime() + (Math.floor(Math.random() * (1000 - 1 + 1)) + 1).toString()
console.log(ts)
this.postAddress = location.protocol + '//' + location.host + '/signature?id=' + ts
// this.postAddress = 'https://zhgh.hmc.edu.cn/signature?id=' + ts
this.showQrCode = true
console.log(this.postAddress)
window.signatureInterval = setInterval(() => {
$.get("/signature/getBase64?id=" + ts).then(res => {
if (res != null) {
this.$emit("change", res)
this.$emit("update:qz", res)
console.log(res)
this.getImageUrl(res)
this.qz = res
this.loading = false
this.save()
clearInterval(signatureInterval)
}
})
}, 1000 * 3)
},
async save() {
if (this.is_value_base64) {
localStorage.setItem("signature", this.qz)
} else {
return localStorage.setItem("signatureWelfare", this.qz)
}
},
clear() {
if (this.is_value_base64) {
localStorage.removeItem("signature")
} else {
localStorage.removeItem("signatureWelfare")
}
},
getSignature() {
if (this.is_value_base64) {
return localStorage.getItem("signature")
} else {
return localStorage.getItem("signatureWelfare")
}
},
async getMySign() {
setTimeout(() => {
if (this.qz) {
this.getImageUrl(this.qz)
} else {
$.get("/mobile/common/signing/getMySign").then(data => {
if (data) {
this.getImageUrl(data)
this.$emit("change", data)
this.$emit("update:qz", data)
this.qz = data
}
})
}
}, 500)
} }
}, 500) },
created() {
this.getMySign()
} },
}, watch: {
created() { 'qz': {
this.getMySign() handler(s) {
}, this.loading = !s
watch: { if (!s) {
'qz': { const qz = this.getSignature()
handler(s) { if (!qz) {
this.loading = !s this.$emit("change", '')
if (!s) { this.$emit("update:qz", '')
const qz = this.getSignature() this.startRequest()
if (!qz) { } else {
this.$emit("change", '') this.qz = qz
this.$emit("update:qz", '') this.$emit("change", qz)
this.startRequest() this.$emit("update:qz", qz)
} else { }
this.qz = qz } else {
this.$emit("change", qz) this.qz = s
this.$emit("update:qz", qz) this.showQrCode = false
} }
} else { },
this.qz = s immediate: true
this.showQrCode = false
} }
},
immediate: true
} }
}
} }
</script> </script>
<style scoped> <style scoped>
.el-card__body { .el-card__body {
position: relative !important; position: relative !important;
} }
.signature-body { .signature-body {
width: 100%; width: 100%;
height: 100%; height: 100%;
min-height: 160px; min-height: 160px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
} }
.qr-code-div { .qr-code-div {
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%;
left: 0; left: 0;
top: 0; top: 0;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
z-index: 999; z-index: 999;
} }
</style> </style>
@@ -105,6 +105,9 @@
<!--字典混入--> <!--字典混入-->
<script src="${base!}/components/plugins/sysDict/DictData.js"></script> <script src="${base!}/components/plugins/sysDict/DictData.js"></script>
<!--签字canvas-->
<script src="${base!}/assets/platform/plugins/smooth-signature/index.umd.min.js"></script>
<script src="${base!}/assets/platform/js/chengSh.js"></script> <script src="${base!}/assets/platform/js/chengSh.js"></script>
<script src="${base!}/assets/platform/js/util/commonUtil.js"></script> <script src="${base!}/assets/platform/js/util/commonUtil.js"></script>
<script src="${base!}/assets/platform/plugins/xlsx/xlsx.full.min.js"></script> <script src="${base!}/assets/platform/plugins/xlsx/xlsx.full.min.js"></script>
@@ -68,6 +68,8 @@
<!--字典混入--> <!--字典混入-->
<script src="${base!}/components/plugins/sysDict/DictData.js"></script> <script src="${base!}/components/plugins/sysDict/DictData.js"></script>
<!--签字canvas-->
<script src="${base!}/assets/platform/plugins/smooth-signature/index.umd.min.js"></script>
<!--富文本编辑器--> <!--富文本编辑器-->
<link rel="stylesheet" href="${base!}/assets/platform/plugins/wangeditor/wangEditor.css"> <link rel="stylesheet" href="${base!}/assets/platform/plugins/wangeditor/wangEditor.css">
@@ -1,55 +1,22 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<!-- 在 head 标签中添加 meta 标签,并设置 viewport-fit=cover 值 -->
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover"
name="viewport">
<title>签字</title> <title>签字</title>
<!-- 引入样式文件 --> <meta charset="UTF-8" />
<link rel="stylesheet" href="${base!}/assets/platform/plugins/vant/vant.css?v=1.0.1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<link href="https://res.wx.qq.com/open/libs/weui/2.2.0/weui.min.css" rel="stylesheet"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
<!-- 引入 Vue 和 Vant 的 JS 文件 -->
<script src="${base!}/assets/platform/plugins/vue/vue.min.js"></script> <script src="${base!}/assets/platform/plugins/vue/vue.js"></script>
<script src="${base!}/assets/platform/plugins/vant/vant.js"></script> <script src="${base!}/assets/platform/plugins/vue-loader/httpVueLoader.js"></script>
<script src="/assets/platform/plugins/jquery/jquery-1.11.1.min.js"></script> <script src="${base!}/assets/platform/plugins/smooth-signature/index.umd.min.js"></script>
<script src="/assets/platform/plugins/moment/moment.min.js"></script> <script src="${base!}/assets/platform/plugins/jquery/jquery-1.11.1.min.js"></script>
<script src="/assets/platform/js/signature.min.js"></script>
</head> </head>
<style> <body>
[v-clock] { <div id="app">
display: none <signature @save="save"></signature>
}
</style>
<body style="height: 100%;width: 100%">
<div id="app" style="height: 100%;width: 100%" v-clock>
<van-row style="margin-top: 10vh;text-align: center">
<h3 style="color: #1989fa">提示:请在下方区域签字!</h3>
</van-row>
<van-row style="margin-top: 20px">
<canvas id="canvas" style="border: rgb(220,220,220) 1px solid"></canvas>
</van-row>
<van-row style="margin-top: 20px">
<van-col span="24" style="text-align: center">
<van-row justify="space-around" type="flex">
<van-col span="11">
<van-button @click="reset" size="large" type="default">清 空</van-button>
</van-col>
<van-col span="11">
<van-button :disabled="subDis" @click="doSub" size="large" type="info">提 交
</van-button>
</van-col>
</van-row>
</van-col>
</van-row>
</div> </div>
</body>
<script> <script>
document.body.addEventListener('touchmove', function (e) {
e.preventDefault(); //阻止默认的处理方式(阻止下拉滑动的效果)
}, {passive: false}); //passive 参数不能省略,用来兼容ios和android
function getQueryVariable(variable) { function getQueryVariable(variable) {
var query = window.location.search.substring(1); var query = window.location.search.substring(1);
var vars = query.split("&"); var vars = query.split("&");
@@ -62,98 +29,82 @@
return ''; return '';
} }
var qmoption = { new Vue({
el: '#canvas', el: "#app",
lineWidth: 4, components: {
lineColor: '#000', signature: httpVueLoader("/components/sign/index.vue?v=" + new Date().getTime())
overflow: false, },
background: '#fff' data() {
} return {
var qm prefix: '',
id: '',
var isNull: function (val) {
vue = new Vue({ if (val === null || val === undefined || val === '') {
el: "#app", return true
data: function () {
return {
is_value_base64: true,
prefix: '',
id: '',
base64: '',
subDis: false,
isNull: function (val) {
if (val === null || val === undefined || val === '') {
return true
}
return false
},
}
},
filters: {
none2str: function (value) {
if (vue.isNull(value)) {
return '暂无'
} }
return value return false
}
},
methods: {
reset() {
qm.reset()
}, },
}
doSub() { },
qm.getContentBase64(false, (val) => { methods: {
this.base64 = val save(signature) {
$.post("/mobile/common/signing/pcUpdate", {
sign: signature,
id: this.id
})
.then((res) => {
if (res.code === 0) {
alert("保存成功")
} else {
alert("保存失败")
}
}) })
$.post("/signature/doSub", { // $.post("/signature/doSub", {
prefix: this.prefix, // prefix: this.prefix,
id: this.id, // id: this.id,
base64: this.base64, // base64: signature,
is_value_base64: this.is_value_base64 // is_value_base64: false
}, (data) => { // })
if (data.code == 0) { // .then((res) => {
this.$notify({ // if (res.code === 0) {
message: data.msg, // alert("保存成功")
type: 'success' // this.showSignaturePanel = false
}); // } else {
this.subDis = true // alert("保存失败")
} else { // }
this.$notify({ // })
message: data.msg,
type: 'danger'
});
}
}, "json");
}
}, },
mounted: () => { base64ToFile(base64Data, filename) {
canvas = document.getElementById("canvas"); // 将base64的数据部分提取出来
if (canvas.width < window.innerWidth) { const parts = base64Data.split(";base64,")
canvas.width = window.innerWidth; const contentType = parts[0].split(":")[1]
const raw = window.atob(parts[1])
const rawLength = raw.length
const uInt8Array = new Uint8Array(rawLength)
for (let i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i)
} }
if (canvas.height < window.innerHeight) { // 使用Blob对象创建File对象
canvas.height = window.innerHeight * 0.5; const blob = new Blob([uInt8Array], { type: contentType })
} blob.lastModifiedDate = new Date()
blob.name = filename
qm = new Signature(qmoption); return new File([blob], filename, { type: contentType })
qm.create();
},
created: function () {
if (this.isNull(getQueryVariable("id")) || this.isNull(getQueryVariable("prefix"))) {
this.$notify({
message: '缺少参数!',
type: 'danger'
});
this.subDis = true
}
this.id = getQueryVariable("id")
this.prefix = getQueryVariable("prefix")
this.is_value_base64 = getQueryVariable("is_value_base64")
} }
}) },
created: function () {
if (this.isNull(getQueryVariable("id"))) {
this.$notify({
message: '缺少参数!',
type: 'danger'
});
this.subDis = true
}
this.id = getQueryVariable("id")
}
})
</script> </script>
</body>
</html> </html>
@@ -88,11 +88,18 @@ layout("/layouts/platform.html"){
prop="tag" width="120"> prop="tag" width="120">
</el-table-column> </el-table-column>
<el-table-column <el-table-column
header-align="left" header-align="center"
label="日志内容" label="日志内容"
prop="msg" prop="msg"
> >
</el-table-column> </el-table-column>
<el-table-column
:show-overflow-tooltip="true"
header-align="center"
label="参数"
prop="param"
>
</el-table-column>
<el-table-column <el-table-column
:show-overflow-tooltip="true" :show-overflow-tooltip="true"
@@ -1043,6 +1043,12 @@ layout("/layouts/platform.html"){
r.name = "收货地址:" + r.province + r.city + r.county + r.addressDetail + ",收件人:" + r.userName + ",联系方式:" + r.tel r.name = "收货地址:" + r.province + r.city + r.county + r.addressDetail + ",收件人:" + r.userName + ",联系方式:" + r.tel
}) })
this.addressOptions = resp.data this.addressOptions = resp.data
// 回显默认地址
if (this.addressOptions) {
const address = this.addressOptions.find(a => a.isDefault)
this.$set(this.formData, "addressId", address.id)
this.$set(this.editWelfareData, "receiveAddress", address.name)
}
}, },
addressChange(val) { addressChange(val) {
const address = this.addressOptions.find(a => a.id === val) const address = this.addressOptions.find(a => a.id === val)
@@ -1123,6 +1129,8 @@ layout("/layouts/platform.html"){
} }
}) })
// 把地址给置空然后重新复制
this.formData.addressId = null
this.exportWelfareListDialog = true this.exportWelfareListDialog = true
const data = await searchUser(row.loginname, null, null, null, null) const data = await searchUser(row.loginname, null, null, null, null)
@@ -1165,7 +1173,7 @@ layout("/layouts/platform.html"){
for (const option of options) { for (const option of options) {
// 检查是否有 specs // 检查是否有 specs
if (option.specs) { if (option.specs && option.specs.length > 0) {
// 检查 selectSpecs 是否为空或未定义 // 检查 selectSpecs 是否为空或未定义
if (!option.selectSpecs) { if (!option.selectSpecs) {
this.$message.warning('请选择' + option.optionName + '的规格') this.$message.warning('请选择' + option.optionName + '的规格')
@@ -763,10 +763,26 @@ layout("/layouts/platform.html"){
//如果已选择 //如果已选择
if (row.isChoose > 0) { if (row.isChoose > 0) {
const data = this.addressOptions.map(r => {
const data = r.name = "收货地址:" + r.province + r.city + r.county + r.addressDetail + ",收件人:" + r.userName + ",联系方式:" + r.tel
const resultData = data.replace(/[,:]/g, '')
return {id: r.id, name: resultData}
})
const replace = this.userSelection[0].receiveAddress.replace(/[,:]/g, '')
let findData = data.find(v => v.name === replace)
if (findData && Array.isArray(findData)) {
findData = findData[0]
}
this.$set(this.formData, "receiveAddress", this.userSelection[0].receiveAddress) this.$set(this.formData, "receiveAddress", this.userSelection[0].receiveAddress)
this.$set(this.formData, "addressId", null) this.$set(this.formData, "addressId", findData.id)
this.$set(this.formData, "sign", this.userSelection[0].userSign) this.$set(this.formData, "sign", this.userSelection[0].userSign)
} else { } else {
debugger
const address = this.addressOptions.find(a => a.isDefault) const address = this.addressOptions.find(a => a.isDefault)
if (address) { if (address) {
this.$set(this.formData, "receiveAddress", address.name) this.$set(this.formData, "receiveAddress", address.name)