作者 lixiang

1、增加参会证生成功能

2、增加指定人员参会证下载功能
3、增加批量下载参会证功能
@@ -141,3 +141,25 @@ export function batchUpdateAttendees(data) { @@ -141,3 +141,25 @@ export function batchUpdateAttendees(data) {
141 }) 141 })
142 } 142 }
143 143
  144 +
  145 +export function validateBatchUpdate(data) {
  146 + return request({
  147 + url: '/xvisit/meetingattendance/validateBatchUpdate',
  148 + method: 'post',
  149 + data: data,
  150 + headers: {
  151 + 'Content-Type': 'multipart/form-data'
  152 + }
  153 +
  154 + })
  155 +}
  156 +
  157 +export function exportBatchUpdateTemplate(data) {
  158 + return request({
  159 + url: '/xvisit/meetingattendance/exportBatchUpdateTemplate',
  160 + method: 'post',
  161 + data: data,
  162 + responseType: 'blob'
  163 + })
  164 +}
  165 +
@@ -298,7 +298,27 @@ export default { @@ -298,7 +298,27 @@ export default {
298 download_fail_empty: "ダウンロード内容が空です", 298 download_fail_empty: "ダウンロード内容が空です",
299 download_cancel: "ダウンロードをキャンセルしました", 299 download_cancel: "ダウンロードをキャンセルしました",
300 badge: "参会証", 300 badge: "参会証",
301 - batch_download :"一括ダウンロード" 301 + batch_download :"一括ダウンロード",
  302 + batch_update: "一括修正",
  303 + batch_update_title: "参加者情報の一括修正",
  304 + batch_update_tip: "修正してアップロードしてください。テンプレートのスタイルルールなどの情報は修正しないでください。",
  305 + download_template: "一括修正テンプレートをダウンロード。",
  306 + select_file: "ファイルを選択",
  307 + upload_file: "ファイルをアップロード",
  308 + upload_tip: "xlsx形式のファイルのみアップロード可能",
  309 + select_data_first: "修正するデータを選択してください",
  310 + preparing_download: "ダウンロード準備中...",
  311 + download_failed: "ダウンロード失敗:",
  312 + only_excel: "Excelファイルのみアップロード可能",
  313 + batch_update_success: "一括修正が成功しました",
  314 + batch_update_failed: "一括修正に失敗しました",
  315 + upload_hint: 'ファイルをここにドラッグするか、',
  316 + click_to_select: 'クリックしてファイルを選択',
  317 + batch_update_errors:'一括更新エラーメッセージ',
  318 + error_row:'行番号',
  319 + error_message:'エラー原因',
  320 + confirm:'確認',
  321 + export_errors:'エクスポートエラー情報'
302 }, 322 },
303 tab: { 323 tab: {
304 basic: '基本情報', 324 basic: '基本情報',
@@ -47,6 +47,19 @@ @@ -47,6 +47,19 @@
47 <el-button type="primary" plain icon="el-icon-user" size="mini" @click="openAddAttendeeDialog" 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> 48 v-hasPermi="['xvisit:meetingattendance:add']">{{ $t('meetatt.add_attendees') }}</el-button>
49 </el-col> 49 </el-col>
  50 +
  51 + <el-col :span="1.5">
  52 + <el-button
  53 + type="warning"
  54 + plain
  55 + icon="el-icon-edit"
  56 + size="mini"
  57 + @click="handleBatchUpdate"
  58 + :disabled="multiple"
  59 +
  60 + >{{ $t('meetatt.batch_update') }}</el-button>
  61 + </el-col>
  62 +
50 </el-row> 63 </el-row>
51 64
52 <el-table v-loading="loading" :data="meetingattendanceList" @selection-change="handleSelectionChange"> 65 <el-table v-loading="loading" :data="meetingattendanceList" @selection-change="handleSelectionChange">
@@ -226,6 +239,97 @@ @@ -226,6 +239,97 @@
226 <el-button @click="cancel">{{ $t('meetatt.cancel') }}</el-button> 239 <el-button @click="cancel">{{ $t('meetatt.cancel') }}</el-button>
227 </div> 240 </div>
228 </el-dialog> 241 </el-dialog>
  242 +
  243 +
  244 + <el-dialog
  245 + :title="$t('meetatt.batch_update_title')"
  246 + :visible.sync="batchUpdateDialogVisible"
  247 + width="600px"
  248 + custom-class="batch-update-dialog"
  249 + >
  250 + <div class="dialog-content">
  251 + <div class="download-section">
  252 + <el-alert
  253 + :title="$t('meetatt.batch_update_tip')"
  254 + type="info"
  255 + :closable="false"
  256 + show-icon
  257 + >
  258 + <el-link
  259 + type="primary"
  260 + @click="downloadBatchTemplate"
  261 + class="download-link"
  262 + >
  263 + <i class="el-icon-download"></i> {{ $t('meetatt.download_template') }}
  264 + </el-link>
  265 + </el-alert>
  266 + </div>
  267 +
  268 + <div class="upload-section">
  269 + <el-upload
  270 + class="upload-area"
  271 + drag
  272 + :action="uploadUrl"
  273 + :headers="headers"
  274 + :on-success="handleUploadSuccess"
  275 + :on-error="handleUploadError"
  276 + :before-upload="beforeUpload"
  277 + :file-list="fileList"
  278 + :auto-upload="true"
  279 + :show-file-list="false"
  280 + :limit="1"
  281 + accept=".xlsx"
  282 + >
  283 + <i class="el-icon-upload"></i>
  284 + <div class="el-upload__text">
  285 + {{ $t('meetatt.upload_hint') }}<br>
  286 + <em>{{ $t('meetatt.click_to_select') }}</em>
  287 + </div>
  288 + </el-upload>
  289 + </div>
  290 +
  291 + <div v-if="uploadStatus === 'uploading'" class="upload-progress">
  292 + <el-progress
  293 + :percentage="uploadPercentage"
  294 + :status="uploadProgressStatus"
  295 + :stroke-width="6"
  296 + ></el-progress>
  297 + </div>
  298 + </div>
  299 +
  300 + <div slot="footer" class="dialog-footer">
  301 + <el-button @click="batchUpdateDialogVisible = false">
  302 + {{ $t('meetatt.cancel') }}
  303 + </el-button>
  304 + </div>
  305 + </el-dialog>
  306 +
  307 +
  308 + <el-dialog
  309 + :title="$t('meetatt.batch_update_errors')"
  310 + :visible.sync="showErrorDialog"
  311 + width="70%"
  312 + >
  313 + <el-table :data="errorData" border style="width: 100%">
  314 + <el-table-column
  315 + :label="$t('meetatt.error_row')"
  316 + prop="row"
  317 + width="100"
  318 + />
  319 + <el-table-column
  320 + :label="$t('meetatt.error_message')"
  321 + prop="message"
  322 + />
  323 + </el-table>
  324 + <div slot="footer" class="dialog-footer">
  325 + <el-button type="primary" @click="showErrorDialog = false">
  326 + {{ $t('meetatt.confirm') }}
  327 + </el-button>
  328 + <el-button @click="exportErrorData">
  329 + {{ $t('meetatt.export_errors') }}
  330 + </el-button>
  331 + </div>
  332 + </el-dialog>
229 </div> 333 </div>
230 </template> 334 </template>
231 335
@@ -243,7 +347,8 @@ import { @@ -243,7 +347,8 @@ import {
243 getAttendeesForMeeting, 347 getAttendeesForMeeting,
244 batchAddAttendees, 348 batchAddAttendees,
245 getAttendeesStatus, 349 getAttendeesStatus,
246 - batchUpdateAttendees 350 + batchUpdateAttendees,
  351 + exportBatchUpdateTemplate
247 } from "@/api/xvisit/meetingattendance"; 352 } from "@/api/xvisit/meetingattendance";
248 353
249 import { listMeeting } from "@/api/xvisit/meeting"; 354 import { listMeeting } from "@/api/xvisit/meeting";
@@ -253,6 +358,17 @@ export default { @@ -253,6 +358,17 @@ export default {
253 name: "Meetingattendance", 358 name: "Meetingattendance",
254 data() { 359 data() {
255 return { 360 return {
  361 + errorData: [], // 存储错误信息
  362 + showErrorDialog: false, // 控制错误对话框显示
  363 + uploadStatus: '', // uploading, success, error
  364 + uploadPercentage: 0,
  365 + uploadProgressStatus: '',
  366 + batchUpdateDialogVisible: false,
  367 + fileList: [],
  368 + uploadUrl: "/dev-api/xvisit/meetingattendance/importBatchUpdateData",
  369 + headers: {
  370 + Authorization: "Bearer " + this.$store.state.user.token
  371 + },
256 addAttendeeDialogVisible: false, 372 addAttendeeDialogVisible: false,
257 selectedMeeting: null, 373 selectedMeeting: null,
258 meetingOptions: [], 374 meetingOptions: [],
@@ -350,6 +466,137 @@ export default { @@ -350,6 +466,137 @@ export default {
350 }, 466 },
351 467
352 methods: { 468 methods: {
  469 + exportErrorData() {
  470 + // 创建 CSV 内容
  471 + let csvContent = "行番号、エラーの原因\n";
  472 + this.errorData.forEach(item => {
  473 + csvContent += `${item.row},${item.message}\n`;
  474 + });
  475 +
  476 + // 创建 Blob 对象
  477 + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
  478 +
  479 + // 创建下载链接
  480 + const link = document.createElement('a');
  481 + const url = URL.createObjectURL(blob);
  482 + link.href = url;
  483 + link.setAttribute('download', '一括更新エラー情報.csv');
  484 + document.body.appendChild(link);
  485 +
  486 + // 触发下载
  487 + link.click();
  488 +
  489 + // 清理
  490 + document.body.removeChild(link);
  491 + URL.revokeObjectURL(url);
  492 + },
  493 + // 批量修改按钮点击事件
  494 + handleBatchUpdate() {
  495 + if (this.ids.length === 0) {
  496 + this.$modal.msgWarning(this.$t('meetatt.select_data_first'));
  497 + return;
  498 + }
  499 + this.batchUpdateDialogVisible = true;
  500 + this.resetUploadStatus();
  501 + },
  502 +
  503 + // 重置上传状态
  504 + resetUploadStatus() {
  505 + this.uploadStatus = '';
  506 + this.uploadPercentage = 0;
  507 + this.uploadProgressStatus = '';
  508 + },
  509 +
  510 + // 下载批量修改模板
  511 + downloadBatchTemplate() {
  512 + const params = {
  513 + meetingIds: this.ids,
  514 + attendeeIds: this.attendIds
  515 + };
  516 +
  517 + this.$modal.loading(this.$t('meetatt.preparing_download'));
  518 +
  519 + exportBatchUpdateTemplate(params).then(response => {
  520 + const url = window.URL.createObjectURL(new Blob([response]));
  521 + const link = document.createElement('a');
  522 + link.href = url;
  523 + link.setAttribute('download', `batch_update_template_${new Date().getTime()}.xlsx`);
  524 + document.body.appendChild(link);
  525 + link.click();
  526 + document.body.removeChild(link);
  527 + window.URL.revokeObjectURL(url);
  528 + this.$modal.closeLoading();
  529 + }).catch(error => {
  530 + this.$modal.closeLoading();
  531 + this.$modal.msgError(this.$t('meetatt.download_failed') + (error.message || error));
  532 + });
  533 + },
  534 +
  535 + // 上传前校验
  536 + beforeUpload(file) {
  537 + const isExcel = file.name.endsWith('.xlsx') || file.name.endsWith('.xls');
  538 + if (!isExcel) {
  539 + this.$modal.msgError(this.$t('meetatt.only_excel'));
  540 + return false;
  541 + }
  542 +
  543 + // 重置上传状态
  544 + this.resetUploadStatus();
  545 + this.uploadStatus = 'uploading';
  546 + this.uploadProgressStatus = '';
  547 +
  548 + // 模拟上传进度
  549 + const interval = setInterval(() => {
  550 + if (this.uploadPercentage >= 90) {
  551 + clearInterval(interval);
  552 + return;
  553 + }
  554 + this.uploadPercentage += 10;
  555 + }, 200);
  556 +
  557 + return true;
  558 + },
  559 +
  560 + // 上传成功处理
  561 + handleUploadSuccess(response, file, fileList) {
  562 + this.uploadPercentage = 100;
  563 + this.uploadProgressStatus = 'success';
  564 + this.uploadStatus = 'success';
  565 +
  566 + if (response.code === 200) {
  567 + // 成功处理
  568 + if (response.data && response.data.errors && response.data.errors.length > 0) {
  569 + // 部分成功,有错误数据
  570 + this.errorData = response.data.errors;
  571 + this.showErrorDialog = true;
  572 + this.$modal.msgWarning(`成功更新 ${response.data.successCount} 条数据,但有 ${response.data.errors.length} 条数据存在问题`);
  573 + } else {
  574 + // 全部成功
  575 + this.$modal.msgSuccess(response.msg || this.$t('meetatt.batch_update_success'));
  576 + this.batchUpdateDialogVisible = false;
  577 + }
  578 + this.getList(); // 刷新列表
  579 + } else {
  580 + // 处理失败
  581 + this.$modal.msgError(response.msg || this.$t('meetatt.batch_update_failed'));
  582 + }
  583 + this.fileList = [];
  584 + },
  585 +
  586 + // 上传失败处理
  587 + handleUploadError(err, file, fileList) {
  588 + this.uploadPercentage = 100;
  589 + this.uploadProgressStatus = 'exception';
  590 + this.uploadStatus = 'error';
  591 +
  592 + let errorMsg = this.$t('meetatt.batch_update_failed');
  593 + if (err.message) {
  594 + errorMsg += ': ' + err.message;
  595 + }
  596 + this.$modal.msgError(errorMsg);
  597 + },
  598 +
  599 +
353 600
354 // 打开添加参会人员对话框 601 // 打开添加参会人员对话框
355 openAddAttendeeDialog() { 602 openAddAttendeeDialog() {
@@ -454,7 +701,7 @@ export default { @@ -454,7 +701,7 @@ export default {
454 removeAttendeeIds: toRemoveString 701 removeAttendeeIds: toRemoveString
455 }).then(response => { 702 }).then(response => {
456 if (response.code === 200) { 703 if (response.code === 200) {
457 - this.$modal.msgSuccess('参会人员更新成功'); 704 + this.$modal.msgSuccess('参加者の更新に成功しました');
458 this.addAttendeeDialogVisible = false; 705 this.addAttendeeDialogVisible = false;
459 this.getList(); // 刷新列表 706 this.getList(); // 刷新列表
460 } 707 }
@@ -749,16 +996,16 @@ export default { @@ -749,16 +996,16 @@ export default {
749 attendeeId: item.attendeeId 996 attendeeId: item.attendeeId
750 })); 997 }));
751 998
752 - this.$modal.confirm('确认要为选中的' + params.length + '条数据生成参会证吗?').then(() => { 999 + this.$modal.confirm('選択されていることを確認' + params.length + '条データ生成参会証か?').then(() => {
753 generateBadges(params).then(res => { 1000 generateBadges(params).then(res => {
754 if (res.code === 200) { 1001 if (res.code === 200) {
755 - this.$modal.msgSuccess("参会证生成成功"); 1002 + this.$modal.msgSuccess("参会証の生成に成功しました");
756 this.getList(); 1003 this.getList();
757 } else { 1004 } else {
758 this.$modal.msgError(res.msg); 1005 this.$modal.msgError(res.msg);
759 } 1006 }
760 }).catch(error => { 1007 }).catch(error => {
761 - this.$modal.msgError("生成参会证失败:" + error.message); 1008 + this.$modal.msgError("参会証の生成に失敗しました:" + error.message);
762 }); 1009 });
763 }).catch(() => { 1010 }).catch(() => {
764 1011
@@ -885,4 +1132,49 @@ export default { @@ -885,4 +1132,49 @@ export default {
885 padding: 8px 10px; 1132 padding: 8px 10px;
886 border-bottom: 1px solid #f0f0f0; 1133 border-bottom: 1px solid #f0f0f0;
887 } 1134 }
  1135 +
  1136 + .batch-update-dialog {
  1137 + border-radius: 8px;
  1138 + }
  1139 +
  1140 +.dialog-content {
  1141 + padding: 0 20px;
  1142 +}
  1143 +
  1144 +.download-section {
  1145 + margin-bottom: 20px;
  1146 +}
  1147 +
  1148 +.download-link {
  1149 + margin-left: 10px;
  1150 + font-size: 14px;
  1151 + vertical-align: middle;
  1152 +}
  1153 +
  1154 +.upload-section {
  1155 + margin: 20px 0;
  1156 +}
  1157 +
  1158 +.upload-area {
  1159 + border: 1px dashed #d9d9d9;
  1160 + border-radius: 6px;
  1161 + padding: 40px 0;
  1162 + text-align: center;
  1163 + background-color: #fafafa;
  1164 + transition: border-color 0.3s;
  1165 +}
  1166 +
  1167 +.upload-area:hover {
  1168 + border-color: #409EFF;
  1169 +}
  1170 +
  1171 +.upload-progress {
  1172 + margin-top: 20px;
  1173 +}
  1174 +
  1175 +.dialog-footer {
  1176 + text-align: right;
  1177 + padding: 10px 20px 0;
  1178 + border-top: 1px solid #e8e8e8;
  1179 +}
888 </style> 1180 </style>