作者 lixiang

1、增加参会证生成功能

2、增加指定人员参会证下载功能
3、增加批量下载参会证功能
1 import request from '@/utils/request' 1 import request from '@/utils/request'
2 2
  3 +
  4 +
3 // 查询签到状态列表 5 // 查询签到状态列表
4 export function listMeetingattendance(query) { 6 export function listMeetingattendance(query) {
5 return request({ 7 return request({
@@ -104,3 +106,38 @@ export function downloadBadgeBatch(params) { @@ -104,3 +106,38 @@ export function downloadBadgeBatch(params) {
104 } 106 }
105 }); 107 });
106 } 108 }
  109 +
  110 +// 获取参会/未参会人员列表
  111 +export function getAttendeesForMeeting(meetingId, isAttending) {
  112 + return request({
  113 + url: '/xvisit/meetingattendance/getAttendeesForMeeting',
  114 + method: 'get',
  115 + params: { meetingId, isAttending }
  116 + })
  117 +}
  118 +
  119 +// 批量添加参会人员
  120 +export function batchAddAttendees(data) {
  121 + return request({
  122 + url: '/xvisit/meetingattendance/batchAddAttendees',
  123 + method: 'post',
  124 + data: data
  125 + })
  126 +}
  127 +
  128 +export function getAttendeesStatus(meetingId) {
  129 + return request({
  130 + url: '/xvisit/meetingattendance/getAttendeesStatus',
  131 + method: 'get',
  132 + params: { meetingId }
  133 + })
  134 +}
  135 +
  136 +export function batchUpdateAttendees(data) {
  137 + return request({
  138 + url: '/xvisit/meetingattendance/batchUpdateAttendees',
  139 + method: 'post',
  140 + data: data
  141 + })
  142 +}
  143 +
@@ -227,7 +227,11 @@ export default { @@ -227,7 +227,11 @@ export default {
227 227
228 }, 228 },
229 meetatt: { 229 meetatt: {
230 - 230 + select_meeting: 'ミーティングの選択',
  231 + participants: '参会者',
  232 + participants_title: ['参加者', '未参加者'],
  233 + name: '名前',
  234 + add_attendees: '参加者を追加',
231 query: '検索', 235 query: '検索',
232 reset: 'リセット', 236 reset: 'リセット',
233 add: '追加', 237 add: '追加',
@@ -238,24 +242,19 @@ export default { @@ -238,24 +242,19 @@ export default {
238 del: '削除', 242 del: '削除',
239 cancel: 'キャンセル', 243 cancel: 'キャンセル',
240 submit: '確認', 244 submit: '確認',
241 -  
242 add_title: '訪問者の追加', 245 add_title: '訪問者の追加',
243 edit_title: '訪問者の変更', 246 edit_title: '訪問者の変更',
244 import_title: 'X', 247 import_title: 'X',
245 -  
246 sign_sts_0: '署名なし', 248 sign_sts_0: '署名なし',
247 sign_sts_1: '署名あり', 249 sign_sts_1: '署名あり',
248 -  
249 pay_sts_0: '未払い', 250 pay_sts_0: '未払い',
250 pay_sts_1: '払い済', 251 pay_sts_1: '払い済',
251 -  
252 add_msg: '追加サクセス', 252 add_msg: '追加サクセス',
253 edit_msg: '修正サクセス', 253 edit_msg: '修正サクセス',
254 import_msg: 'インポート結果', 254 import_msg: 'インポート結果',
255 del_msg: '削除サクセス', 255 del_msg: '削除サクセス',
256 del_msgInfo: '削除しますか?', 256 del_msgInfo: '削除しますか?',
257 add_msg_NG: '追加に失敗、メンバーIDは既に存在', 257 add_msg_NG: '追加に失敗、メンバーIDは既に存在',
258 -  
259 attendee_id: '個人ID', 258 attendee_id: '個人ID',
260 attendee_name: '個人', 259 attendee_name: '個人',
261 meeting_id: '会議ID', 260 meeting_id: '会議ID',
@@ -284,21 +283,21 @@ export default { @@ -284,21 +283,21 @@ export default {
284 memo_P: 'X', 283 memo_P: 'X',
285 del_flag_P: 'X', 284 del_flag_P: 'X',
286 code_P: 'QRコードを入力してください', 285 code_P: 'QRコードを入力してください',
287 - zh_pass: "中文参会证",  
288 - ja_pass: "日文参会证", 286 + zh_pass: "中国語参会証",
  287 + ja_pass: "日本語参会証",
289 attendee_id_R: '個人IDは空白にできない', 288 attendee_id_R: '個人IDは空白にできない',
290 meeting_id_R: '会議IDを空にすることはできません', 289 meeting_id_R: '会議IDを空にすることはできません',
291 290
292 - generate_badge:'参加証を生成',  
293 - download: "参加証をダウンロード", 291 + generate_badge:'参会証を生成',
  292 + download: "参会証をダウンロード",
294 download_zh: "中国語版", 293 download_zh: "中国語版",
295 download_ja: "日本語版", 294 download_ja: "日本語版",
296 - download_confirm: "参加証をダウンロードしますか?",  
297 - download_success: "参加証のダウンロードが成功しました", 295 + download_confirm: "参会証をダウンロードしますか?",
  296 + download_success: "参会証のダウンロードが成功しました",
298 download_fail: "ダウンロード失敗:", 297 download_fail: "ダウンロード失敗:",
299 download_fail_empty: "ダウンロード内容が空です", 298 download_fail_empty: "ダウンロード内容が空です",
300 download_cancel: "ダウンロードをキャンセルしました", 299 download_cancel: "ダウンロードをキャンセルしました",
301 - badge: "参証", 300 + badge: "参証",
302 batch_download :"一括ダウンロード" 301 batch_download :"一括ダウンロード"
303 }, 302 },
304 tab: { 303 tab: {
@@ -43,6 +43,10 @@ @@ -43,6 +43,10 @@
43 <el-button type="info" plain icon="el-icon-download" size="mini" @click="handleBatchDownload" 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> 44 :disabled="multiple" v-hasPermi="['xvisit:meetingattendance:downloadZip']">{{ $t('meetatt.batch_download') }}</el-button>
45 </el-col> 45 </el-col>
  46 + <el-col :span="1.5">
  47 + <el-button type="primary" plain icon="el-icon-user" size="mini" @click="openAddAttendeeDialog"
  48 + v-hasPermi="['xvisit:meetingattendance:add']">{{ $t('meetatt.add_attendees') }}</el-button>
  49 + </el-col>
46 </el-row> 50 </el-row>
47 51
48 <el-table v-loading="loading" :data="meetingattendanceList" @selection-change="handleSelectionChange"> 52 <el-table v-loading="loading" :data="meetingattendanceList" @selection-change="handleSelectionChange">
@@ -62,6 +66,7 @@ @@ -62,6 +66,7 @@
62 <template #default="scope"> 66 <template #default="scope">
63 <el-tag v-if="scope.row.paySts == '0'">{{ $t('meetatt.pay_sts_0') }}</el-tag> 67 <el-tag v-if="scope.row.paySts == '0'">{{ $t('meetatt.pay_sts_0') }}</el-tag>
64 <el-tag v-else-if="scope.row.paySts == '1'">{{ $t('meetatt.pay_sts_1') }}</el-tag> 68 <el-tag v-else-if="scope.row.paySts == '1'">{{ $t('meetatt.pay_sts_1') }}</el-tag>
  69 + <el-tag v-else>{{ $t('meetatt.pay_sts_0') }}</el-tag>
65 </template> 70 </template>
66 </el-table-column> 71 </el-table-column>
67 <el-table-column :label="$t('meetatt.code')" align="center" prop="attendeeCode" /> 72 <el-table-column :label="$t('meetatt.code')" align="center" prop="attendeeCode" />
@@ -119,7 +124,9 @@ @@ -119,7 +124,9 @@
119 v-hasPermi="['xvisit:meetingattendance:edit']">{{ $t('meetatt.edit') }}</el-button> 124 v-hasPermi="['xvisit:meetingattendance:edit']">{{ $t('meetatt.edit') }}</el-button>
120 <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" 125 <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
121 v-hasPermi="['xvisit:meetingattendance:remove']">{{ $t('meetatt.del') }}</el-button> 126 v-hasPermi="['xvisit:meetingattendance:remove']">{{ $t('meetatt.del') }}</el-button>
122 - <el-button size="mini" type="text" icon="el-icon-download" @click="handleDownload(scope.row)" 127 + <el-button
  128 + v-if="scope.row.conferencePassZhBase64 || scope.row.conferencePassJaBase64"
  129 + size="mini" type="text" icon="el-icon-download" @click="handleDownload(scope.row)"
123 v-hasPermi="['xvisit:meetingattendance:download']">{{ $t('meetatt.download') }}</el-button> 130 v-hasPermi="['xvisit:meetingattendance:download']">{{ $t('meetatt.download') }}</el-button>
124 </template> 131 </template>
125 </el-table-column> 132 </el-table-column>
@@ -128,6 +135,47 @@ @@ -128,6 +135,47 @@
128 <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" 135 <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
129 @pagination="getList" /> 136 @pagination="getList" />
130 137
  138 + <!-- 添加参会人员对话框 -->
  139 + <el-dialog :title="$t('meetatt.add_attendees')" :visible.sync="addAttendeeDialogVisible" width="800px">
  140 + <el-form :model="form" label-width="100px">
  141 + <el-form-item :label="$t('meetatt.select_meeting')" prop="meetingId">
  142 + <el-select
  143 + v-model="selectedMeeting"
  144 + :placeholder="$t('meetatt.select_meeting')"
  145 + @change="handleMeetingChange"
  146 + style="width: 100%"
  147 + >
  148 + <el-option
  149 + v-for="item in meetingOptions"
  150 + :key="item.value"
  151 + :label="item.label"
  152 + :value="item.value">
  153 + </el-option>
  154 + </el-select>
  155 + </el-form-item>
  156 +
  157 + <el-form-item :label="$t('meetatt.participants')">
  158 + <el-transfer
  159 + v-model="selectedAttendees"
  160 + :data="transferData"
  161 + :titles="$t('meetatt.participants_title')"
  162 + :props="{
  163 + key: 'attendeeId',
  164 + label: 'name'
  165 + }"
  166 + filterable
  167 + :filter-placeholder="$t('meetatt.name')"
  168 + @change="handleTransferChange"
  169 + />
  170 + </el-form-item>
  171 + </el-form>
  172 +
  173 + <div slot="footer" class="dialog-footer">
  174 + <el-button type="primary" @click="submitAddAttendees">{{ $t('meetatt.submit') }}</el-button>
  175 + <el-button @click="addAttendeeDialogVisible = false">{{ $t('meetatt.cancel') }}</el-button>
  176 + </div>
  177 + </el-dialog>
  178 +
131 <!-- 添加会议状态对话框 --> 179 <!-- 添加会议状态对话框 -->
132 <el-dialog :title="title" :visible.sync="open" width="400px" append-to-body> 180 <el-dialog :title="title" :visible.sync="open" width="400px" append-to-body>
133 <el-form ref="form" :model="form" :rules="rules" label-width="80px"> 181 <el-form ref="form" :model="form" :rules="rules" label-width="80px">
@@ -191,7 +239,11 @@ import { @@ -191,7 +239,11 @@ import {
191 checkDataExists, 239 checkDataExists,
192 generateBadges, 240 generateBadges,
193 downloadBadge, 241 downloadBadge,
194 - downloadBadgeBatch 242 + downloadBadgeBatch,
  243 + getAttendeesForMeeting,
  244 + batchAddAttendees,
  245 + getAttendeesStatus,
  246 + batchUpdateAttendees
195 } from "@/api/xvisit/meetingattendance"; 247 } from "@/api/xvisit/meetingattendance";
196 248
197 import { listMeeting } from "@/api/xvisit/meeting"; 249 import { listMeeting } from "@/api/xvisit/meeting";
@@ -201,6 +253,23 @@ export default { @@ -201,6 +253,23 @@ export default {
201 name: "Meetingattendance", 253 name: "Meetingattendance",
202 data() { 254 data() {
203 return { 255 return {
  256 + addAttendeeDialogVisible: false,
  257 + selectedMeeting: null,
  258 + meetingOptions: [],
  259 + // 穿梭框数据
  260 + selectedAttendees: [], // 选中的参会人员ID数组
  261 + attendingAttendees: [], // 已参会人员
  262 + nonAttendingAttendees: [], // 未参会人员
  263 + transferData: [] ,
  264 +
  265 + leftAttendees: [], // 左侧:已参会人员(attending)
  266 + rightAttendees: [], // 右侧:未参会人员(nonAttending)
  267 + allAttendees: [], // 所有人员数据(供穿梭框内部使用)
  268 + transferProps: {
  269 + key: 'attendeeId',
  270 + label: 'name',
  271 + disabled: 'disabled'
  272 + },
204 // 遮罩层 273 // 遮罩层
205 loading: true, 274 loading: true,
206 // 选中数组 275 // 选中数组
@@ -268,6 +337,8 @@ export default { @@ -268,6 +337,8 @@ export default {
268 }, 337 },
269 //add 0 upd 1 338 //add 0 upd 1
270 sts: 0, 339 sts: 0,
  340 + toAdd:[],
  341 + toRemove:[]
271 }; 342 };
272 }, 343 },
273 344
@@ -279,6 +350,116 @@ export default { @@ -279,6 +350,116 @@ export default {
279 }, 350 },
280 351
281 methods: { 352 methods: {
  353 +
  354 + // 打开添加参会人员对话框
  355 + openAddAttendeeDialog() {
  356 + this.toAdd = [];
  357 + this.toRemove = [];
  358 + this.addAttendeeDialogVisible = true;
  359 + this.selectedMeeting = null;
  360 + this.selectedAttendees = [];
  361 + this.attendingAttendees = [];
  362 + this.nonAttendingAttendees = [];
  363 + this.transferData = [];
  364 +
  365 + // 加载会议列表
  366 + listMeeting({ pageNum: 1, pageSize: 1000 }).then(response => {
  367 + this.meetingOptions = response.rows.map(item => ({
  368 + value: item.meetingId,
  369 + label: item.meetingName
  370 + }));
  371 + });
  372 + },
  373 +
  374 + // 会议选择变化时触发
  375 + handleMeetingChange(meetingId) {
  376 + if (!meetingId) {
  377 + this.attendingAttendees = [];
  378 + this.nonAttendingAttendees = [];
  379 + this.transferData = [];
  380 + this.selectedAttendees = [];
  381 + return;
  382 + }
  383 +
  384 + // 获取参会人员状态
  385 + getAttendeesStatus(meetingId).then(response => {
  386 + if (response.code === 200) {
  387 + // 已参会人员
  388 + this.attendingAttendees = response.data.attending || [];
  389 + // 未参会人员
  390 + this.nonAttendingAttendees = response.data.nonAttending || [];
  391 +
  392 + // 更新穿梭框数据:未参会人员在前,已参会人员在后
  393 + this.transferData = [
  394 + ...this.nonAttendingAttendees,
  395 + ...this.attendingAttendees
  396 + ];
  397 +
  398 + // 默认选中未参会人员
  399 + this.selectedAttendees = this.nonAttendingAttendees.map(item => item.attendeeId);
  400 + }
  401 + });
  402 + },
  403 + // 穿梭框数据变化时触发
  404 + handleTransferChange(newSelectedAttendees, direction, movedKeys) {
  405 + //新增
  406 + if (direction === 'left'){
  407 + if (this.toRemove.includes(movedKeys)) {
  408 + const index = this.toRemove.indexOf(movedKeys);
  409 + if (index !== -1) {
  410 + this.toRemove.splice(index, 1); // 删除该元素
  411 + }
  412 + }
  413 + this.toAdd.push(movedKeys)
  414 + }
  415 + //删除
  416 + if (direction === 'right'){
  417 + if (this.toAdd.includes(movedKeys)) {
  418 + const index = this.toAdd.indexOf(movedKeys);
  419 + if (index !== -1) {
  420 + this.toAdd.splice(index, 1); // 删除该元素
  421 + }
  422 + }
  423 + this.toRemove.push(movedKeys)
  424 + }
  425 +
  426 +
  427 + // 不需要额外处理,selectedAttendees会自动更新
  428 + },
  429 +
  430 + // 提交添加参会人员
  431 + submitAddAttendees() {
  432 + if (!this.selectedMeeting) {
  433 + this.$modal.msgError('请选择会议');
  434 + return;
  435 + }
  436 +
  437 + console.log(" this.toAdd", this.toAdd)
  438 + console.log(" this.toRemove", this.toRemove)
  439 +
  440 +
  441 + // 将数组转换为字符串
  442 + const toAddString = this.toAdd.join(',');
  443 + const toRemoveString = this.toRemove.join(',');
  444 +
  445 + if (!toAddString && !toRemoveString) {
  446 + this.$modal.msgWarning('没有变更的参会人员');
  447 + return;
  448 + }
  449 + console.log("toRemoveString",toRemoveString)
  450 + // 调用API批量更新
  451 + batchUpdateAttendees({
  452 + meetingId: this.selectedMeeting,
  453 + addAttendeeIds: toAddString,
  454 + removeAttendeeIds: toRemoveString
  455 + }).then(response => {
  456 + if (response.code === 200) {
  457 + this.$modal.msgSuccess('参会人员更新成功');
  458 + this.addAttendeeDialogVisible = false;
  459 + this.getList(); // 刷新列表
  460 + }
  461 + });
  462 + },
282 /** 批量下载参会证按钮操作 */ 463 /** 批量下载参会证按钮操作 */
283 handleBatchDownload() { 464 handleBatchDownload() {
284 if (this.ids.length === 0) { 465 if (this.ids.length === 0) {
@@ -572,6 +753,7 @@ export default { @@ -572,6 +753,7 @@ export default {
572 generateBadges(params).then(res => { 753 generateBadges(params).then(res => {
573 if (res.code === 200) { 754 if (res.code === 200) {
574 this.$modal.msgSuccess("参会证生成成功"); 755 this.$modal.msgSuccess("参会证生成成功");
  756 + this.getList();
575 } else { 757 } else {
576 this.$modal.msgError(res.msg); 758 this.$modal.msgError(res.msg);
577 } 759 }
@@ -617,4 +799,90 @@ export default { @@ -617,4 +799,90 @@ export default {
617 .no-image { 799 .no-image {
618 color: #999; 800 color: #999;
619 } 801 }
  802 +
  803 +.transfer-footer {
  804 + padding: 6px 12px;
  805 + text-align: center;
  806 + color: #666;
  807 + border-top: 1px solid #ebeef5;
  808 +}
  809 +
  810 +
  811 +.transfer-panel__header {
  812 + padding: 10px;
  813 + background-color: #f5f7fa;
  814 + border-bottom: 1px solid #ebeef5;
  815 + font-weight: bold;
  816 +}
  817 +
  818 +
  819 +
  820 +.transfer-item {
  821 + padding: 8px 10px;
  822 + border-bottom: 1px solid #f0f0f0;
  823 + cursor: pointer;
  824 +}
  825 +
  826 +.transfer-item:hover {
  827 + background-color: #f5f7fa;
  828 +}
  829 +
  830 +
  831 +
  832 +.transfer-panel__header {
  833 + padding: 10px;
  834 + background-color: #f5f7fa;
  835 + border-bottom: 1px solid #ebeef5;
  836 + font-weight: bold;
  837 +}
  838 +
  839 +
  840 +
  841 +.transfer-item {
  842 + padding: 8px 10px;
  843 + border-bottom: 1px solid #f0f0f0;
  844 + cursor: pointer;
  845 +}
  846 +
  847 +.transfer-item:hover {
  848 + background-color: #f5f7fa;
  849 +}
  850 +.transfer-panel {
  851 + height: 400px;
  852 + display: flex;
  853 + flex-direction: column;
  854 +}
  855 +
  856 +.transfer-panel__body {
  857 + flex: 1;
  858 + overflow-y: auto;
  859 + padding: 10px;
  860 +}
  861 +
  862 +.transfer-panel {
  863 + border: 1px solid #ebeef5;
  864 + border-radius: 4px;
  865 + overflow: hidden;
  866 + height: 400px;
  867 + display: flex;
  868 + flex-direction: column;
  869 +}
  870 +
  871 +.transfer-panel__header {
  872 + padding: 10px;
  873 + background-color: #f5f7fa;
  874 + border-bottom: 1px solid #ebeef5;
  875 + font-weight: bold;
  876 +}
  877 +
  878 +.transfer-panel__body {
  879 + flex: 1;
  880 + overflow-y: auto;
  881 + padding: 10px;
  882 +}
  883 +
  884 +.transfer-item {
  885 + padding: 8px 10px;
  886 + border-bottom: 1px solid #f0f0f0;
  887 +}
620 </style> 888 </style>