lsw 1 rok temu
rodzic
commit
2818d78290

+ 375 - 0
app/components/signature/signature.vue

@@ -0,0 +1,375 @@
+<template>
+	<view>
+		<u-popup :show="show" round="15" mode="center" :closeable="true" :customStyle="{ width: '95%' }" @close="cancelSignature">
+			<view class="popup">
+				<view class="signature-contain">
+					<view class="signature-main">
+						<view class="title">请签字</view>
+						<canvas disable-scroll="true" class="signature" :class="cid" canvas-id="cvs" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend"></canvas>
+						<view class="signature-btns">
+							<button class="btn" style="background-color: #9e9e9e" @tap="cancelSignature()">取消</button>
+							<button class="btn" @tap="onOK()">确定</button>
+						</view>
+					</view>
+				</view>
+			</view>
+		</u-popup>
+	</view>
+</template>
+
+<script>
+let _keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+var _utf8_encode = function (string) {
+	string = string.replace(/\r\n/g, '\n');
+	var utftext = '';
+	for (var n = 0; n < string.length; n++) {
+		var c = string.charCodeAt(n);
+		if (c < 128) {
+			utftext += String.fromCharCode(c);
+		} else if (c > 127 && c < 2048) {
+			utftext += String.fromCharCode((c >> 6) | 192);
+			utftext += String.fromCharCode((c & 63) | 128);
+		} else {
+			utftext += String.fromCharCode((c >> 12) | 224);
+			utftext += String.fromCharCode(((c >> 6) & 63) | 128);
+			utftext += String.fromCharCode((c & 63) | 128);
+		}
+	}
+	return utftext;
+};
+
+let base64encode = function (input) {
+	var output = '';
+	var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
+	var i = 0;
+	input = _utf8_encode(input);
+	while (i < input.length) {
+		chr1 = input.charCodeAt(i++);
+		chr2 = input.charCodeAt(i++);
+		chr3 = input.charCodeAt(i++);
+		enc1 = chr1 >> 2;
+		enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+		enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+		enc4 = chr3 & 63;
+		if (isNaN(chr2)) {
+			enc3 = enc4 = 64;
+		} else if (isNaN(chr3)) {
+			enc4 = 64;
+		}
+		output = output + _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + _keyStr.charAt(enc3) + _keyStr.charAt(enc4);
+	}
+	return output;
+};
+export default {
+	cxt: null,
+	data() {
+		return {
+			ip: this.http.ip,
+			VERSION: '1.0.0',
+			cid: 'cvs',
+			show: false,
+			ctrl: null,
+			listeners: [],
+			prevView: '',
+
+			draws: [],
+			lines: [],
+			line: null
+		};
+	},
+	props: {
+		value: {
+			default: ''
+		},
+		title: {
+			type: String,
+			default: '请签字'
+		},
+		disabled: {
+			type: Boolean,
+			default: false
+		}
+	},
+	watch: {
+		value() {
+			this.prevView = this.value;
+		}
+	},
+	computed: {
+		titles() {
+			return this.title.split('');
+		},
+		absPrevView() {
+			var pv = this.prevView;
+			// if(pv){
+			// 	pv = this.$wrapUrl(pv)
+			// }
+			return pv;
+		}
+	},
+	mounted() {
+		this.prevView = this.value;
+		console.log('dx');
+	},
+	methods: {
+		onOK() {
+			uni.showModal({
+				title: '提示',
+				content: '确定提交签名并签约?',
+				success: (res) => {
+					if (res.confirm) {
+						uni.canvasToTempFilePath(
+							{
+								canvasId: this.cid,
+								fileType: 'png',
+								quality: 1, //图片质量
+								success: (res) => {
+									uni.uploadFile({
+										url: this.ip + '/app/common/upload',
+										filePath: res.tempFilePath,
+										name: 'file',
+										header: { Authorization: this.getUser().token },
+										success: (res) => {
+											let data = JSON.parse(res.data);
+											if (data.code == 200) {
+												this.$emit('sign', data.fileName);
+												this.hideSignature();
+											} else {
+												uni.showModal({ content: data.msg, showCancel: false });
+											}
+											uni.hideLoading();
+										},
+										fail: (res) => {
+											uni.hideLoading();
+											uni.showModal({ content: '图片上传失败', showCancel: false });
+										}
+									});
+								},
+								fail: (err) => {}
+							},
+							this
+						);
+					}
+				}
+			});
+		},
+		touchSignature() {
+			let sig = this.prevView;
+			if (!sig || !sig.length) {
+				this.showSignature();
+			}
+		},
+		showSignature() {
+			if (this.disabled) return;
+			if (!this.ctrl) {
+				this.initCtrl();
+			} else if (!this.show) {
+				this.clearSignature();
+				this.show = true;
+			}
+		},
+		async getSyncSignature() {
+			this.showSignature();
+			return await new Promise(async (resolve, reject) => {
+				this.listeners.push((res) => {
+					resolve(res);
+				});
+			});
+		},
+		cancelSignature() {
+			this.listeners.map((f) => {
+				f(null);
+			});
+			this.hideSignature();
+		},
+		hideSignature() {
+			this.ctrl && this.ctrl.clear();
+			this.show = false;
+		},
+		clearSignature() {
+			this.ctrl && this.ctrl.clear();
+		},
+		async initCtrl() {
+			this.show = true;
+			let cxt = uni.createCanvasContext(this.cid, this);
+			this.cxt = cxt;
+			// cxt.clearRect(0,0,c.width,c.height);
+			this.ctrl = {
+				width: 0,
+				height: 0,
+				clear: () => {
+					this.lines = [];
+					let info = uni
+						.createSelectorQuery()
+						.in(this)
+						.select('.' + this.cid);
+					info.boundingClientRect((data) => {
+						if (data) {
+							cxt.clearRect(0, 0, data.width, data.height);
+							if (data.width && data.height) {
+								this.ctrl.width = data.width;
+								this.ctrl.height = data.height;
+							}
+						}
+					}).exec();
+					this.redraw();
+				},
+				getValue: () => {
+					if (!this.lines.length) return '';
+					let svg = this._get_svg();
+					// new Buff
+					let b64 = base64encode(svg);
+					let data = 'data:image/svg+xml;base64,' + b64;
+					// console.log(svg);
+					// console.log(data);
+					return data;
+				}
+			};
+			this.$nextTick(function () {
+				this.ctrl.clear();
+			});
+		},
+		_get_svg() {
+			let r = -90;
+			let paths = [];
+			let raww = this.ctrl.width;
+			let rawh = this.ctrl.height;
+			let width = Math.abs(r) != 90 ? raww : rawh;
+			let height = Math.abs(r) == 90 ? raww : rawh;
+			let cx = raww / 2;
+			let cy = rawh / 2;
+			let PI = Math.PI;
+			let R = (r || 0) % 360;
+			let cosv = Math.cos((R * PI) / 180);
+			let sinv = Math.sin((R * PI) / 180);
+			let dcx = (width - raww) / 2;
+			let dcy = (height - rawh) / 2;
+			let trans = function (p) {
+				if (!R) {
+					return p;
+				} else {
+					let nx = (p.x - cx) * cosv - (p.y - cy) * sinv + cx;
+					let ny = (p.x - cx) * sinv + (p.y - cy) * cosv + cy;
+					return {
+						x: nx + dcx,
+						y: ny + dcy
+					};
+				}
+				return p;
+			};
+			this.lines.map((l) => {
+				if (l.points.length < 2) {
+					return;
+				}
+				let sp = trans(l.start);
+				let pts = [`M ${sp.x} ${Number(sp.y)}`];
+				l.points.map((p) => {
+					let np = trans(p);
+					pts.push(`L ${np.x} ${Number(np.y)}`);
+				});
+				paths.push(`<path stroke-linejoin="round" stroke-linecap="round" stroke-width="3" stroke="rgb(0,0,0)" fill="none" d="${pts.join(' ')}"/>`);
+			});
+			let svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="${width}" height="${height}">${paths.join(
+				'\n'
+			)}</svg>`;
+			return svg;
+		},
+		_get_svg_raw() {
+			let paths = [];
+			this.lines.map((l) => {
+				if (l.points.length < 2) {
+					return;
+				}
+				let pts = [`M ${l.start.x} ${Number(l.start.y)}`];
+				l.points.map((p) => {
+					pts.push(`L ${p.x} ${Number(p.y)}`);
+				});
+				paths.push(`<path stroke-linejoin="round" stroke-linecap="round" stroke-width="3" stroke="rgb(0,0,0)" fill="none" d="${pts.join(' ')}"/>`);
+			});
+			let width = this.ctrl.width;
+			let height = this.ctrl.height;
+			let svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="${width}" height="${height}" transform="rotate(-90)">${paths.join(
+				'\n'
+			)}</svg>`;
+			return svg;
+		},
+		_get_point(e) {
+			return {
+				x: e.changedTouches[0].x.toFixed(1),
+				y: e.changedTouches[0].y.toFixed(1)
+			};
+		},
+		touchstart(e) {
+			let p = this._get_point(e);
+			this.line = {
+				start: p,
+				points: [p]
+			};
+			this.lines.push(this.line);
+		},
+		touchmove(e) {
+			let p = this._get_point(e);
+			this.line.points.push(p);
+			if (!this.tm) {
+				this.tm = setTimeout(() => {
+					this.redraw();
+					this.tm = 0;
+				}, 10);
+			}
+		},
+		touchend(e) {
+			let p = this._get_point(e);
+			this.line.points.push(p);
+			this.line.end = p;
+			this.redraw();
+		},
+		redraw() {
+			let cxt = this.cxt;
+			cxt.setStrokeStyle('#000');
+			cxt.setLineWidth(3);
+			var last = null;
+			this.lines.map((l) => {
+				cxt.beginPath();
+				if (l.points.length < 2) {
+					return;
+				}
+				cxt.moveTo(l.start.x, l.start.y);
+				l.points.map((p) => {
+					cxt.lineTo(p.x, p.y);
+				});
+				cxt.stroke();
+			});
+
+			cxt.draw();
+		},
+		canvasIdErrorCallback: function (e) {
+			console.error(e.detail.errMsg);
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+.popup {
+	padding: 15px;
+	background-color: white;
+	border-radius: 10px;
+	.signature-contain {
+		width: 100%;
+		.signature-main {
+			.title {
+				text-align: center;
+			}
+			.signature {
+				width: 100%;
+				border: 1px dotted black;
+				border-bottom: 1px dotted black;
+				margin: 10px 0;
+			}
+			.btn {
+				margin-top: 10px;
+			}
+		}
+	}
+}
+</style>

+ 9 - 0
app/package.json

@@ -0,0 +1,9 @@
+{
+    "id": "sin-signature",
+    "name": "签名组件-兼容H5、小程序、APP",
+    "version": "1.0.0",
+    "description": "用于uni-app的签名组件,支持H5、小程序、APP,可导出svg矢量图片。",
+    "keywords": [
+        "签名,签字,svg,canvas"
+    ]
+}

+ 56 - 35
app/pages/index/index.vue

@@ -42,6 +42,19 @@
 					<view class="go" :style="{ backgroundColor: user.isAuthentication === 1 ? '#5a7afc' : '#d6d6d6' }">去关联</view>
 				</view>
 			</view>
+			<!--电子签约-->
+			<view class="item" @click="go('contract')">
+				<view class="icon tb">&#xe6ed;</view>
+				<view class="con">
+					<view class="bt">电子签约</view>
+					<view class="bor" style="background: linear-gradient(to right, #f6f6bf, rgb(246 246 246))"></view>
+					<view class="zt">{{ !user.isContract || user.isContract == 0 ? '尚未签约' : '已签约' }}</view>
+				</view>
+				<view class="state">
+					<view class="go" :style="{ backgroundColor: user.isCompany > 0 ? '#5a7afc' : '#d6d6d6' }" v-if="!user.isContract || user.isContract == 0">去签约</view>
+					<text class="icon" v-if="user.isContract == 1">&#xe62b;</text>
+				</view>
+			</view>
 			<!--开始接包-->
 			<view class="item" @click="go('packages')">
 				<view class="icon tb">&#xe604;</view>
@@ -51,23 +64,12 @@
 					<view class="zt">{{ !user.isCompany || user.isCompany == 0 ? '请先完成上一步' : '去接包' }}</view>
 				</view>
 				<view class="state">
-					<view class="go" :style="{ backgroundColor: user.isCompany > 0 ? '#5a7afc' : '#d6d6d6' }">去接包</view>
-				</view>
-			</view>
-			<!--电子签约-->
-			<view class="item" @click="go('contract')">
-				<view class="icon tb">&#xe6ed;</view>
-				<view class="con">
-					<view class="bt">电子签约</view>
-					<view class="bor" style="background: linear-gradient(to right, #f6f6bf, rgb(246 246 246))"></view>
-					<view class="zt">{{ !user.isContract || user.isContract == 0 ? '请签约' : '已签约' }}</view>
-				</view>
-				<view class="state">
-					<view class="go" :style="{ backgroundColor: user.isCompany > 0 ? '#5a7afc' : '#d6d6d6' }">去签约</view>
+					<view class="go" :style="{ backgroundColor: user.isCompany > 0 && user.isContract == 1 ? '#5a7afc' : '#d6d6d6' }">去接包</view>
 				</view>
 			</view>
 		</view>
 		<u-action-sheet round="20" :actions="actions" @select="selectClick" cancelText="取消" :show="show" @close="show = false"></u-action-sheet>
+		<signature ref="sig" @sign="sign" v-if=""></signature>
 	</view>
 </template>
 <script>
@@ -78,20 +80,21 @@ export default {
 			user: {},
 			bannerList: [],
 			noticeList: [],
+			contract: {},
 			show: false,
 			actions: [{ name: '查看合同' }, { name: '去签字' }]
 		};
 	},
 	onShow() {
 		if (this.hasLogin()) {
-			this.getUser();
+			this.getUserInfo();
 		}
 	},
 	onLoad() {
 		this.getData();
 	},
 	methods: {
-		getUser() {
+		getUserInfo() {
 			this.http.request({
 				url: '/app/user/info',
 				success: (res) => {
@@ -103,6 +106,7 @@ export default {
 			this.http.request({
 				url: '/app/home/index',
 				success: (res) => {
+					this.contract = res.data.data.contract;
 					this.bannerList = res.data.data.bannerList;
 					res.data.data.noticeList.forEach((item) => {
 						this.noticeList.push(item.title);
@@ -118,11 +122,11 @@ export default {
 				if (url == 'company' && this.user.isAuthentication === 1) {
 					uni.navigateTo({ url: '/pages/company/index' });
 				}
-				if (url == 'packages' && this.user.isCompany > 0) {
+				if (url == 'packages' && this.user.isCompany > 0 && this.user.isContract == 1) {
 					uni.switchTab({ url: '/pages/packages/index' });
 				}
-				if (url == 'contract' && this.user.isContract == 0) {
-					this.show = true;
+				if (url == 'contract' && this.user.isContract == 1) {
+					this.look();
 				}
 			} else {
 				uni.navigateTo({ url: '/pages/user/login' });
@@ -130,25 +134,42 @@ export default {
 		},
 		selectClick(e) {
 			if (e.name == '查看合同') {
-				uni.downloadFile({
-					url: this.ip + '/profile/upload/2024/04/24/承揽合同_20240424004417A002.docx',
-					success: (res) => {
-						// 下载成功后返回的临时文件路径
-						let filePath = res.tempFilePath;
-						// 打开临时文件
-						uni.openDocument({
-							filePath: filePath,
-							showMenu: true,
-							success: (res) => {
-								console.log('打开文档成功');
-							}
-						});
-					},
-					fail: (err) => {}
-				});
+				this.look();
 			} else {
-				console.log('合同签字');
+				this.$refs.sig.getSyncSignature();
 			}
+		},
+		//查看合同
+		look() {
+			uni.downloadFile({
+				url: this.user.isContract == 0 ? this.ip + this.contract.url : this.ip + '/app/contract/look',
+				header: { Authorization: this.getUser().token },
+				success: (res) => {
+					uni.openDocument({
+						filePath: res.tempFilePath,
+						showMenu: true,
+						success: (res) => {}
+					});
+				}
+			});
+		},
+		//电子签名
+		sign(val) {
+			this.http.request({
+				url: '/app/contract/add',
+				data: { contractId: 35, url: val },
+				method: 'POST',
+				success: (res) => {
+					uni.showModal({
+						title: '提示',
+						content: '签约成功',
+						showCancel: false,
+						success: (res) => {
+							this.getUserInfo();
+						}
+					});
+				}
+			});
 		}
 	}
 };

+ 42 - 8
app/pages/user/index.vue

@@ -2,7 +2,7 @@
 	<view class="main">
 		<view class="user" @click="go('/pages/user/info')">
 			<image :src="user.avatarUrl ? user.avatarUrl : '../../static/favicon.png'" class="head"></image>
-			<view class="con" v-if="user.token">
+			<view class="con" v-if="user.id">
 				<view class="nickName">微信用户</view>
 				<view class="welcome">欢迎使用承揽时代</view>
 			</view>
@@ -13,22 +13,22 @@
 			<view class="icon">&#xe62b;</view>
 		</view>
 		<view class="cmd">
-			<view class="s_item" @click="go('/pages/auth/index')">
+			<view class="s_item" @click="go('auth')">
 				<text class="icon ic">&#xe600;</text>
 				<text class="title">我的认证</text>
 				<text class="icon arrow">&#xe62b;</text>
 			</view>
-			<view class="s_item" @click="go('/pages/activity/up')">
+			<view class="s_item" @click="go('contract')">
 				<text class="icon ic">&#xe6ed;</text>
 				<text class="title">我的签约</text>
 				<text class="icon arrow">&#xe62b;</text>
 			</view>
-			<view class="s_item" @click="go('/pages/company/index')">
+			<view class="s_item" @click="go('company')">
 				<text class="icon ic">&#xe623;</text>
 				<text class="title">关联企业</text>
 				<text class="icon arrow">&#xe62b;</text>
 			</view>
-			<view class="s_item" @click="go('/pages/notice/index')">
+			<view class="s_item" @click="go('packages')">
 				<text class="icon ic">&#xe604;</text>
 				<text class="title">我的接包</text>
 				<text class="icon arrow">&#xe62b;</text>
@@ -40,26 +40,60 @@
 export default {
 	data() {
 		return {
+			ip: this.http.ip,
 			user: {}
 		};
 	},
 	onShow() {
-/* 		  this.user = {
+		/* 		  this.user = {
 			token: 'eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjI2NTFjMmU4LTAxNzEtNDQwYS04YjA2LTcwOWI3N2ZhNGZiZCJ9.zb2gQaeHZApJkbo4LoSeZfnVVsJJ-QabY7FnsVn13Kf1KUgKeBQ82bwhzD-CqchI1dhUOQFoVh__zeJaJHFWGg'
 		};
 		uni.setStorageSync('user', this.user); */
 		if (this.hasLogin()) {
-			this.user = this.getUser();
+			this.getUserInfo();
 		}
 	},
 	methods: {
+		getUserInfo() {
+			this.http.request({
+				url: '/app/user/info',
+				success: (res) => {
+					this.user = res.data.data;
+				}
+			});
+		},
 		go(url) {
 			if (this.hasLogin()) {
-				uni.navigateTo({ url: url });
+				if (url == 'auth') {
+					uni.navigateTo({ url: '/pages/auth/index' });
+				}
+				if (url == 'company' && this.user.isAuthentication === 1) {
+					uni.navigateTo({ url: '/pages/company/index' });
+				}
+				if (url == 'packages' && this.user.isCompany > 0 && this.user.isContract == 1) {
+					uni.switchTab({ url: '/pages/packages/index' });
+				}
+				if (url == 'contract' && this.user.isContract == 1) {
+					this.look();
+				}
 			} else {
 				uni.navigateTo({ url: '/pages/user/login' });
 			}
 		},
+		//查看合同
+		look() {
+			uni.downloadFile({
+				url: this.user.isContract == 0 ? this.ip + this.contract.url : this.ip + '/app/contract/look',
+				header: { Authorization: this.getUser().token },
+				success: (res) => {
+					uni.openDocument({
+						filePath: res.tempFilePath,
+						showMenu: true,
+						success: (res) => {}
+					});
+				}
+			});
+		},
 		//退出登录
 		exit(url) {
 			uni.showModal({

+ 0 - 17
ruoyi-admin/src/main/java/com/ruoyi/web/work/api/Api_CommonController.java

@@ -5,10 +5,7 @@ import com.ruoyi.common.config.RuoYiConfig;
 import com.ruoyi.common.constant.CacheConstants;
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.core.redis.RedisCache;
-import com.ruoyi.common.utils.DateUtils;
-import com.ruoyi.common.utils.ImageCompressUtil;
 import com.ruoyi.common.utils.file.FileUploadUtils;
-import com.ruoyi.common.utils.file.FileUtils;
 import com.ruoyi.common.utils.sign.Base64;
 import com.ruoyi.common.utils.uuid.IdUtils;
 import com.ruoyi.web.work.api.config.BaseController;
@@ -24,7 +21,6 @@ import org.springframework.web.multipart.MultipartHttpServletRequest;
 import javax.annotation.Resource;
 import javax.imageio.ImageIO;
 import java.awt.image.BufferedImage;
-import java.io.File;
 import java.io.IOException;
 import java.util.concurrent.TimeUnit;
 
@@ -49,19 +45,6 @@ public class Api_CommonController extends BaseController {
             // 上传文件路径
             String filePath = RuoYiConfig.getUploadPath();
             String fileName = FileUploadUtils.upload(filePath, multiReq.getFile("file"));
-
-            String suffix = ".JPEG .jpeg .JPG .jpg .png .PNG .gif .GIF";
-            String fileType = fileName.substring(fileName.lastIndexOf("."));
-            //图片才压缩
-            if (suffix.indexOf(fileType) != -1) {
-                String oldFile = filePath + fileName.replace("/profile/upload", "");
-                String imagName = System.currentTimeMillis() + ".jpg";
-                String newFile = filePath + "/" + DateUtils.datePath() + "/" + imagName;
-                ImageCompressUtil.zipImageFile(new File(oldFile), new File(newFile));
-                //删掉压缩前的图片
-                FileUtils.deleteFile(oldFile);
-                fileName = "/profile/upload/" + DateUtils.datePath() + "/" + imagName;
-            }
             ajax.put("fileName", fileName);
             return ajax;
         } catch (Exception e) {

+ 18 - 5
ruoyi-admin/src/main/java/com/ruoyi/web/work/api/Api_ContractController.java

@@ -1,12 +1,16 @@
 package com.ruoyi.web.work.api;
 
-import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.web.work.api.config.BaseController;
+import com.ruoyi.web.work.domain.dto.ContractDto;
 import com.ruoyi.web.work.service.IContractService;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.rmi.ServerException;
 
 /**
  * 合同模板
@@ -15,7 +19,7 @@ import org.springframework.web.bind.annotation.RestController;
  * @date 2024-04-22
  */
 @RestController
-@RequestMapping("/work/contract")
+@RequestMapping("/app/contract")
 public class Api_ContractController extends BaseController {
     @Autowired
     private IContractService contractService;
@@ -25,4 +29,13 @@ public class Api_ContractController extends BaseController {
         return AjaxResult.success(contractService.selectContract());
     }
 
+    @PostMapping(value = "/add")
+    public AjaxResult add(@Validated @RequestBody ContractDto dto) throws ServerException {
+        return contractService.add(dto);
+    }
+
+    @GetMapping("/look")
+    public void look(HttpServletResponse response) throws IOException {
+        contractService.look(response);
+    }
 }

+ 5 - 1
ruoyi-admin/src/main/java/com/ruoyi/web/work/api/Api_HomeController.java

@@ -3,6 +3,7 @@ package com.ruoyi.web.work.api;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.web.work.service.IBannerService;
+import com.ruoyi.web.work.service.IContractService;
 import com.ruoyi.web.work.service.INoticeService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -24,12 +25,15 @@ public class Api_HomeController extends BaseController {
     @Autowired
     private IBannerService bannerService;
 
+    @Autowired
+    private IContractService contractService;
+
     @GetMapping("/index")
     public AjaxResult index() {
         AjaxResult result = new AjaxResult();
         result.put("bannerList", bannerService.homeList());
         result.put("noticeList", noticeService.queryList());
+        result.put("contract", contractService.selectContract());
         return AjaxResult.success(result);
     }
-
 }

+ 7 - 3
ruoyi-admin/src/main/java/com/ruoyi/web/work/domain/ContractDetail.java

@@ -1,13 +1,14 @@
 package com.ruoyi.web.work.domain;
 
-import java.util.Date;
-import com.fasterxml.jackson.annotation.JsonFormat;
 import com.baomidou.mybatisplus.annotation.FieldFill;
 import com.baomidou.mybatisplus.annotation.TableField;
-import lombok.Data;
 import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
 import lombok.experimental.Accessors;
+
+import java.util.Date;
 /**
  * @author lsw
  * @date 2024-04-24
@@ -29,6 +30,9 @@ public class ContractDetail{
     @ApiModelProperty(value = "合同路径")
     private String url;
 
+    @ApiModelProperty(value = "合同模板路径")
+    private String contractUrl;
+
     @ApiModelProperty(value = "状态:0=正常,1=停用")
     private Integer state;
 

+ 18 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/work/domain/dto/ContractDto.java

@@ -0,0 +1,18 @@
+package com.ruoyi.web.work.domain.dto;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@Data
+@Accessors(chain = true)
+public class ContractDto {
+
+    @NotNull(message = "合同模板不能为空")
+    private Long contractId;
+
+    @NotBlank(message = "签名不能为空")
+    private String url;
+}

+ 8 - 2
ruoyi-admin/src/main/java/com/ruoyi/web/work/mapper/ContractDetailMapper.java

@@ -1,8 +1,11 @@
 package com.ruoyi.web.work.mapper;
 
-import java.util.List;
-import com.ruoyi.web.work.domain.ContractDetail;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.web.work.domain.ContractDetail;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
 
 /**
  * @author lsw
@@ -10,4 +13,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  */
 public interface ContractDetailMapper extends BaseMapper<ContractDetail> {
     List<ContractDetail> selectList(ContractDetail contractDetail);
+
+    @Select("SELECT * FROM tb_contract_detail WHERE user_id=#{userId}")
+    ContractDetail selectByUserId(@Param("userId") Long userId);
 }

+ 2 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/work/service/IContractDetailService.java

@@ -11,4 +11,6 @@ import java.util.List;
  */
 public interface IContractDetailService extends IService<ContractDetail> {
     List<ContractDetail> selectList(ContractDetail contractDetail);
+
+    ContractDetail selectByUserId(Long userId);
 }

+ 9 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/work/service/IContractService.java

@@ -1,8 +1,13 @@
 package com.ruoyi.web.work.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.web.work.domain.Contract;
+import com.ruoyi.web.work.domain.dto.ContractDto;
 
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.rmi.ServerException;
 import java.util.List;
 
 /**
@@ -13,4 +18,8 @@ public interface IContractService extends IService<Contract>{
     List<Contract> selectList(Contract contract);
 
     Contract selectContract();
+
+    AjaxResult add(ContractDto dto) throws ServerException;
+
+    void look(HttpServletResponse response) throws IOException;
 }

+ 5 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/work/service/impl/ContractDetailServiceImpl.java

@@ -22,4 +22,9 @@ public class ContractDetailServiceImpl extends ServiceImpl<ContractDetailMapper,
     public List<ContractDetail> selectList(ContractDetail contractDetail) {
         return contractDetailMapper.selectList(contractDetail);
     }
+
+    @Override
+    public ContractDetail selectByUserId(Long userId) {
+        return contractDetailMapper.selectByUserId(userId);
+    }
 }

+ 102 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/work/service/impl/ContractServiceImpl.java

@@ -1,12 +1,33 @@
 package com.ruoyi.web.work.service.impl;
 
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.config.RuoYiConfig;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.web.work.api.util.AppUtil;
 import com.ruoyi.web.work.domain.Contract;
+import com.ruoyi.web.work.domain.ContractDetail;
+import com.ruoyi.web.work.domain.User;
+import com.ruoyi.web.work.domain.dto.ContractDto;
 import com.ruoyi.web.work.mapper.ContractMapper;
+import com.ruoyi.web.work.service.IContractDetailService;
 import com.ruoyi.web.work.service.IContractService;
+import com.ruoyi.web.work.service.IUserService;
+import com.spire.doc.Document;
+import com.spire.doc.FileFormat;
+import com.spire.doc.documents.TextSelection;
+import com.spire.doc.fields.DocPicture;
+import com.spire.doc.fields.TextRange;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
+import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.rmi.ServerException;
 import java.util.List;
 
 /**
@@ -18,6 +39,12 @@ public class ContractServiceImpl extends ServiceImpl<ContractMapper, Contract> i
     @Autowired
     private ContractMapper contractMapper;
 
+    @Autowired
+    private IContractDetailService detailService;
+
+    @Autowired
+    IUserService userService;
+
     @Override
     public List<Contract> selectList(Contract contract) {
         return contractMapper.selectList(contract);
@@ -27,4 +54,79 @@ public class ContractServiceImpl extends ServiceImpl<ContractMapper, Contract> i
     public Contract selectContract() {
         return contractMapper.selectContract();
     }
+
+    @Transactional
+    @Override
+    public AjaxResult add(ContractDto dto) throws ServerException {
+        Contract contract = getById(dto.getContractId());
+        if (contract == null) {
+            return AjaxResult.error("合同信息不存在");
+        }
+        ContractDetail detail = detailService.selectByUserId(AppUtil.getUser().getId());
+        if (detail != null) {
+            return AjaxResult.error("你已签署合同");
+        }
+        detail = new ContractDetail();
+        detail.setUserId(AppUtil.getUser().getId());
+        detail.setContractId(contract.getId());
+        detail.setContractUrl(contract.getUrl());
+        detail.setUrl(dto.getUrl());
+        detail.setState(1);
+        if (!detailService.save(detail)) {
+            throw new ServerException("签署合同失败,请联系平台");
+        }
+        User user = userService.getById(AppUtil.getUser().getId());
+        user.setIsContract(1);
+        if (!userService.updateById(user)) {
+            throw new ServerException("签署合同失败,请联系平台");
+        }
+        return AjaxResult.success();
+    }
+
+    @Override
+    public void look(HttpServletResponse response) throws IOException {
+        ContractDetail detail = detailService.selectByUserId(AppUtil.getUser().getId());
+        if (detail == null) {
+            throw new ServerException("你还未签约");
+        }
+        String contractPath = RuoYiConfig.getProfile() + StringUtils.substringAfter(RuoYiConfig.getProfile() + detail.getContractUrl(), Constants.RESOURCE_PREFIX);
+        String imgPath = RuoYiConfig.getProfile() + StringUtils.substringAfter(RuoYiConfig.getProfile() + detail.getUrl(), Constants.RESOURCE_PREFIX);
+        // 加载文档
+        Document doc = new Document();
+        doc.loadFromFile(contractPath);
+        // 创建一个 DocPicture 对象,并加载要插入的图片
+        DocPicture picture = new DocPicture(doc);
+        picture.loadImage(imgPath);
+
+        // 调整图片大小和旋转角度
+        picture.setWidth(80); // 设置图片宽度
+        picture.setHeight(30); // 设置图片高度
+        // 查找并替换占位符
+        //doc.replace("${jiaSign}", "高韦神", true, true);
+
+        // 查找并替换占位符
+        TextSelection[] selections = doc.findAllString("${jiaSign}", true, true);
+        for (TextSelection selection : selections) {
+            // 获取占位符的文本范围
+            TextRange range = selection.getAsOneRange();
+            // 在占位符位置插入图片
+            range.getOwnerParagraph().getChildObjects().insert(range.getOwnerParagraph().getChildObjects().indexOf(range) + 1, picture);
+            // 移除占位符
+            range.getOwnerParagraph().getChildObjects().remove(range);
+        }
+        // 创建一个内存流
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        // 将文档保存到流中
+        doc.saveToStream(outputStream, FileFormat.Docx_2013);
+        // 获取流中的字节数组
+        byte[] byteArray = outputStream.toByteArray();
+        // 设置响应头
+        response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
+        response.setHeader("Content-Disposition", "attachment; filename=output.docx");
+        // 将流中的内容写入响应输出流
+        OutputStream outStream = response.getOutputStream();
+        outStream.write(byteArray);
+        // 关闭输出流
+        outStream.close();
+    }
 }

+ 9 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/work/service/impl/PackagesServiceImpl.java

@@ -5,10 +5,12 @@ import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.web.work.api.util.AppUtil;
 import com.ruoyi.web.work.domain.Packages;
 import com.ruoyi.web.work.domain.Project;
+import com.ruoyi.web.work.domain.User;
 import com.ruoyi.web.work.domain.vo.PackagesListVo;
 import com.ruoyi.web.work.mapper.PackagesMapper;
 import com.ruoyi.web.work.service.IPackagesService;
 import com.ruoyi.web.work.service.IProjectService;
+import com.ruoyi.web.work.service.IUserService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -27,6 +29,9 @@ public class PackagesServiceImpl extends ServiceImpl<PackagesMapper, Packages> i
     @Autowired
     private IProjectService projectService;
 
+    @Autowired
+    IUserService userService;
+
     @Override
     public List<Packages> selectList(Packages packages) {
         return packagesMapper.selectList(packages);
@@ -41,6 +46,10 @@ public class PackagesServiceImpl extends ServiceImpl<PackagesMapper, Packages> i
     @Transactional
     @Override
     public AjaxResult add(Packages packages) {
+        User user = userService.getById(AppUtil.getUser().getId());
+        if (user.getIsContract() == 0) {
+            return AjaxResult.error("请先签约");
+        }
         Project project = projectService.getById(packages.getProjectId());
         if (project == null || project.getState() == 1) {
             return AjaxResult.error("项目不存在或被停用");

+ 0 - 9
ruoyi-admin/src/main/resources/mapper/work/BannerMapper.xml

@@ -6,15 +6,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     
     <select id="selectList" resultType="com.ruoyi.web.work.domain.Banner">
         select * from tb_banner
-        <where>  
-            <if test="title != null  and title != ''"> and title = #{title}</if>
-            <if test="pic != null  and pic != ''"> and pic = #{pic}</if>
-            <if test="types != null  and types != ''"> and types = #{types}</if>
-            <if test="contentId != null  and contentId != ''"> and content_id = #{contentId}</if>
-            <if test="contentTitle != null  and contentTitle != ''"> and content_title = #{contentTitle}</if>
-            <if test="state != null "> and state = #{state}</if>
-            <if test="orderNum != null "> and order_num = #{orderNum}</if>
-        </where>
     </select>
 
 </mapper>