|
...
|
...
|
@@ -47,6 +47,19 @@ |
|
|
|
<el-button type="primary" plain icon="el-icon-user" size="mini" @click="openAddAttendeeDialog"
|
|
|
|
v-hasPermi="['xvisit:meetingattendance:add']">{{ $t('meetatt.add_attendees') }}</el-button>
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
<el-col :span="1.5">
|
|
|
|
<el-button
|
|
|
|
type="warning"
|
|
|
|
plain
|
|
|
|
icon="el-icon-edit"
|
|
|
|
size="mini"
|
|
|
|
@click="handleBatchUpdate"
|
|
|
|
:disabled="multiple"
|
|
|
|
|
|
|
|
>{{ $t('meetatt.batch_update') }}</el-button>
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
<el-table v-loading="loading" :data="meetingattendanceList" @selection-change="handleSelectionChange">
|
|
...
|
...
|
@@ -226,6 +239,97 @@ |
|
|
|
<el-button @click="cancel">{{ $t('meetatt.cancel') }}</el-button>
|
|
|
|
</div>
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
|
|
<el-dialog
|
|
|
|
:title="$t('meetatt.batch_update_title')"
|
|
|
|
:visible.sync="batchUpdateDialogVisible"
|
|
|
|
width="600px"
|
|
|
|
custom-class="batch-update-dialog"
|
|
|
|
>
|
|
|
|
<div class="dialog-content">
|
|
|
|
<div class="download-section">
|
|
|
|
<el-alert
|
|
|
|
:title="$t('meetatt.batch_update_tip')"
|
|
|
|
type="info"
|
|
|
|
:closable="false"
|
|
|
|
show-icon
|
|
|
|
>
|
|
|
|
<el-link
|
|
|
|
type="primary"
|
|
|
|
@click="downloadBatchTemplate"
|
|
|
|
class="download-link"
|
|
|
|
>
|
|
|
|
<i class="el-icon-download"></i> {{ $t('meetatt.download_template') }}
|
|
|
|
</el-link>
|
|
|
|
</el-alert>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="upload-section">
|
|
|
|
<el-upload
|
|
|
|
class="upload-area"
|
|
|
|
drag
|
|
|
|
:action="uploadUrl"
|
|
|
|
:headers="headers"
|
|
|
|
:on-success="handleUploadSuccess"
|
|
|
|
:on-error="handleUploadError"
|
|
|
|
:before-upload="beforeUpload"
|
|
|
|
:file-list="fileList"
|
|
|
|
:auto-upload="true"
|
|
|
|
:show-file-list="false"
|
|
|
|
:limit="1"
|
|
|
|
accept=".xlsx"
|
|
|
|
>
|
|
|
|
<i class="el-icon-upload"></i>
|
|
|
|
<div class="el-upload__text">
|
|
|
|
{{ $t('meetatt.upload_hint') }}<br>
|
|
|
|
<em>{{ $t('meetatt.click_to_select') }}</em>
|
|
|
|
</div>
|
|
|
|
</el-upload>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div v-if="uploadStatus === 'uploading'" class="upload-progress">
|
|
|
|
<el-progress
|
|
|
|
:percentage="uploadPercentage"
|
|
|
|
:status="uploadProgressStatus"
|
|
|
|
:stroke-width="6"
|
|
|
|
></el-progress>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div slot="footer" class="dialog-footer">
|
|
|
|
<el-button @click="batchUpdateDialogVisible = false">
|
|
|
|
{{ $t('meetatt.cancel') }}
|
|
|
|
</el-button>
|
|
|
|
</div>
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
|
|
<el-dialog
|
|
|
|
:title="$t('meetatt.batch_update_errors')"
|
|
|
|
:visible.sync="showErrorDialog"
|
|
|
|
width="70%"
|
|
|
|
>
|
|
|
|
<el-table :data="errorData" border style="width: 100%">
|
|
|
|
<el-table-column
|
|
|
|
:label="$t('meetatt.error_row')"
|
|
|
|
prop="row"
|
|
|
|
width="100"
|
|
|
|
/>
|
|
|
|
<el-table-column
|
|
|
|
:label="$t('meetatt.error_message')"
|
|
|
|
prop="message"
|
|
|
|
/>
|
|
|
|
</el-table>
|
|
|
|
<div slot="footer" class="dialog-footer">
|
|
|
|
<el-button type="primary" @click="showErrorDialog = false">
|
|
|
|
{{ $t('meetatt.confirm') }}
|
|
|
|
</el-button>
|
|
|
|
<el-button @click="exportErrorData">
|
|
|
|
{{ $t('meetatt.export_errors') }}
|
|
|
|
</el-button>
|
|
|
|
</div>
|
|
|
|
</el-dialog>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
...
|
...
|
@@ -243,7 +347,8 @@ import { |
|
|
|
getAttendeesForMeeting,
|
|
|
|
batchAddAttendees,
|
|
|
|
getAttendeesStatus,
|
|
|
|
batchUpdateAttendees
|
|
|
|
batchUpdateAttendees,
|
|
|
|
exportBatchUpdateTemplate
|
|
|
|
} from "@/api/xvisit/meetingattendance";
|
|
|
|
|
|
|
|
import { listMeeting } from "@/api/xvisit/meeting";
|
|
...
|
...
|
@@ -253,6 +358,17 @@ export default { |
|
|
|
name: "Meetingattendance",
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
errorData: [], // 存储错误信息
|
|
|
|
showErrorDialog: false, // 控制错误对话框显示
|
|
|
|
uploadStatus: '', // uploading, success, error
|
|
|
|
uploadPercentage: 0,
|
|
|
|
uploadProgressStatus: '',
|
|
|
|
batchUpdateDialogVisible: false,
|
|
|
|
fileList: [],
|
|
|
|
uploadUrl: "/dev-api/xvisit/meetingattendance/importBatchUpdateData",
|
|
|
|
headers: {
|
|
|
|
Authorization: "Bearer " + this.$store.state.user.token
|
|
|
|
},
|
|
|
|
addAttendeeDialogVisible: false,
|
|
|
|
selectedMeeting: null,
|
|
|
|
meetingOptions: [],
|
|
...
|
...
|
@@ -350,6 +466,137 @@ export default { |
|
|
|
},
|
|
|
|
|
|
|
|
methods: {
|
|
|
|
exportErrorData() {
|
|
|
|
// 创建 CSV 内容
|
|
|
|
let csvContent = "行番号、エラーの原因\n";
|
|
|
|
this.errorData.forEach(item => {
|
|
|
|
csvContent += `${item.row},${item.message}\n`;
|
|
|
|
});
|
|
|
|
|
|
|
|
// 创建 Blob 对象
|
|
|
|
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
|
|
|
|
|
|
|
// 创建下载链接
|
|
|
|
const link = document.createElement('a');
|
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
link.href = url;
|
|
|
|
link.setAttribute('download', '一括更新エラー情報.csv');
|
|
|
|
document.body.appendChild(link);
|
|
|
|
|
|
|
|
// 触发下载
|
|
|
|
link.click();
|
|
|
|
|
|
|
|
// 清理
|
|
|
|
document.body.removeChild(link);
|
|
|
|
URL.revokeObjectURL(url);
|
|
|
|
},
|
|
|
|
// 批量修改按钮点击事件
|
|
|
|
handleBatchUpdate() {
|
|
|
|
if (this.ids.length === 0) {
|
|
|
|
this.$modal.msgWarning(this.$t('meetatt.select_data_first'));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.batchUpdateDialogVisible = true;
|
|
|
|
this.resetUploadStatus();
|
|
|
|
},
|
|
|
|
|
|
|
|
// 重置上传状态
|
|
|
|
resetUploadStatus() {
|
|
|
|
this.uploadStatus = '';
|
|
|
|
this.uploadPercentage = 0;
|
|
|
|
this.uploadProgressStatus = '';
|
|
|
|
},
|
|
|
|
|
|
|
|
// 下载批量修改模板
|
|
|
|
downloadBatchTemplate() {
|
|
|
|
const params = {
|
|
|
|
meetingIds: this.ids,
|
|
|
|
attendeeIds: this.attendIds
|
|
|
|
};
|
|
|
|
|
|
|
|
this.$modal.loading(this.$t('meetatt.preparing_download'));
|
|
|
|
|
|
|
|
exportBatchUpdateTemplate(params).then(response => {
|
|
|
|
const url = window.URL.createObjectURL(new Blob([response]));
|
|
|
|
const link = document.createElement('a');
|
|
|
|
link.href = url;
|
|
|
|
link.setAttribute('download', `batch_update_template_${new Date().getTime()}.xlsx`);
|
|
|
|
document.body.appendChild(link);
|
|
|
|
link.click();
|
|
|
|
document.body.removeChild(link);
|
|
|
|
window.URL.revokeObjectURL(url);
|
|
|
|
this.$modal.closeLoading();
|
|
|
|
}).catch(error => {
|
|
|
|
this.$modal.closeLoading();
|
|
|
|
this.$modal.msgError(this.$t('meetatt.download_failed') + (error.message || error));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
// 上传前校验
|
|
|
|
beforeUpload(file) {
|
|
|
|
const isExcel = file.name.endsWith('.xlsx') || file.name.endsWith('.xls');
|
|
|
|
if (!isExcel) {
|
|
|
|
this.$modal.msgError(this.$t('meetatt.only_excel'));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 重置上传状态
|
|
|
|
this.resetUploadStatus();
|
|
|
|
this.uploadStatus = 'uploading';
|
|
|
|
this.uploadProgressStatus = '';
|
|
|
|
|
|
|
|
// 模拟上传进度
|
|
|
|
const interval = setInterval(() => {
|
|
|
|
if (this.uploadPercentage >= 90) {
|
|
|
|
clearInterval(interval);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.uploadPercentage += 10;
|
|
|
|
}, 200);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
// 上传成功处理
|
|
|
|
handleUploadSuccess(response, file, fileList) {
|
|
|
|
this.uploadPercentage = 100;
|
|
|
|
this.uploadProgressStatus = 'success';
|
|
|
|
this.uploadStatus = 'success';
|
|
|
|
|
|
|
|
if (response.code === 200) {
|
|
|
|
// 成功处理
|
|
|
|
if (response.data && response.data.errors && response.data.errors.length > 0) {
|
|
|
|
// 部分成功,有错误数据
|
|
|
|
this.errorData = response.data.errors;
|
|
|
|
this.showErrorDialog = true;
|
|
|
|
this.$modal.msgWarning(`成功更新 ${response.data.successCount} 条数据,但有 ${response.data.errors.length} 条数据存在问题`);
|
|
|
|
} else {
|
|
|
|
// 全部成功
|
|
|
|
this.$modal.msgSuccess(response.msg || this.$t('meetatt.batch_update_success'));
|
|
|
|
this.batchUpdateDialogVisible = false;
|
|
|
|
}
|
|
|
|
this.getList(); // 刷新列表
|
|
|
|
} else {
|
|
|
|
// 处理失败
|
|
|
|
this.$modal.msgError(response.msg || this.$t('meetatt.batch_update_failed'));
|
|
|
|
}
|
|
|
|
this.fileList = [];
|
|
|
|
},
|
|
|
|
|
|
|
|
// 上传失败处理
|
|
|
|
handleUploadError(err, file, fileList) {
|
|
|
|
this.uploadPercentage = 100;
|
|
|
|
this.uploadProgressStatus = 'exception';
|
|
|
|
this.uploadStatus = 'error';
|
|
|
|
|
|
|
|
let errorMsg = this.$t('meetatt.batch_update_failed');
|
|
|
|
if (err.message) {
|
|
|
|
errorMsg += ': ' + err.message;
|
|
|
|
}
|
|
|
|
this.$modal.msgError(errorMsg);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 打开添加参会人员对话框
|
|
|
|
openAddAttendeeDialog() {
|
|
...
|
...
|
@@ -454,7 +701,7 @@ export default { |
|
|
|
removeAttendeeIds: toRemoveString
|
|
|
|
}).then(response => {
|
|
|
|
if (response.code === 200) {
|
|
|
|
this.$modal.msgSuccess('参会人员更新成功');
|
|
|
|
this.$modal.msgSuccess('参加者の更新に成功しました');
|
|
|
|
this.addAttendeeDialogVisible = false;
|
|
|
|
this.getList(); // 刷新列表
|
|
|
|
}
|
|
...
|
...
|
@@ -749,16 +996,16 @@ export default { |
|
|
|
attendeeId: item.attendeeId
|
|
|
|
}));
|
|
|
|
|
|
|
|
this.$modal.confirm('确认要为选中的' + params.length + '条数据生成参会证吗?').then(() => {
|
|
|
|
this.$modal.confirm('選択されていることを確認' + params.length + '条データ生成参会証か?').then(() => {
|
|
|
|
generateBadges(params).then(res => {
|
|
|
|
if (res.code === 200) {
|
|
|
|
this.$modal.msgSuccess("参会证生成成功");
|
|
|
|
this.$modal.msgSuccess("参会証の生成に成功しました");
|
|
|
|
this.getList();
|
|
|
|
} else {
|
|
|
|
this.$modal.msgError(res.msg);
|
|
|
|
}
|
|
|
|
}).catch(error => {
|
|
|
|
this.$modal.msgError("生成参会证失败:" + error.message);
|
|
|
|
this.$modal.msgError("参会証の生成に失敗しました:" + error.message);
|
|
|
|
});
|
|
|
|
}).catch(() => {
|
|
|
|
|
|
...
|
...
|
@@ -885,4 +1132,49 @@ export default { |
|
|
|
padding: 8px 10px;
|
|
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
|
}
|
|
|
|
|
|
|
|
.batch-update-dialog {
|
|
|
|
border-radius: 8px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.dialog-content {
|
|
|
|
padding: 0 20px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.download-section {
|
|
|
|
margin-bottom: 20px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.download-link {
|
|
|
|
margin-left: 10px;
|
|
|
|
font-size: 14px;
|
|
|
|
vertical-align: middle;
|
|
|
|
}
|
|
|
|
|
|
|
|
.upload-section {
|
|
|
|
margin: 20px 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
.upload-area {
|
|
|
|
border: 1px dashed #d9d9d9;
|
|
|
|
border-radius: 6px;
|
|
|
|
padding: 40px 0;
|
|
|
|
text-align: center;
|
|
|
|
background-color: #fafafa;
|
|
|
|
transition: border-color 0.3s;
|
|
|
|
}
|
|
|
|
|
|
|
|
.upload-area:hover {
|
|
|
|
border-color: #409EFF;
|
|
|
|
}
|
|
|
|
|
|
|
|
.upload-progress {
|
|
|
|
margin-top: 20px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.dialog-footer {
|
|
|
|
text-align: right;
|
|
|
|
padding: 10px 20px 0;
|
|
|
|
border-top: 1px solid #e8e8e8;
|
|
|
|
}
|
|
|
|
</style> |
...
|
...
|
|