作者 lixiang

1、增加参会证生成功能

2、增加指定人员参会证下载功能
3、增加批量下载参会证功能
@@ -72,3 +72,35 @@ export function addMeetinglst(params) { @@ -72,3 +72,35 @@ export function addMeetinglst(params) {
72 } 72 }
73 }) 73 })
74 } 74 }
  75 +
  76 +export function generateBadges(params) {
  77 +
  78 + return request({
  79 + url: '/xvisit/meetingattendance/generateBadges' ,
  80 + method: 'post',
  81 + data: params
  82 + })
  83 +}
  84 +
  85 +export function downloadBadge(params) {
  86 + return request({
  87 + url: 'xvisit/meetingattendance/downloadBadge',
  88 + method: 'get',
  89 + params: params,
  90 + responseType: 'blob', // 确保设置为blob
  91 + headers: {
  92 + 'Content-Type': 'application/json'
  93 + }
  94 + });
  95 +}
  96 +export function downloadBadgeBatch(params) {
  97 + return request({
  98 + url: 'xvisit/meetingattendance/downloadBadgeBatch',
  99 + method: 'post',
  100 + data: params,
  101 + responseType: 'blob',
  102 + headers: {
  103 + 'Content-Type': 'application/json'
  104 + }
  105 + });
  106 +}
@@ -227,6 +227,7 @@ export default { @@ -227,6 +227,7 @@ export default {
227 227
228 }, 228 },
229 meetatt: { 229 meetatt: {
  230 +
230 query: '検索', 231 query: '検索',
231 reset: 'リセット', 232 reset: 'リセット',
232 add: '追加', 233 add: '追加',
@@ -283,11 +284,22 @@ export default { @@ -283,11 +284,22 @@ export default {
283 memo_P: 'X', 284 memo_P: 'X',
284 del_flag_P: 'X', 285 del_flag_P: 'X',
285 code_P: 'QRコードを入力してください', 286 code_P: 'QRコードを入力してください',
286 -  
287 - 287 + zh_pass: "中文参会证",
  288 + ja_pass: "日文参会证",
288 attendee_id_R: '個人IDは空白にできない', 289 attendee_id_R: '個人IDは空白にできない',
289 meeting_id_R: '会議IDを空にすることはできません', 290 meeting_id_R: '会議IDを空にすることはできません',
290 291
  292 + generate_badge:'参加証を生成',
  293 + download: "参加証をダウンロード",
  294 + download_zh: "中国語版",
  295 + download_ja: "日本語版",
  296 + download_confirm: "参加証をダウンロードしますか?",
  297 + download_success: "参加証のダウンロードが成功しました",
  298 + download_fail: "ダウンロード失敗:",
  299 + download_fail_empty: "ダウンロード内容が空です",
  300 + download_cancel: "ダウンロードをキャンセルしました",
  301 + badge: "参加証",
  302 + batch_download :"一括ダウンロード"
291 }, 303 },
292 tab: { 304 tab: {
293 basic: '基本情報', 305 basic: '基本情報',
@@ -31,20 +31,29 @@ @@ -31,20 +31,29 @@
31 <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" 31 <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
32 v-hasPermi="['xvisit:meetingattendance:add']">{{ $t('meetatt.add') }}</el-button> 32 v-hasPermi="['xvisit:meetingattendance:add']">{{ $t('meetatt.add') }}</el-button>
33 </el-col> 33 </el-col>
34 -  
35 <el-col :span="1.5"> 34 <el-col :span="1.5">
36 <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" 35 <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
37 v-hasPermi="['xvisit:meetingattendance:export']">{{ $t('meetatt.export') }}</el-button> 36 v-hasPermi="['xvisit:meetingattendance:export']">{{ $t('meetatt.export') }}</el-button>
38 </el-col> 37 </el-col>
  38 + <el-col :span="1.5">
  39 + <el-button type="success" plain icon="el-icon-tickets" size="mini" @click="handleGenerateBadge"
  40 + :disabled="multiple" v-hasPermi="['xvisit:meetingattendance:generate']">{{ $t('meetatt.generate_badge') }}</el-button>
  41 + </el-col>
  42 + <el-col :span="1.5">
  43 + <el-button type="info" plain icon="el-icon-download" size="mini" @click="handleBatchDownload"
  44 + :disabled="multiple" v-hasPermi="['xvisit:meetingattendance:downloadZip']">{{ $t('meetatt.batch_download') }}</el-button>
  45 + </el-col>
39 </el-row> 46 </el-row>
40 47
41 - <el-table v-loading="loading" :data="meetingattendanceList"> 48 + <el-table v-loading="loading" :data="meetingattendanceList" @selection-change="handleSelectionChange">
  49 + <el-table-column type="selection" width="55" align="center" />
42 <el-table-column :label="$t('meetatt.meeting_id')" align="center" prop="meetingId" /> 50 <el-table-column :label="$t('meetatt.meeting_id')" align="center" prop="meetingId" />
43 <el-table-column :label="$t('meetatt.meeting_name')" align="center" prop="meetingName" /> 51 <el-table-column :label="$t('meetatt.meeting_name')" align="center" prop="meetingName" />
44 <el-table-column :label="$t('meetatt.attendee_id')" align="center" prop="attendeeId" /> 52 <el-table-column :label="$t('meetatt.attendee_id')" align="center" prop="attendeeId" />
45 <el-table-column :label="$t('meetatt.attendee_name')" align="center" prop="attendeeName" /> 53 <el-table-column :label="$t('meetatt.attendee_name')" align="center" prop="attendeeName" />
46 <el-table-column :label="$t('meetatt.seat_info')" align="center" prop="seatInfo" /> 54 <el-table-column :label="$t('meetatt.seat_info')" align="center" prop="seatInfo" />
47 - <el-table-column :label="$t('meetatt.attend_sts')" align="center" prop="attendSts"><template #default="scope"> 55 + <el-table-column :label="$t('meetatt.attend_sts')" align="center" prop="attendSts">
  56 + <template #default="scope">
48 <el-tag v-if="scope.row.attendSts == '0'">{{ $t('meetatt.sign_sts_0') }}</el-tag> 57 <el-tag v-if="scope.row.attendSts == '0'">{{ $t('meetatt.sign_sts_0') }}</el-tag>
49 <el-tag v-else-if="scope.row.attendSts == '1'">{{ $t('meetatt.sign_sts_1') }}</el-tag> 58 <el-tag v-else-if="scope.row.attendSts == '1'">{{ $t('meetatt.sign_sts_1') }}</el-tag>
50 </template> 59 </template>
@@ -55,18 +64,63 @@ @@ -55,18 +64,63 @@
55 <el-tag v-else-if="scope.row.paySts == '1'">{{ $t('meetatt.pay_sts_1') }}</el-tag> 64 <el-tag v-else-if="scope.row.paySts == '1'">{{ $t('meetatt.pay_sts_1') }}</el-tag>
56 </template> 65 </template>
57 </el-table-column> 66 </el-table-column>
  67 + <el-table-column :label="$t('meetatt.code')" align="center" prop="attendeeCode" />
  68 +
  69 + <el-table-column :label="$t('meetatt.pay_image')" align="center" prop="payImage" width="100">
  70 + <template slot-scope="scope">
  71 + <image-preview :src="scope.row.payImage" :width="50" :height="50" />
  72 + </template>
  73 + </el-table-column>
  74 +
  75 +
  76 + <el-table-column :label="$t('meetatt.zh_pass')" align="center" width="120">
  77 + <template slot-scope="scope">
  78 + <div v-if="scope.row.conferencePassZhBase64" class="image-cell">
  79 + <el-image
  80 + :src="scope.row.conferencePassZhBase64"
  81 + :preview-src-list="getPreviewList(scope.row, 'Zh')"
  82 + fit="contain"
  83 + class="table-image"
  84 + :z-index="9999"
  85 + @click.stop
  86 + >
  87 + <div slot="error" class="image-slot">
  88 + <i class="el-icon-picture-outline"></i>
  89 + </div>
  90 + </el-image>
  91 + </div>
  92 + <span v-else class="no-image">无</span>
  93 + </template>
  94 + </el-table-column>
  95 +
  96 + <el-table-column :label="$t('meetatt.ja_pass')" align="center" width="120">
  97 + <template slot-scope="scope">
  98 + <div v-if="scope.row.conferencePassJaBase64" class="image-cell">
  99 + <el-image
  100 + :src="scope.row.conferencePassJaBase64"
  101 + :preview-src-list="getPreviewList(scope.row, 'Ja')"
  102 + fit="contain"
  103 + class="table-image"
  104 + :z-index="9999"
  105 + @click.stop
  106 + >
  107 + <div slot="error" class="image-slot">
  108 + <i class="el-icon-picture-outline"></i>
  109 + </div>
  110 + </el-image>
  111 + </div>
  112 + <span v-else class="no-image">无</span>
  113 + </template>
  114 + </el-table-column>
  115 +
58 <el-table-column :label="$t('meetatt.act')" align="center" class-name="small-padding fixed-width" fixed="right"> 116 <el-table-column :label="$t('meetatt.act')" align="center" class-name="small-padding fixed-width" fixed="right">
59 <template slot-scope="scope"> 117 <template slot-scope="scope">
60 <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" 118 <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
61 v-hasPermi="['xvisit:meetingattendance:edit']">{{ $t('meetatt.edit') }}</el-button> 119 v-hasPermi="['xvisit:meetingattendance:edit']">{{ $t('meetatt.edit') }}</el-button>
62 <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" 120 <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
63 v-hasPermi="['xvisit:meetingattendance:remove']">{{ $t('meetatt.del') }}</el-button> 121 v-hasPermi="['xvisit:meetingattendance:remove']">{{ $t('meetatt.del') }}</el-button>
64 - </template>  
65 - </el-table-column>  
66 - <el-table-column :label="$t('meetatt.code')" align="center" prop="attendeeCode" />  
67 - <el-table-column :label="$t('meetatt.pay_image')" align="center" prop="payImage" width="100">  
68 - <template slot-scope="scope">  
69 - <image-preview :src="scope.row.payImage" :width="50" :height="50" /> 122 + <el-button size="mini" type="text" icon="el-icon-download" @click="handleDownload(scope.row)"
  123 + v-hasPermi="['xvisit:meetingattendance:download']">{{ $t('meetatt.download') }}</el-button>
70 </template> 124 </template>
71 </el-table-column> 125 </el-table-column>
72 </el-table> 126 </el-table>
@@ -124,7 +178,6 @@ @@ -124,7 +178,6 @@
124 <el-button @click="cancel">{{ $t('meetatt.cancel') }}</el-button> 178 <el-button @click="cancel">{{ $t('meetatt.cancel') }}</el-button>
125 </div> 179 </div>
126 </el-dialog> 180 </el-dialog>
127 -  
128 </div> 181 </div>
129 </template> 182 </template>
130 183
@@ -136,6 +189,9 @@ import { @@ -136,6 +189,9 @@ import {
136 addMeetingattendance, 189 addMeetingattendance,
137 updateMeetingattendance, 190 updateMeetingattendance,
138 checkDataExists, 191 checkDataExists,
  192 + generateBadges,
  193 + downloadBadge,
  194 + downloadBadgeBatch
139 } from "@/api/xvisit/meetingattendance"; 195 } from "@/api/xvisit/meetingattendance";
140 196
141 import { listMeeting } from "@/api/xvisit/meeting"; 197 import { listMeeting } from "@/api/xvisit/meeting";
@@ -223,6 +279,94 @@ export default { @@ -223,6 +279,94 @@ export default {
223 }, 279 },
224 280
225 methods: { 281 methods: {
  282 + /** 批量下载参会证按钮操作 */
  283 + handleBatchDownload() {
  284 + if (this.ids.length === 0) {
  285 + this.$modal.msgWarning("请至少选择一条数据");
  286 + return;
  287 + }
  288 +
  289 + // 检查选中的记录是否都有参会证
  290 + const hasNoBadge = this.meetingattendanceList
  291 + .filter(item => this.ids.includes(item.meetingId) && this.attendIds.includes(item.attendeeId))
  292 + .some(item => !item.conferencePassZhBase64 && !item.conferencePassJaBase64);
  293 +
  294 + if (hasNoBadge) {
  295 + this.$modal.confirm('选中的记录中存在未生成参会证的数据,是否继续下载已生成的参会证?').then(() => {
  296 + this.downloadSelectedBadges();
  297 + }).catch(() => {});
  298 + } else {
  299 + this.downloadSelectedBadges();
  300 + }
  301 + },
  302 +
  303 + /** 执行批量下载 */
  304 + downloadSelectedBadges() {
  305 + const params = this.meetingattendanceList
  306 + .filter(item => this.ids.includes(item.meetingId) && this.attendIds.includes(item.attendeeId))
  307 + .map(item => ({
  308 + meetingId: item.meetingId,
  309 + attendeeId: item.attendeeId
  310 + }));
  311 +
  312 + this.$modal.loading("正在准备批量下载...");
  313 +
  314 + downloadBadgeBatch(params).then(response => {
  315 + const url = window.URL.createObjectURL(new Blob([response]));
  316 + const link = document.createElement('a');
  317 + link.href = url;
  318 + link.setAttribute('download', '参会证批量下载.zip');
  319 + document.body.appendChild(link);
  320 + link.click();
  321 + document.body.removeChild(link);
  322 + window.URL.revokeObjectURL(url);
  323 + this.$modal.closeLoading();
  324 + }).catch(error => {
  325 + this.$modal.closeLoading();
  326 + this.$modal.msgError("批量下载失败: " + (error.message || error));
  327 + console.error('批量下载失败:', error);
  328 + });
  329 + },
  330 +
  331 +
  332 + /** 下载参会证按钮操作 */
  333 + handleDownload(row) {
  334 + const params = {
  335 + meetingId: row.meetingId,
  336 + attendeeId: row.attendeeId,
  337 + langType: 'zh' // 默认中文,可根据需要修改
  338 + };
  339 +
  340 + this.$modal.loading("正在准备下载...");
  341 +
  342 + downloadBadge(params).then(response => {
  343 + console.log("response.data",response)
  344 + // 创建一个URL对象
  345 + const url = window.URL.createObjectURL(new Blob([response]));
  346 +
  347 + // 创建a标签并触发下载
  348 + const link = document.createElement('a');
  349 + link.href = url;
  350 + link.setAttribute('download', '参会证.zip');
  351 + document.body.appendChild(link);
  352 + link.click();
  353 +
  354 + // 清理
  355 + document.body.removeChild(link);
  356 + window.URL.revokeObjectURL(url);
  357 +
  358 + this.$modal.closeLoading();
  359 + }).catch(error => {
  360 + this.$modal.closeLoading();
  361 + this.$modal.msgError("下载失败: " + (error.message || error));
  362 + console.error('下载失败:', error);
  363 + });
  364 + },
  365 +
  366 + getPreviewList(row, type) {
  367 + const image = type === 'Zh' ? row.conferencePassZhBase64 : row.conferencePassJaBase64;
  368 + return image ? [image] : [];
  369 + },
226 /** 查询签到状态列表 */ 370 /** 查询签到状态列表 */
227 getList() { 371 getList() {
228 this.loading = true; 372 this.loading = true;
@@ -233,6 +377,14 @@ export default { @@ -233,6 +377,14 @@ export default {
233 }); 377 });
234 }, 378 },
235 379
  380 + // 多选框选中数据
  381 + handleSelectionChange(selection) {
  382 + this.ids = selection.map(item => item.meetingId);
  383 + this.attendIds = selection.map(item => item.attendeeId);
  384 + this.single = selection.length !== 1;
  385 + this.multiple = !selection.length;
  386 + },
  387 +
236 // 取消按钮 388 // 取消按钮
237 cancel() { 389 cancel() {
238 this.open = false; 390 this.open = false;
@@ -268,10 +420,6 @@ export default { @@ -268,10 +420,6 @@ export default {
268 420
269 /** 重置按钮操作 */ 421 /** 重置按钮操作 */
270 resetQuery() { 422 resetQuery() {
271 -  
272 - // this.$router.push('/meetingattendance');  
273 - // this.$router.replace({ path: this.$route.path, query: {} });  
274 -  
275 this.resetForm("queryForm"); 423 this.resetForm("queryForm");
276 this.resetForm("form"); 424 this.resetForm("form");
277 425
@@ -292,10 +440,9 @@ export default { @@ -292,10 +440,9 @@ export default {
292 /** 新增会议按钮操作 */ 440 /** 新增会议按钮操作 */
293 handleAdd() { 441 handleAdd() {
294 this.reset(); 442 this.reset();
295 -  
296 this.queryParams.pageNum = 1; 443 this.queryParams.pageNum = 1;
297 -  
298 this.queryParams.pageSize = 1000; 444 this.queryParams.pageSize = 1000;
  445 +
299 listVisit(this.queryParams).then((response) => { 446 listVisit(this.queryParams).then((response) => {
300 this.postOptions = response.rows.map((row) => ({ 447 this.postOptions = response.rows.map((row) => ({
301 attendeeId: row.attendeeId, 448 attendeeId: row.attendeeId,
@@ -303,8 +450,8 @@ export default { @@ -303,8 +450,8 @@ export default {
303 })); 450 }));
304 this.open = true; 451 this.open = true;
305 this.title = this.$t('meetatt.add_title'); 452 this.title = this.$t('meetatt.add_title');
306 -  
307 }); 453 });
  454 +
308 listMeeting(this.queryParams).then((response) => { 455 listMeeting(this.queryParams).then((response) => {
309 this.postOptions2 = response.rows.map((row) => ({ 456 this.postOptions2 = response.rows.map((row) => ({
310 meetingId: row.meetingId, 457 meetingId: row.meetingId,
@@ -314,26 +461,24 @@ export default { @@ -314,26 +461,24 @@ export default {
314 }, 461 },
315 462
316 onAttendeeChange() { 463 onAttendeeChange() {
317 - // 根据选中的attendeeId查找对应的name  
318 const selectedAttendee = this.postOptions.find( 464 const selectedAttendee = this.postOptions.find(
319 (item) => item.attendeeId === this.form.attendeeId 465 (item) => item.attendeeId === this.form.attendeeId
320 ); 466 );
321 if (selectedAttendee) { 467 if (selectedAttendee) {
322 this.form.name = selectedAttendee.name; 468 this.form.name = selectedAttendee.name;
323 } else { 469 } else {
324 - this.form.name = ""; // 如果没有找到,清空文本框 470 + this.form.name = "";
325 } 471 }
326 }, 472 },
327 473
328 onMeetingChange() { 474 onMeetingChange() {
329 - // 根据选中的attendeeId查找对应的name  
330 const selectedMeeting = this.postOptions2.find( 475 const selectedMeeting = this.postOptions2.find(
331 (item) => item.meetingId === this.form.meetingId 476 (item) => item.meetingId === this.form.meetingId
332 ); 477 );
333 if (selectedMeeting) { 478 if (selectedMeeting) {
334 this.form.meetingName = selectedMeeting.meetingName; 479 this.form.meetingName = selectedMeeting.meetingName;
335 } else { 480 } else {
336 - this.form.meetingName = ""; // 如果没有找到,清空文本框 481 + this.form.meetingName = "";
337 } 482 }
338 }, 483 },
339 484
@@ -375,7 +520,6 @@ export default { @@ -375,7 +520,6 @@ export default {
375 }) 520 })
376 .catch((error) => { 521 .catch((error) => {
377 console.error("请求失败:", error); 522 console.error("请求失败:", error);
378 - // 处理错误  
379 }); 523 });
380 } 524 }
381 } 525 }
@@ -407,8 +551,70 @@ export default { @@ -407,8 +551,70 @@ export default {
407 }, 551 },
408 `meetingattendance_${new Date().getTime()}.xlsx` 552 `meetingattendance_${new Date().getTime()}.xlsx`
409 ); 553 );
  554 + },
  555 +
  556 + /** 生成参会证按钮操作 */
  557 + handleGenerateBadge() {
  558 + if (this.ids.length === 0) {
  559 + this.$modal.msgWarning("请至少选择一条数据");
  560 + return;
  561 + }
  562 +
  563 + // 构建参数数组,每个元素包含 meetingId 和 attendeeId
  564 + const params = this.meetingattendanceList
  565 + .filter(item => this.ids.includes(item.meetingId) && this.attendIds.includes(item.attendeeId))
  566 + .map(item => ({
  567 + meetingId: item.meetingId,
  568 + attendeeId: item.attendeeId
  569 + }));
  570 +
  571 + this.$modal.confirm('确认要为选中的' + params.length + '条数据生成参会证吗?').then(() => {
  572 + generateBadges(params).then(res => {
  573 + if (res.code === 200) {
  574 + this.$modal.msgSuccess("参会证生成成功");
  575 + } else {
  576 + this.$modal.msgError(res.msg);
410 } 577 }
  578 + }).catch(error => {
  579 + this.$modal.msgError("生成参会证失败:" + error.message);
  580 + });
  581 + }).catch(() => {
411 582
  583 + });
  584 + }
412 }, 585 },
413 }; 586 };
414 </script> 587 </script>
  588 +<style>
  589 +.image-cell {
  590 + display: flex;
  591 + justify-content: center;
  592 + align-items: center;
  593 + height: 60px;
  594 + cursor: pointer;
  595 +}
  596 +
  597 +.table-image {
  598 + width: 50px;
  599 + height: 50px;
  600 + transition: transform 0.3s;
  601 +}
  602 +
  603 +.table-image:hover {
  604 + transform: scale(1.1);
  605 +}
  606 +
  607 +.image-slot {
  608 + display: flex;
  609 + justify-content: center;
  610 + align-items: center;
  611 + width: 100%;
  612 + height: 100%;
  613 + background: #f5f7fa;
  614 + color: #909399;
  615 +}
  616 +
  617 +.no-image {
  618 + color: #999;
  619 +}
  620 +</style>