You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1663 lines
45 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view>
<topnav :title='pageTitle' @tohome="tohome"></topnav>
<view class="content">
<view class="orderBox">
<orderinfo :info="order"></orderinfo>
</view>
<view class="pageTitle">
协议签名
</view>
<view class="signatureBox">
<view class="signatureItem">
<view class="signatureTitle">护工签名</view>
<view class="signatureArea">
<!-- 未签名时显示开始签名按钮弹出全屏签名 -->
<view v-if="!paramedicSignImage" class="signatureStart" @click="openSignature('paramedic')">
开始签名
</view>
<!-- 签名图片 - 已签名时显示 -->
<image
v-if="paramedicSignImage"
:src="paramedicSignImage"
class="signatureImage"
:style="getSignatureImageStyle(paramedicSignImage)"
mode="aspectFit">
</image>
</view>
<view class="signatureActions">
<text class="btnSave" @click="openSignature('paramedic')" v-if="!paramedicSignImage">开始签名</text>
<text class="btnResign" @click="resignSignature('paramedic')" v-if="paramedicSignImage">重新签名</text>
</view>
</view>
<view class="signatureItem">
<view class="signatureTitle">客户签名</view>
<view class="signatureArea">
<!-- 未签名时显示开始签名按钮(弹出全屏签名) -->
<view v-if="!customerSignImage" class="signatureStart" @click="openSignature('customer')">
开始签名
</view>
<!-- 签名图片 - 已签名时显示 -->
<image
v-if="customerSignImage"
:src="customerSignImage"
class="signatureImage"
:style="getSignatureImageStyle(customerSignImage)"
mode="aspectFit">
</image>
</view>
<view class="signatureActions">
<text class="btnSave" @click="openSignature('customer')" v-if="!customerSignImage">开始签名</text>
<text class="btnResign" @click="resignSignature('customer')" v-if="customerSignImage">重新签名</text>
</view>
</view>
<!-- <view class="signatureItem">
<view class="signatureTitle">公司签名</view>
<view class="signatureArea">
<image
:src="companySignImage"
class="signatureImage"
mode="aspectFit">
</image>
</view>
<view class="signatureActions">
<text class="btnSave" style="opacity:.6;">已使用公司签名</text>
</view>
</view> -->
</view>
<!-- 预览协议按钮 -->
<view class="previewSection">
<view class="previewBtn" @click="previewAgreement">
预览协议
</view>
</view>
<!-- 协议预览区块 -->
<view class="agreementPreviewSection" v-if="agreementImage">
<view class="sectionTitle">协议预览</view>
<view class="agreementImageContainer">
<image
:src="agreementImage"
class="agreementImage"
mode="aspectFit"
@click="viewAgreementFullscreen">
</image>
</view>
<view class="agreementActions">
<text class="btnRegenerate" @click="regenerateAgreement">重新生成协议</text>
</view>
</view>
</view>
<view class="bottom">
<view class="bottomLeft">
</view>
<view class="bottomRight">
<view class="btnCancel btn" @click="bindCancel">取消</view>
<view class="btnSubmit btn" @click="bindsubmitFun">提交</view>
</view>
</view>
<!-- 协议预览弹窗 -->
<uni-popup ref="agreementPopup" type="center" :mask-click="true" class="fullscreen-popup" :safe-area="false">
<view class="agreementPopup">
<view class="popupHeader">
<text class="popupTitle">协议预览</text>
<text class="popupClose" @click="closeAgreementPopup">×</text>
</view>
<scroll-view class="popupContent" v-if="agreementHtml" scroll-y="true" style="max-height: 100vh;">
<div class="agreementContent" v-html="agreementHtml"></div>
</scroll-view>
<view class="popupContent" v-else>
<view class="noAgreement">
<text>还未上传协议,请联系管理员补充协议</text>
</view>
</view>
<view class="popupFooter" v-if="agreementHtml">
<view class="btnGenerate"
@click="generateAgreementImage"
:class="{ disabled: isGenerating }"
:disabled="isGenerating">
{{ generateButtonText }}
</view>
</view>
</view>
</uni-popup>
<!-- 全屏签名弹窗 -->
<uni-popup ref="signaturePopup" type="center" :mask-click="false" class="fullscreen-popup" :safe-area="false">
<view class="signaturePopup">
<view class="popupHeader">
<text class="popupTitle">{{ signaturePopupTitle }}</text>
<text class="popupClose" @click="closeSignaturePopup">×</text>
</view>
<view class="popupContent">
<view class="signatureWorkspace">
<view class="signatureInlineToast" v-if="signatureToastVisible">{{ signatureToastText }}</view>
<view class="signatureCanvasWrap">
<l-signature
ref="popupSignatureRef"
:penColor="penColor"
:penSize="penSize"
:openSmooth="openSmooth"
:boundingBox="boundingBox"
disableScroll
class="signatureCanvas">
</l-signature>
</view>
<view class="signatureControlsRow">
<view class="btnRow btnRowClear" @click="clearPopupSignature"></view>
<view class="btnRow btnRowReset" @click="resetPopupSignature"></view>
<view class="btnRow btnRowSave" @click="savePopupSignature"></view>
</view>
</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import html2canvas from 'html2canvas';
var util = require("../../../../utils/util.js");
export default {
data() {
return {
pageTitle: "签订协议",
order: {},
id: "",
// 签名相关
paramedicSignPath: "",
customerSignPath: "",
companySignPath: "",
paramedicSignId: "",
customerSignId: "",
companySignId: "",
paramedicSignImage: "", // 护工签名图片
customerSignImage: "", // 客户签名图片
companySignImage: '', // 公司签名图片(固定)
// 协议预览相关
agreementImage: "", // 协议图片路径
agreementId: "", // 协议ID
canPreview: false, // 是否可以预览
agreementHtml: "", // 协议HTML内容
file_id: "", // 协议文件ID
// 签名组件配置
penColor: '#000',
penSize: 3,
openSmooth: true,
boundingBox: true,
// 生成协议按钮状态
isGenerating: false, // 是否正在生成协议
generateButtonText: '生成协议', // 按钮文本
// 全屏签名弹窗
currentSignatureType: '',
signaturePopupTitle: '',
signatureToastVisible: false,
signatureToastText: ''
}
},
onLoad: function(option) {
var that = this;
that.id = option.id;
util.getOrderInfo(option.id, function(r) {
that.order = r.data;
// 初始化公司签名(固定图片)
that.initCompanySign();
// 检查是否可以预览协议
that.checkCanPreview();
// 检查协议状态一致性
that.checkAgreementConsistency();
}, function(r) {
});
},
methods: {
// 规范化URL去除域名后的多余斜杠以及 //storage -> /storage
normalizeUrl: function(url){
if(!url){ return url; }
let u = String(url);
// 把 :// 后面可能出现的多个 / 压缩成一个 /
u = u.replace(/(:\/\/[^/]*?)\/+/, '$1/');
// 把 //storage 归一为 /storage
u = u.replace(/\/+storage\//, '/storage/');
return u;
},
// 初始化公司签名(不需要手写)
initCompanySign: function(){
// 使用指定的公司签名图片地址
this.companySignImage = 'https://tiantianxinye.365care.langye.net/h5/static/companySign.png';
this.companySignId = 'STATIC_COMPANY_SIGN';
},
tohome:function(){
uni.navigateTo({
url:"../../../../pages/index/index"
})
},
// 显示签名弹窗内的提示
showSignatureToast: function(text){
this.signatureToastText = text || '';
this.signatureToastVisible = true;
clearTimeout(this._signatureToastTimer);
this._signatureToastTimer = setTimeout(()=>{
this.signatureToastVisible = false;
}, 1600);
},
bindCancel: function(e) {
uni.navigateBack({
})
},
bindsubmitFun: function(e) {
var that = this;
// 检查是否所有签名都已完成(公司签名固定,不再校验 companySignId
if (!that.paramedicSignId || !that.customerSignId) {
util.alert("请完成所有签名");
return false;
}
// 检查是否已生成协议文件
if (!that.file_id) {
util.alert("请先生成协议文件");
return false;
}
uni.showModal({
title: '提示',
content: "确认签订协议?",
confirmText: "确认",
confirmColor: "#000",
cancelColor: "#eee",
success(res) {
if (res.confirm) {
that.submitAgreement();
}
}
});
},
// 打开全屏签名
openSignature: function(type){
this.currentSignatureType = type;
const typeNames = {
'paramedic': '护工签名',
'customer': '客户签名',
'company': '公司签名'
};
this.signaturePopupTitle = typeNames[type] || '签名';
this.$nextTick(()=>{
this.$refs.signaturePopup.open();
// 清空一次画布
setTimeout(()=>{
if(this.$refs.popupSignatureRef){
this.$refs.popupSignatureRef.clear();
}
}, 50);
});
},
// 关闭全屏签名
closeSignaturePopup: function(){
if(this.$refs.signaturePopup){
this.$refs.signaturePopup.close();
}
},
// 清除弹窗中的签名
clearPopupSignature: function(){
if(this.$refs.popupSignatureRef){
this.$refs.popupSignatureRef.clear();
}
},
// 重置清空画布并清除当前签名人的已保存签名ID与图片
resetPopupSignature: function(){
this.clearPopupSignature();
const type = this.currentSignatureType;
switch(type){
case 'paramedic':
this.paramedicSignId = '';
this.paramedicSignImage = '';
break;
case 'customer':
this.customerSignId = '';
this.customerSignImage = '';
break;
case 'company':
this.companySignId = '';
this.companySignImage = '';
break;
}
// 签名被重置后,若已有协议,需清除以避免不一致
this.autoClearAgreement();
},
// 保存弹窗中的签名
savePopupSignature: function(){
const type = this.currentSignatureType;
if(!type){
return;
}
if(this.$refs.popupSignatureRef){
// 若未签字,禁止保存
try{
if (typeof this.$refs.popupSignatureRef.isEmpty === 'function' && this.$refs.popupSignatureRef.isEmpty()){
this.showSignatureToast('请先签名后再保存');
return;
}
}catch(e){
// 兼容无isEmpty方法的实现继续后续流程
}
this.$refs.popupSignatureRef.canvasToTempFilePath({
quality: 0.9,
success: (res)=>{
// 直接使用原图上传,不进行旋转
this.rotateImageToLandscape(res.tempFilePath, (rotatedDataUrl)=>{
const uploadPath = rotatedDataUrl || res.tempFilePath;
this.uploadSignature(uploadPath, type, ()=>{
this.closeSignaturePopup();
// 清理已生成协议
this.checkAndClearAgreement(type);
});
});
},
fail: ()=>{
util.alert('签名保存失败');
}
});
}
},
// 不进行图片旋转,直接使用原图
rotateImageToLandscape: function(filePath, callback){
console.log('使用原图,不进行旋转:', filePath);
// 直接返回 null表示使用原图
callback(null);
},
// 清除签名
clearSignature: function(type) {
const refName = type + 'SignatureRef';
if (this.$refs[refName]) {
this.$refs[refName].clear();
this.setSignPath(type, "");
}
},
// 获取签名图片样式(正向显示)
getSignatureImageStyle: function(imagePath) {
// 统一使用正向显示,不进行旋转
return {
width: '120px',
height: '60px'
};
},
// 保存签名
saveSignature: function(type) {
const refName = type + 'SignatureRef';
if (this.$refs[refName]) {
console.log('开始保存签名:', type);
this.$refs[refName].canvasToTempFilePath({
quality: 0.8,
success: (res) => {
console.log('签名保存成功:', type, res);
this.setSignPath(type, res.tempFilePath);
// 保存成功后立即上传到接口
console.log('开始上传签名到接口:', type, res.tempFilePath);
this.uploadSignature(res.tempFilePath, type, (signId) => {
console.log('签名上传完成:', type, signId);
// 上传成功后清除签名路径,图片会自动显示
this.setSignPath(type, "");
// 将英文type转换为中文显示
const typeNames = {
'paramedic': '护工',
'customer': '客户',
'company': '公司'
};
const typeName = typeNames[type] || type;
util.alert(typeName + '签名上传成功');
// 检查是否需要清除已生成的协议
this.checkAndClearAgreement(type);
});
},
fail: (err) => {
console.log('签名保存失败:', type, err);
util.alert(type + '签名保存失败');
}
});
} else {
console.log('签名组件引用不存在:', refName);
}
},
// 重新签名
resignSignature: function(type) {
console.log('重新签名:', type);
// 清除签名ID和图片
switch(type) {
case 'paramedic':
this.paramedicSignId = "";
this.paramedicSignImage = "";
break;
case 'customer':
this.customerSignId = "";
this.customerSignImage = "";
break;
case 'company':
this.companySignId = "";
this.companySignImage = "";
break;
}
// 清除签名路径
this.setSignPath(type, "");
// 将英文type转换为中文显示
const typeNames = {
'paramedic': '护工',
'customer': '客户',
'company': '公司'
};
const typeName = typeNames[type] || type;
// 自动清除已生成的协议
this.autoClearAgreement();
// 立即弹出对应的签名区域
this.openSignature(type);
},
// 签名保存事件处理
onSignatureSave: function(e) {
const type = e.currentTarget.dataset.type;
console.log('签名保存事件:', type);
},
// 设置签名路径
setSignPath: function(type, path) {
switch(type) {
case 'paramedic':
this.paramedicSignPath = path;
break;
case 'customer':
this.customerSignPath = path;
break;
case 'company':
this.companySignPath = path;
break;
}
},
// 上传单个签名
uploadSignature: function(filePath, type, callback) {
var that = this;
var user = uni.getStorageSync('userInfo');
if (!user || !user.access_token) {
util.alert('用户未登录或token无效');
return;
}
console.log('开始上传签名:', type, filePath);
// 支持 dataURL 和本地临时路径
const doUpload = (pathToUpload) => {
uni.uploadFile({
url: util.HOST + 'manager/upload-image',
filePath: pathToUpload,
name: 'file',
formData: {
'token': user.access_token,
'folder': 'public'
},
success(res) {
console.log('签名上传响应:', res);
console.log('响应状态码:', res.statusCode);
console.log('响应数据类型:', typeof res.data);
console.log('响应数据内容:', res.data);
if (res.statusCode == 200 || res.statusCode == "200") {
try {
// 尝试解析响应数据
let mod;
if (typeof res.data === 'string') {
mod = JSON.parse(res.data);
} else if (typeof res.data === 'object') {
mod = res.data;
} else {
throw new Error('响应数据格式不支持');
}
console.log('解析后的数据:', mod);
// 检查必要字段
if (!mod.id || !mod.public_path) {
console.log('响应数据缺少必要字段:', mod);
util.alert('签名上传失败:响应数据格式不完整');
return;
}
// 直接设置签名ID和图片路径
switch(type) {
case 'paramedic':
that.paramedicSignId = mod.id;
that.paramedicSignImage = util.HOST + mod.public_path;
break;
case 'customer':
that.customerSignId = mod.id;
that.customerSignImage = util.HOST + mod.public_path;
break;
case 'company':
that.companySignId = mod.id;
that.companySignImage = util.HOST + mod.public_path;
break;
}
callback(mod.id);
} catch (error) {
console.log('解析上传响应失败:', error);
console.log('原始响应数据:', res.data);
// util.alert('签名上传失败:响应格式错误 - ' + error.message);
}
} else {
console.log('签名上传失败,状态码:', res.statusCode);
util.alert('签名上传失败:' + res.statusCode);
}
},
fail(res) {
console.log('签名上传失败:', res);
util.alert('签名上传失败:网络错误');
}
});
};
if (typeof filePath === 'string' && filePath.startsWith('data:')) {
// 直接上传 dataURL
doUpload(filePath);
} else {
// 本地路径按原逻辑上传
doUpload(filePath);
}
},
// 提交协议
submitAgreement: function() {
var that = this;
// 检查必要参数
if (!that.paramedicSignId || !that.customerSignId || !that.companySignId || !that.file_id) {
util.alert("请完成所有签名并生成协议文件");
return;
}
console.log(that.paramedicSignId,that.customerSignId,that.companySignId,that.file_id)
return
util.request({
api: 'manager/create-order-agreements',
method: 'POST',
data: {
order_id: that.id,
paramedic_id: that.order.paramedic_id,
paramedic_sign_id: that.paramedicSignId,
customer_id: that.order.customer_id,
customer_sign_id: that.customerSignId,
// 后端若需要可传固定占位值
company_sign_id: that.companySignId || 'STATIC_COMPANY_SIGN',
file_id: that.file_id
},
utilSuccess: function(r) {
util.alert("协议签订成功");
uni.navigateTo({
url: "/pages/order/order"
});
},
utilFail: function(r) {
util.alert(r);
}
});
},
// 预览协议
previewAgreement: function() {
var that = this;
// 检查三个签名是否都已完成(公司签名固定,不再校验 companySignId
if (!that.paramedicSignId || !that.customerSignId) {
util.alert('请先完成所有签名');
return;
}
// 检查是否有协议内容
if (!that.order.project || !that.order.project.content) {
util.alert('还未上传协议,请联系管理员补充协议');
return;
}
// 处理协议内容,替换占位符
that.processAgreementContent();
// 打开预览弹窗
that.$refs.agreementPopup.open();
},
// 重新生成协议
regenerateAgreement: function() {
var that = this;
uni.showModal({
title: '提示',
content: "确定要重新生成协议吗?",
confirmText: "确定",
confirmColor: "#000",
cancelColor: "#eee",
success(res) {
if (res.confirm) {
// 清除协议相关数据
that.agreementImage = "";
that.file_id = "";
that.agreementId = "";
// 重新打开协议预览
that.previewAgreement();
}
}
});
},
// 协议预览全屏
viewAgreementFullscreen: function() {
var that = this;
if (that.agreementImage) {
uni.previewImage({
current: that.agreementImage,
urls: [that.agreementImage],
zIndex: 9999
});
}
},
// 检查是否可以预览协议
checkCanPreview: function() {
var that = this;
if (that.order.file_id) {
that.canPreview = true;
that.agreementImage = util.HOST + that.order.file_path;
that.agreementId = that.order.file_id;
} else {
that.canPreview = false;
that.agreementImage = "";
that.agreementId = "";
}
},
// 关闭协议预览弹窗
closeAgreementPopup: function() {
this.$refs.agreementPopup.close();
},
// 生成协议 (此方法在弹窗中调用,用于重新生成协议)
generateAgreement: function() {
var that = this;
uni.showModal({
title: '提示',
content: "确定要重新生成协议吗?",
confirmText: "确定",
confirmColor: "#000",
cancelColor: "#eee",
success(res) {
if (res.confirm) {
that.submitAgreement(); // 重新提交协议,会生成新的文件
util.alert('协议已重新生成,请刷新页面查看');
that.closeAgreementPopup(); // 关闭弹窗
}
}
});
},
// 处理协议内容,替换占位符
processAgreementContent: function() {
var that = this;
let content = that.order.project.content;
// 获取签名图片的样式(正向显示)
const getSignatureImgStyle = function(imagePath) {
if (!imagePath) return '';
// 统一使用正向显示,不进行旋转
return 'vertical-align: middle; display: inline-block; width: 50px; height: 20px; margin: 0 5px;';
};
// 定义占位符映射
const placeholders = {
'paramedic_sign': that.paramedicSignImage ? `<img src="${that.paramedicSignImage}" alt="护工签名" style="${getSignatureImgStyle(that.paramedicSignImage)}">` : '',
'customer_sign': that.customerSignImage ? `<img src="${that.customerSignImage}" alt="客户签名" style="${getSignatureImgStyle(that.customerSignImage)}">` : '',
'company_sign': `<img class="company-stamp" src="${that.companySignImage}" alt="公司签名" style="display: inline-block;width: 80px;height: 80px;position: relative;left: -150px;margin: 0 5px;vertical-align: middle;">`,
'manage_sign': `<img class="company-stamp" src="${that.companySignImage}" alt="公司签名" style="display: inline-block; width: 80px;height: 80px;position: relative;left: -150px;margin: 0 5px;vertical-align: middle;">`,
'paramedic_sign2': that.paramedicSignImage ? `<img src="${that.paramedicSignImage}" alt="护工签名" style="${getSignatureImgStyle(that.paramedicSignImage)} margin-right: 54px;">` : '',
'customer_sign2': that.customerSignImage ? `<img src="${that.customerSignImage}" alt="客户签名" style="${getSignatureImgStyle(that.customerSignImage)} margin-right: 54px;">` : '',
'manage_sign2': `<img class="company-stamp" src="${that.companySignImage}" alt="公司签名" style="display: inline-block; width: 80px;height: 80px;position: relative;left: -150px;vertical-align: middle; margin: 0 5px; margin-right: 54px;">`,
'paramedic_sign_id': that.paramedicSignId || '',
'customer_sign_id': that.customerSignId || '',
'company_sign_id': that.companySignId || 'STATIC_COMPANY_SIGN',
'days': that.order.days || '',
'price': that.order.price || '',
'total': that.order.total || '',
'range_mobile': (that.order.project && that.order.project.range_mobile) || '',
'complaint_mobile': (that.order.project && that.order.project.complaint_mobile) || '',
'today_date': (() => {
const today = new Date();
return today.getFullYear() + '年' + (today.getMonth() + 1) + '月' + today.getDate() + '日';
})()
};
// 替换所有占位符包括被HTML标签包裹的情况
Object.keys(placeholders).forEach(key => {
// 匹配 {key} 或 {<span>key</span>} 等复杂情况
const regex = new RegExp(`\\{([^}]*${key}[^}]*)\\}`, 'g');
const matches = content.match(regex);
if (matches && matches.length > 0) {
content = content.replace(regex, placeholders[key]);
}
});
// 清理text-wrap-mode: nowrap样式
content = content.replace(/text-wrap-mode:\s*nowrap;?/gi, '');
content = content.replace(/text-wrap-mode:\s*nowrap/gi, '');
that.agreementHtml = content;
},
// 生成协议图片
generateAgreementImage: function() {
var that = this;
// 防重复点击
if (that.isGenerating) {
util.alert('正在生成协议,请稍候...');
return;
}
// 设置按钮状态
that.isGenerating = true;
that.generateButtonText = '生成中...';
// 添加延迟,防止快速重复点击
setTimeout(() => {
that.startGenerateAgreement();
}, 500);
},
// 开始生成协议的具体逻辑
startGenerateAgreement: function() {
var that = this;
console.log('开始生成协议图片协议HTML内容:', that.agreementHtml);
console.log('协议HTML内容长度:', that.agreementHtml ? that.agreementHtml.length : 0);
// 使用接口生成协议图片
that.generateAgreementByAPI();
},
// 使用接口生成协议图片
generateAgreementByAPI: function() {
var that = this;
// 构建完整的HTML内容
const fullHtml = `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>协议</title>
</head>
<body style="font-family: 'SimSun', 'Microsoft YaHei', 'WenQuanYi Zen Hei', sans-serif;">
${that.agreementHtml}
</body>
</html>`;
console.log('准备发送到接口的HTML内容:', fullHtml);
var user = uni.getStorageSync('userInfo');
// 调用接口生成协议图片
uni.request({
url: util.HOST + 'manager/html-to-image',
method: 'POST',
header: {
'Content-Type': 'application/json',
},
data: {
html: fullHtml,
'token': user.access_token,
},
success: function(res) {
console.log('接口返回结果:', res);
if (res && res.statusCode === 200) {
// 获取生成的图片信息
const imageData = res.data;
that.agreementImage = util.HOST + imageData.public_path;
that.agreementId = imageData.id;
that.file_id = imageData.id;
console.log('协议生成成功:', imageData);
// 重置按钮状态
that.isGenerating = false;
that.generateButtonText = '生成协议';
// 关闭预览窗口
that.closeAgreementPopup();
util.alert('协议生成成功');
} else {
console.log('接口返回错误:', res.data);
that.handleAgreementGenerationError(res.data ? res.data.message : '协议生成失败');
}
},
fail: function(error) {
console.log('接口调用失败:', error);
that.handleAgreementGenerationError('网络请求失败');
}
});
},
// 处理协议生成错误
handleAgreementGenerationError: function(errorMessage) {
var that = this;
// 重置按钮状态
that.isGenerating = false;
that.generateButtonText = '生成协议';
util.alert(errorMessage || '协议生成失败,请稍后重试');
},
// 上传协议图片
uploadAgreementImage: function(file) {
var that = this;
var user = uni.getStorageSync('userInfo');
if (!user || !user.access_token) {
util.alert('用户未登录或token无效');
return;
}
// 将File对象转换为临时路径
const reader = new FileReader();
reader.onload = function(e) {
const tempPath = e.target.result;
uni.uploadFile({
url: util.HOST + 'manager/upload-image',
filePath: tempPath,
name: 'file',
formData: {
'token': user.access_token,
'folder': 'public'
},
success(res) {
if (res.statusCode == "200") {
try {
var mod = JSON.parse(res.data);
console.log('协议图片上传成功:', mod);
// 设置协议图片和ID
that.agreementImage = util.HOST + mod.public_path;
that.file_id = mod.id;
// 关闭弹窗
that.closeAgreementPopup();
util.alert('协议生成成功');
} catch (error) {
console.log('解析上传响应失败:', error);
util.alert('协议生成失败:响应格式错误');
// 重置按钮状态
that.isGenerating = false;
that.generateButtonText = '生成协议';
}
} else {
util.alert('协议生成失败:' + res.statusCode);
// 重置按钮状态
that.isGenerating = false;
that.generateButtonText = '生成协议';
}
},
fail(res) {
console.log('协议图片上传失败:', res);
util.alert('协议生成失败:网络错误');
// 重置按钮状态
that.isGenerating = false;
that.generateButtonText = '生成协议';
}
});
};
reader.readAsDataURL(file);
},
// 提交协议
submitAgreement: function() {
var that = this;
// 检查必要参数
if (!that.paramedicSignId || !that.customerSignId || !that.companySignId || !that.file_id) {
util.alert("请完成所有签名并生成协议文件");
return;
}
util.request({
api: 'manager/create-order-agreements',
method: 'POST',
data: {
order_id: that.id,
paramedic_id: that.order.paramedic_id,
paramedic_sign_id: that.paramedicSignId,
customer_id: that.order.customer_id,
customer_sign_id: that.customerSignId,
// 后端若需要可传固定占位值
company_sign_id: that.companySignId || 'STATIC_COMPANY_SIGN',
file_id: that.file_id
},
utilSuccess: function(r) {
util.alert("协议签订成功");
uni.navigateTo({
url: "/pages/order/order"
});
},
utilFail: function(r) {
util.alert(r);
}
});
},
// 预览协议
previewAgreement: function() {
var that = this;
// 检查三个签名是否都已完成(公司签名固定,不再校验 companySignId
if (!that.paramedicSignId || !that.customerSignId) {
util.alert('请先完成所有签名');
return;
}
// 检查是否有协议内容
if (!that.order.project || !that.order.project.content) {
util.alert('还未上传协议,请联系管理员补充协议');
return;
}
// 处理协议内容,替换占位符
that.processAgreementContent();
// 打开预览弹窗
that.$refs.agreementPopup.open();
},
// 重新生成协议
regenerateAgreement: function() {
var that = this;
uni.showModal({
title: '提示',
content: "确定要重新生成协议吗?",
confirmText: "确定",
confirmColor: "#000",
cancelColor: "#eee",
success(res) {
if (res.confirm) {
// 清除协议相关数据
that.agreementImage = "";
that.file_id = "";
that.agreementId = "";
// 重新打开协议预览
that.previewAgreement();
}
}
});
},
// 协议预览全屏
viewAgreementFullscreen: function() {
var that = this;
if (that.agreementImage) {
uni.previewImage({
current: that.agreementImage,
urls: [that.agreementImage],
zIndex: 9999
});
}
},
// 检查是否可以预览协议
checkCanPreview: function() {
var that = this;
if (that.order.file_id) {
that.canPreview = true;
that.agreementImage = util.HOST + that.order.file_path;
that.agreementId = that.order.file_id;
} else {
that.canPreview = false;
that.agreementImage = "";
that.agreementId = "";
}
},
// 关闭协议预览弹窗
closeAgreementPopup: function() {
this.$refs.agreementPopup.close();
},
// 生成协议 (此方法在弹窗中调用,用于重新生成协议)
generateAgreement: function() {
var that = this;
uni.showModal({
title: '提示',
content: "确定要重新生成协议吗?",
confirmText: "确定",
confirmColor: "#000",
cancelColor: "#eee",
success(res) {
if (res.confirm) {
that.submitAgreement(); // 重新提交协议,会生成新的文件
util.alert('协议已重新生成,请刷新页面查看');
that.closeAgreementPopup(); // 关闭弹窗
}
}
});
},
// 处理协议内容,替换占位符
processAgreementContent: function() {
var that = this;
let content = that.order.project.content;
// 获取签名图片的样式(正向显示)
const getSignatureImgStyle = function(imagePath) {
if (!imagePath) return '';
// 统一使用正向显示,不进行旋转
return 'vertical-align: middle; display: inline-block; width: 50px; height: 20px; margin: 0 5px;';
};
// 定义占位符映射
const placeholders = {
'paramedic_sign': that.paramedicSignImage ? `<img src="${that.paramedicSignImage}" alt="护工签名" style="${getSignatureImgStyle(that.paramedicSignImage)}">` : '',
'customer_sign': that.customerSignImage ? `<img src="${that.customerSignImage}" alt="客户签名" style="${getSignatureImgStyle(that.customerSignImage)}">` : '',
'company_sign': `<img class="company-stamp" src="${that.companySignImage}" alt="公司签名" style="display: inline-block;width: 80px;height: 80px;position: relative;left: -150px;margin: 0 5px;vertical-align: middle;">`,
'manage_sign': `<img class="company-stamp" src="${that.companySignImage}" alt="公司签名" style="display: inline-block; width: 80px;height: 80px;position: relative;left: -150px;margin: 0 5px;vertical-align: middle;">`,
'paramedic_sign2': that.paramedicSignImage ? `<img src="${that.paramedicSignImage}" alt="护工签名" style="${getSignatureImgStyle(that.paramedicSignImage)} margin-right: 54px;">` : '',
'customer_sign2': that.customerSignImage ? `<img src="${that.customerSignImage}" alt="客户签名" style="${getSignatureImgStyle(that.customerSignImage)} margin-right: 54px;">` : '',
'manage_sign2': `<img class="company-stamp" src="${that.companySignImage}" alt="公司签名" style="display: inline-block; width: 80px;height: 80px;position: relative;left: -150px;vertical-align: middle; margin: 0 5px; margin-right: 54px;">`,
'paramedic_sign_id': that.paramedicSignId || '',
'customer_sign_id': that.customerSignId || '',
'company_sign_id': that.companySignId || 'STATIC_COMPANY_SIGN',
'days': that.order.days || '',
'price': that.order.price || '',
'total': that.order.total || '',
'range_mobile': (that.order.project && that.order.project.range_mobile) || '',
'complaint_mobile': (that.order.project && that.order.project.complaint_mobile) || '',
'today_date': (() => {
const today = new Date();
return today.getFullYear() + '年' + (today.getMonth() + 1) + '月' + today.getDate() + '日';
})()
};
// 替换所有占位符包括被HTML标签包裹的情况
Object.keys(placeholders).forEach(key => {
// 匹配 {key} 或 {<span>key</span>} 等复杂情况
const regex = new RegExp(`\\{([^}]*${key}[^}]*)\\}`, 'g');
const matches = content.match(regex);
if (matches && matches.length > 0) {
content = content.replace(regex, placeholders[key]);
}
});
// 清理text-wrap-mode: nowrap样式
content = content.replace(/text-wrap-mode:\s*nowrap;?/gi, '');
content = content.replace(/text-wrap-mode:\s*nowrap/gi, '');
that.agreementHtml = content;
},
// 生成协议图片
generateAgreementImage: function() {
var that = this;
// 防重复点击
if (that.isGenerating) {
util.alert('正在生成协议,请稍候...');
return;
}
// 设置按钮状态
that.isGenerating = true;
that.generateButtonText = '生成中...';
// 添加延迟,防止快速重复点击
setTimeout(() => {
that.startGenerateAgreement();
}, 500);
}
}
}
</script>
<style lang="scss" scoped>
::v-deep .nav{
z-index: 998 !important;
}
.content {
padding: 20rpx;
padding-bottom: 120rpx;
}
.pageTitle {
font-size: 32rpx;
font-weight: bold;
margin: 30rpx 0 20rpx 0;
color: #333;
}
.signatureBox {
background: #fff;
border-radius: 12rpx;
padding: 20rpx;
}
.signatureItem {
margin-bottom: 30rpx;
}
.signatureTitle {
font-size: 28rpx;
color: #333;
margin-bottom: 15rpx;
}
.signatureImage {
width: 100%;
height: 200rpx;
border: 1px solid #ddd;
border-radius: 8rpx;
background-color: #f9f9f9;
}
.signatureArea {
height: 200rpx;
border: 1px solid #ddd;
border-radius: 8rpx;
background-color: #fff;
position: relative;
display: flex;
justify-content: center;
}
.signatureStart {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #0DC99E;
font-size: 28rpx;
}
.signatureActions {
margin-top: 15rpx;
display: flex;
justify-content: space-between;
}
.btnClear, .btnSave, .btnResign {
padding: 10rpx 20rpx;
border-radius: 6rpx;
font-size: 24rpx;
}
.btnClear {
color: #666;
background: #f0f0f0;
}
.btnSave {
color: #fff;
background: #0DC99E;
}
.btnResign {
color: #fff;
background: #FF9500;
}
.previewSection {
margin-top: 30rpx;
padding: 20rpx;
background: #fff;
border-radius: 12rpx;
display: flex;
justify-content: center;
}
.previewBtn {
background-color: #0DC99E;
color: #fff;
padding: 10px 20px;
border-radius: 6px;
border: none;
font-size: 16px;
cursor: pointer;
margin-right: 10px;
}
.previewBtn:hover {
background-color: #0bb08a;
}
.agreementPreviewSection {
margin-top: 20px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
z-index: 9999;
}
.sectionTitle {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 15rpx;
}
.agreementImageContainer {
height: 300rpx;
display: flex;
justify-content: center;
align-items: center;
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 8rpx;
}
.agreementImage {
width: 100%;
height: 100%;
border-radius: 8rpx;
}
.agreementActions {
margin-top: 15rpx;
display: flex;
justify-content: flex-end;
}
.btnRegenerate {
padding: 10rpx 20rpx;
border-radius: 6rpx;
font-size: 24rpx;
color: #fff;
background: #FF9500;
}
/* 弹窗样式 */
.fullscreen-popup {
width: 100vw !important;
height: 100vh !important;
max-width: none !important;
left: 0 !important;
top: 0 !important;
transform: none !important;
}
/* 预览协议弹窗样式 */
.fullscreen-popup {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
z-index: 9999;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
/* 确保在移动设备上可以滚动 */
-webkit-overflow-scrolling: touch;
overflow: hidden;
}
/* 全屏签名弹窗内部 */
.signaturePopup {
width: 100vw;
height: 90vh;
background: #fff;
display: flex;
flex-direction: column;
position: relative;
z-index: 9999;
}
.signatureWorkspace{display:flex;flex-direction:column;gap:20rpx;position:relative;height:100%;}
/* 下方按钮区域:横向排布 */
.signatureControlsRow{display:flex;align-items:center;justify-content:center;gap:20rpx;padding:20rpx 0;}
.btnRow{display:flex;align-items:center;justify-content:center;padding: 20rpx 40rpx;border-radius: 999rpx;text-align:center;font-size: 28rpx;min-width:120rpx;}
.btnRowClear{background:#f0f0f0;color:#333;border:2rpx solid #c84f3d;}
.btnRowSave{background:#0DC99E;color:#fff;}
.btnRowReset{background:#FFEFD9;color:#c84f3d;border:2rpx solid #f3c7a5;}
.signatureCanvasWrap{flex:1;}
.signatureCanvas {width: 100%;height: calc(100% - 100rpx)!important;background: #fff;border: 1px solid #eee;border-radius: 8rpx;}
/* 弹窗内提示,层级高于画布 */
.signatureInlineToast{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);background: rgba(0,0,0,0.75);color:#fff;padding: 16rpx 24rpx;border-radius: 10rpx;font-size: 26rpx;z-index: 10;}
.agreementPopup {
width: 100vw;
height: 100vh;
background: #fff;
display: flex;
flex-direction: column;
position: relative;
z-index: 9999;
/* 微信浏览器滚动优化 */
-webkit-overflow-scrolling: touch;
overflow: hidden;
/* 确保在微信浏览器中正常显示 */
-webkit-transform: translateZ(0);
transform: translateZ(0);
/* 防止微信浏览器的滚动穿透 */
overscroll-behavior: contain;
}
.popupHeader {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
border-bottom: 1px solid #eee;
background: #f8f8f8;
flex-shrink: 0;
/* 确保头部固定 */
position: sticky;
top: 0;
z-index: 10;
}
.popupTitle {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.popupClose {
font-size: 40rpx;
color: #999;
cursor: pointer;
padding: 10rpx 15rpx;
background: #f0f0f0;
border-radius: 50%;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.popupClose:hover {
background: #e0e0e0;
color: #666;
}
.popupContent {
flex: 1;
height: calc(100vh - 120rpx); /* 减去头部和底部的高度 */
overflow-y: auto;
overflow-x: hidden;
padding: 20rpx;
/* 微信浏览器滚动优化 */
-webkit-overflow-scrolling: touch;
/* 允许触摸滚动 */
touch-action: pan-y;
/* 设置滚动条样式 */
scrollbar-width: thin;
scrollbar-color: #ccc transparent;
/* 微信浏览器特殊处理 */
position: relative;
/* 确保内容可以滚动 */
overscroll-behavior: contain;
}
/* Webkit浏览器的滚动条样式 */
.popupContent::-webkit-scrollbar {
width: 6px;
}
.popupContent::-webkit-scrollbar-track {
background: transparent;
}
.popupContent::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 3px;
}
.popupContent::-webkit-scrollbar-thumb:hover {
background: #999;
}
.agreementContent {
font-size: 28rpx;
line-height: 1.6;
color: #333;
word-wrap: break-word;
word-break: break-all;
overflow-wrap: break-word;
/* 确保在移动设备上内容可以正常显示和滚动 */
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
text-size-adjust: 100%;
// height:100vh;
// overflow: scroll;
}
.agreementContent * {
max-width: 100% !important;
box-sizing: border-box !important;
/* 确保所有元素都能正常显示 */
word-wrap: break-word !important;
overflow-wrap: break-word !important;
}
.agreementContent img {
max-width: 50px !important;
height: 20px !important;
display: inline-block !important;
margin: 0 5px !important;
vertical-align: middle !important;
}
.agreementContent p, .agreementContent div {
margin: 10rpx 0 !important;
padding: 0 !important;
}
.agreementContent span {
word-break: break-all !important;
}
.noAgreement {
text-align: center;
padding: 40rpx;
color: #999;
font-size: 28rpx;
}
.popupFooter {
padding: 20rpx;
border-top: 1px solid #eee;
display: flex;
justify-content: center;
flex-shrink: 0;
/* 确保底部固定 */
position: sticky;
bottom: 0;
background: #fff;
z-index: 10;
}
.btnGenerate {
padding: 15rpx 30rpx;
border-radius: 8rpx;
font-size: 28rpx;
color: #fff;
background: #0DC99E;
}
.btnGenerate.disabled {
background-color: #ccc;
color: #666;
cursor: not-allowed;
}
.bottom {
background: #FFFFFF;
box-shadow: 0 -2rpx 12rpx 0 rgba(0, 0, 0, 0.16), inset 0 1rpx 0 0 #E4E4E4;
width: 100%;
height: 100rpx;
position: fixed;
bottom: 0;
left: 0;
display: flex;
justify-content: space-between;
}
.bottom .bottomLeft {
display: flex;
align-items: center;
}
.bottom .bottomRight {
display: flex;
flex: 1;
}
.btn {
width: 50%;
line-height: 100rpx;
text-align: center;
line-height: 100rpx;
font-family: SourceHanSansCN-Medium;
font-size: 32rpx;
letter-spacing: 0;
}
.btnCancel {
color: #666666;
background: #F0F0F0;
}
.btnSubmit {
color: #FFFFFF;
background: #0DC99E;
}
/* 移动设备优化 */
@media screen and (max-width: 768px) {
.agreementPopup {
width: 100vw;
height: 100vh;
max-height: 100vh;
}
.popupContent {
padding: 15rpx;
/* 移动设备上的滚动优化 */
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
}
.agreementContent {
font-size: 26rpx;
line-height: 1.5;
}
.popupHeader {
padding: 15rpx;
}
.popupFooter {
padding: 15rpx;
}
}
/* 小屏幕设备优化 */
@media screen and (max-width: 480px) {
.agreementContent {
font-size: 24rpx;
line-height: 1.4;
}
.popupContent {
padding: 10rpx;
}
.popupHeader {
padding: 10rpx;
}
.popupFooter {
padding: 10rpx;
}
}
/* 微信浏览器特殊优化 */
@supports (-webkit-touch-callout: none) {
.popupContent {
/* 强制启用硬件加速 */
-webkit-transform: translateZ(0);
transform: translateZ(0);
/* 微信浏览器滚动优化 */
-webkit-overflow-scrolling: touch;
/* 防止滚动卡顿 */
will-change: scroll-position;
/* 确保内容可以滚动 */
overflow-y: auto;
height: calc(100vh - 120rpx);
}
.agreementPopup {
/* 防止微信浏览器的滚动穿透 */
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
/* 微信浏览器特殊处理 */
-webkit-transform: translateZ(0);
transform: translateZ(0);
}
/* 微信浏览器中的滚动条优化 */
.popupContent::-webkit-scrollbar {
width: 4px;
}
.popupContent::-webkit-scrollbar-track {
background: transparent;
}
.popupContent::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.3);
border-radius: 2px;
}
}
</style>