ソースを参照

物资-领用单图片下载功能

huangguoce 4 日 前
コミット
c82b0d5c11

+ 7 - 0
src/api/psi/CollectService.js

@@ -53,6 +53,13 @@ export default class CollectService {
 			responseType: "blob",
 		});
 	}
+	templateImageData(params) {
+		return request({
+			url: prefix + "/psi/collect/templateImageData",
+			method: "get",
+			params: params,
+		});
+	}
 	wareHouseList(params) {
 		return request({
 			url: prefix + "/psi/collect/wareHouseList",

+ 313 - 0
src/views/psiManagement/collect/CollectImageTemplate.vue

@@ -0,0 +1,313 @@
+<template>
+	<div class="collect-image-stage">
+		<div ref="exportRoot" class="collect-image-sheet">
+			<img class="sheet-stamp" :src="stampImage" alt="审核通过章">
+			<div class="sheet-title">领用单</div>
+			<div class="sheet-store-name">景聚庭</div>
+			<div class="sheet-info-grid">
+				<div class="info-item">
+					<span class="info-label">领用编号</span>
+					<span class="info-value">{{ safeText(templateData.collectNo) }}</span>
+				</div>
+				<div class="info-item">
+					<span class="info-label">领用时间</span>
+					<span class="info-value">{{ safeText(templateData.collectDate) }}</span>
+				</div>
+				<div class="info-item">
+					<span class="info-label">领用人</span>
+					<span class="info-value">{{ safeText(templateData.handledByName) }}</span>
+				</div>
+				<div class="info-item">
+					<span class="info-label">领用人部门</span>
+					<span class="info-value">{{ safeText(templateData.handledByOfficeName) }}</span>
+				</div>
+			</div>
+			<div class="section-title">领用列表</div>
+			<table class="sheet-table">
+				<thead>
+					<tr>
+						<th class="type-col">领用类型</th>
+						<th class="goods-col">物品名称</th>
+						<th class="count-col">领用数量</th>
+						<th class="company-col">单位</th>
+						<th class="remark-col">备注</th>
+					</tr>
+				</thead>
+				<tbody>
+					<tr v-for="(item, index) in tableRows" :key="index">
+						<td>{{ safeText(item.collectType) }}</td>
+						<td>{{ safeText(item.goodsName) }}</td>
+						<td>{{ safeText(item.collectNumber) }}</td>
+						<td>{{ safeText(item.company) }}</td>
+						<td>{{ safeText(item.detailRemarks) }}</td>
+					</tr>
+				</tbody>
+			</table>
+			<div class="sheet-footer">
+				<div class="footer-row">
+					<span class="footer-label">备注</span>
+					<span class="footer-value preserve-wrap">{{ safeText(templateData.remarks) }}</span>
+				</div>
+				<div class="footer-row">
+					<span class="footer-label">审批流程</span>
+					<span class="footer-value footer-user">{{ safeText(templateData.auditUser) }}</span>
+					<span class="footer-value footer-status-message">{{ safeText(templateData.statusMessage) }}</span>
+					<span class="footer-value footer-time">{{ safeText(templateData.auditTime) }}</span>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+import stampImage from '@/assets/img/flowable/2.png'
+
+export default {
+	name: 'CollectImageTemplate',
+	props: {
+		templateData: {
+			type: Object,
+			default: () => ({
+				collectList: []
+			})
+		}
+	},
+	data() {
+		return {
+			stampImage
+		}
+	},
+	computed: {
+		tableRows() {
+			if (Array.isArray(this.templateData.collectList) && this.templateData.collectList.length > 0) {
+				return this.templateData.collectList
+			}
+			return [{}]
+		},
+		auditSummary() {
+			const parts = [
+				this.normalizeText(this.templateData.auditUser),
+				this.normalizeText(this.templateData.statusMessage),
+			].filter(Boolean)
+			return parts.length ? parts.join('   ') : '--'
+		}
+	},
+	methods: {
+		normalizeText(value) {
+			if (value === null || value === undefined) {
+				return ''
+			}
+			const text = String(value).trim()
+			return text && text !== 'null' ? text : ''
+		},
+		safeText(value) {
+			return this.normalizeText(value) || '--'
+		},
+		getExportElement() {
+			return this.$refs.exportRoot
+		}
+	}
+}
+</script>
+
+<style scoped>
+.collect-image-stage {
+	position: fixed;
+	left: -200vw;
+	top: 0;
+	pointer-events: none;
+}
+
+.collect-image-sheet {
+	position: relative;
+	width: 1080px;
+	padding: 40px 44px;
+	box-sizing: border-box;
+	background: #ffffff;
+	color: #303133;
+	font-family: "Microsoft YaHei", sans-serif;
+}
+
+.sheet-stamp {
+	position: absolute;
+	top: 60px;
+	right: 28px;
+	width: 148px;
+	height: 148px;
+	object-fit: contain;
+	opacity: 0.9;
+}
+
+.sheet-title {
+	font-size: 30px;
+	font-weight: 700;
+	letter-spacing: 2px;
+	text-align: center;
+	margin-bottom: 40px;
+}
+
+.sheet-store-name {
+	position: absolute;
+	top: 96px;
+	left: 48px;
+	font-size: 17px;
+	letter-spacing: 1px;
+	text-align: left;
+	margin: 0;
+	padding: 0;
+	z-index: 1;
+}
+
+.sheet-info-grid {
+	display: grid;
+	grid-template-columns: repeat(2, minmax(0, 1fr));
+	border: 1px solid #303133;
+	border-bottom: none;
+}
+
+.info-item {
+	display: flex;
+	min-height: 54px;
+	border-bottom: 1px solid #303133;
+}
+
+.info-item:nth-child(odd) {
+	border-right: 1px solid #303133;
+}
+
+.info-label,
+.footer-label {
+	flex: 0 0 128px;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	padding: 12px;
+	box-sizing: border-box;
+	background: #f5f7fa;
+	font-weight: 600;
+	border-right: 1px solid #303133;
+}
+
+.info-value,
+.footer-value {
+	flex: 1;
+	display: flex;
+	align-items: center;
+	padding: 12px 16px;
+	box-sizing: border-box;
+	line-height: 1.7;
+}
+
+.footer-user {
+	flex: 0 0 auto;
+	justify-content: flex-start;
+	text-align: left;
+	padding-left: 10px;
+	padding-right: 10px;
+	white-space: nowrap;
+}
+
+.footer-status-message {
+	flex: 1 1 auto;
+	min-width: 0;
+}
+
+.footer-time {
+	flex: 0 0 auto;
+	justify-content: flex-end;
+	text-align: right;
+	padding-left: 10px;
+	padding-right: 10px;
+	white-space: nowrap;
+}
+
+.section-title {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	height: 54px;
+	border: 1px solid #303133;
+	border-top: none;
+	font-size: 20px;
+	font-weight: 700;
+}
+
+.sheet-table {
+	width: 100%;
+	border-collapse: separate;
+	border-spacing: 0;
+	table-layout: fixed;
+}
+
+.sheet-table th,
+.sheet-table td {
+	padding: 14px 10px;
+	text-align: center;
+	vertical-align: middle;
+	line-height: 1.6;
+	word-break: break-word;
+	border-right: 1px solid #303133;
+	border-bottom: 1px solid #303133;
+}
+
+.sheet-table th:first-child,
+.sheet-table td:first-child {
+	border-left: 1px solid #303133;
+}
+
+.sheet-table th:last-child,
+.sheet-table td:last-child {
+	border-right: 1px solid #303133;
+}
+
+.sheet-table th {
+	background: #f5f7fa;
+	font-size: 16px;
+	font-weight: 700;
+	border-top: none;
+}
+
+.sheet-table td {
+	font-size: 15px;
+}
+
+.type-col {
+	width: 20%;
+}
+
+.goods-col {
+	width: 25%;
+}
+
+.count-col {
+	width: 16%;
+}
+
+.company-col {
+	width: 12%;
+}
+
+.remark-col {
+	width: 27%;
+}
+
+.sheet-footer {
+	border-left: 1px solid #303133;
+	border-right: 1px solid #303133;
+	border-bottom: 1px solid #303133;
+}
+
+.footer-row {
+	display: flex;
+	min-height: 56px;
+	border-bottom: 1px solid #303133;
+}
+
+.footer-row:last-child {
+	border-bottom: none;
+}
+
+.preserve-wrap {
+	white-space: pre-wrap;
+}
+</style>

+ 94 - 17
src/views/psiManagement/collect/CollectList.vue

@@ -146,12 +146,15 @@
 				<UpdateCollectInfoForm ref="updateCollectInfoForm" @refreshList="refreshList"></UpdateCollectInfoForm>
 			</div>
 		</div>
+		<CollectImageTemplate v-if="exportTemplateData" ref="collectImageTemplate" :template-data="exportTemplateData">
+		</CollectImageTemplate>
 	</div>
 </template>
 
 <script>
 import UserSelect from '@/components/userSelect'
 import CollectService from '@/api/psi/CollectService'
+import CollectImageTemplate from './CollectImageTemplate'
 import UpdateCollectInfoForm from './UpdateCollectInfoForm'
 import SelectTree from '@/components/treeSelect/treeSelect.vue'
 import taskService from '@/api/flowable/taskService'
@@ -188,7 +191,9 @@ export default {
 			isAdmin: false,
 			create: '',
 			processDefinitionAuditIdReturn: '',
-			procDefAuditKeyReturn: ''
+			procDefAuditKeyReturn: '',
+			exportTemplateData: null,
+			html2CanvasLoader: null
 		}
 	},
 	collectService: null,
@@ -198,7 +203,8 @@ export default {
 	components: {
 		UserSelect,
 		SelectTree,
-		UpdateCollectInfoForm
+		UpdateCollectInfoForm,
+		CollectImageTemplate
 	},
 	computed: {
 		userName() {
@@ -665,8 +671,8 @@ export default {
 		// 导出
 		exportFile() {
 			const options = {
-				filename: `${this.moment(new Date()).format('YYYY-MM-DD')}采购数据导出`,
-				sheetName: '采购数据导出',
+				filename: `${this.moment(new Date()).format('YYYY-MM-DD')}领用数据导出`,
+				sheetName: '领用数据导出',
 				mode: 'all'
 			}
 			this.loading = true
@@ -683,22 +689,93 @@ export default {
 				this.loading = false
 			})
 		},
-		exportByTemplate(row) {
-			const options = {
-				filename: `${this.moment(new Date()).format('YYYY-MM-DD')}领用单`,
-				sheetName: '领用单',
-				mode: 'all'
+		waitForTemplateRender() {
+			return new Promise((resolve) => {
+				this.$nextTick(() => {
+					setTimeout(resolve, 80)
+				})
+			})
+		},
+		ensureHtml2Canvas() {
+			if (window.html2canvas) {
+				return Promise.resolve(window.html2canvas)
+			}
+			if (this.html2CanvasLoader) {
+				return this.html2CanvasLoader
+			}
+			this.html2CanvasLoader = new Promise((resolve, reject) => {
+				const script = document.createElement('script')
+				script.src = '/datav/cdn/html2canvas/html2canvas.min.js'
+				script.async = true
+				script.onload = () => {
+					if (window.html2canvas) {
+						resolve(window.html2canvas)
+						return
+					}
+					reject(new Error('html2canvas不存在'))
+				}
+				script.onerror = () => reject(new Error('load html2canvas failed'))
+				document.body.appendChild(script)
+			})
+			return this.html2CanvasLoader
+		},
+		// 通过将模板转换canvas的方式下载图片
+		downloadCanvas(canvas, filename) {
+			const realFilename = filename.endsWith('.png') ? filename : `${filename}.png`
+			const triggerDownload = (href) => {
+				const downloadElement = document.createElement('a')
+				downloadElement.href = href
+				downloadElement.download = realFilename
+				document.body.appendChild(downloadElement)
+				downloadElement.click()
+				document.body.removeChild(downloadElement)
+				if (href.indexOf('blob:') === 0) {
+					window.URL.revokeObjectURL(href)
+				}
+			}
+			if (canvas.toBlob) {
+				canvas.toBlob((blob) => {
+					if (!blob) {
+						triggerDownload(canvas.toDataURL('image/png'))
+						return
+					}
+					triggerDownload(window.URL.createObjectURL(blob))
+				}, 'image/png')
+				return
 			}
+			triggerDownload(canvas.toDataURL('image/png'))
+		},
+		// 导出领用单,图片格式
+		async exportByTemplate(row) {
+			const filename = `${this.moment(new Date()).format('YYYY-MM-DD')}领用单`
 			this.loading = true
-			this.collectService.exportFileByTemplate({
-				...options,
-				id: row.id
-			}).then((res) => {
-				this.$utils.downloadExcel(res, options.filename)
-				this.loading = false
-			}).catch(() => {
+			try {
+				const data = await this.collectService.templateImageData({
+					id: row.id
+				})
+				this.exportTemplateData = {
+					...data,
+					collectList: Array.isArray(data.collectList) ? data.collectList : []
+				}
+				await this.waitForTemplateRender()
+				const html2canvas = await this.ensureHtml2Canvas()
+				//将组件作为模板
+				const exportEl = this.$refs.collectImageTemplate && this.$refs.collectImageTemplate.getExportElement()
+				if (!exportEl) {
+					throw new Error('领用模板不存在')
+				}
+				const canvas = await html2canvas(exportEl, {
+					backgroundColor: '#ffffff',
+					scale: 2,
+					useCORS: true
+				})
+				this.downloadCanvas(canvas, filename)
+			} catch (e) {
+				this.$message.error('领用单图片导出失败')
+			} finally {
+				this.exportTemplateData = null
 				this.loading = false
-			})
+			}
 		}
 	}
 }