feat(attendee): Excel 批量导入 (后端 /importData + /importTemplate + 5邀请差集推送, 前端 dialog-upload 套用 dialog-import SKILL)
This commit is contained in:
@@ -56,6 +56,7 @@
|
||||
<div class="attendee-section">
|
||||
<div class="attendee-toolbar">
|
||||
<el-button type="primary" size="small" :loading="attendeeLoading" @click="openAttendeeDialog()">+ 新增参会人</el-button>
|
||||
<el-button size="small" @click="openAttendeeImport">批量导入</el-button>
|
||||
<span class="toolbar-hint">按手机号定位; 手机号未注册将自动建 sys_user (用户名=密码=手机号)</span>
|
||||
</div>
|
||||
<!--
|
||||
@@ -390,6 +391,35 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 参会人 Excel 批量导入 (沿用 dialog-import SKILL: v-if + on-error + 模板下载在框外 + ImportResultDialog) -->
|
||||
<el-dialog v-model="importOpen" title="批量导入参会人" width="400px" append-to-body :close-on-click-modal="false">
|
||||
<el-upload
|
||||
v-if="importOpen"
|
||||
ref="importUploadRef"
|
||||
:limit="1"
|
||||
:headers="importHeaders"
|
||||
accept=".xlsx, .xls"
|
||||
:action="importAction"
|
||||
:on-progress="onImportProgress"
|
||||
:on-success="onImportSuccess"
|
||||
:on-error="onImportError"
|
||||
:auto-upload="false"
|
||||
drag
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload /></el-icon>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
</el-upload>
|
||||
<div style="margin-top:10px;font-size:13px;text-align:center">
|
||||
<span style="color:#909399;margin-right:8px">仅允许导入 xls、xlsx 格式文件</span>
|
||||
<el-link type="primary" :underline="false" @click="downloadAttendeeImportTpl">下载模板</el-link>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="importOpen=false">取 消</el-button>
|
||||
<el-button type="primary" :loading="importing" @click="submitImportFile">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<ImportResultDialog v-model="importResultOpen" :result="importResult" />
|
||||
|
||||
<div class="form-actions">
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
</div>
|
||||
@@ -405,7 +435,9 @@ import { listSupporters, listExecutor } from '@/api/system'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import OssFileUploader from '@/components/OssFileUploader.vue'
|
||||
import ImportResultDialog from '@/components/ImportResultDialog.vue'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { Upload } from '@element-plus/icons-vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -709,6 +741,70 @@ async function confirmDeleteAttendee(row) {
|
||||
ElMessage.error(e?.msg || e?.message || '删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== 参会人 Excel 批量导入 (沿用 dialog-import SKILL) =====================
|
||||
const importOpen = ref(false)
|
||||
const importing = ref(false)
|
||||
const importResultOpen = ref(false)
|
||||
const importResult = ref(null)
|
||||
const importUploadRef = ref(null)
|
||||
// el-upload 用原生 XHR, 不走 axios interceptor, 必须显式带 Authorization
|
||||
const importHeaders = computed(() => ({ Authorization: 'Bearer ' + (localStorage.getItem('ry_token') || '') }))
|
||||
// 后端 baseURL 与 request.js 一致 (/dev-api), 拼绝对路径给 :action; meetingId 走 query 串
|
||||
const importAction = computed(() => `/dev-api/business/meetingAttendee/importData?meetingId=${meetingId.value}`)
|
||||
|
||||
function openAttendeeImport() {
|
||||
importResult.value = null
|
||||
importOpen.value = true
|
||||
}
|
||||
|
||||
function downloadAttendeeImportTpl() {
|
||||
fetch('/dev-api/business/meetingAttendee/importTemplate', { headers: importHeaders.value })
|
||||
.then(r => r.blob())
|
||||
.then(blob => {
|
||||
const a = document.createElement('a')
|
||||
a.href = URL.createObjectURL(blob)
|
||||
a.download = '参会人批量导入模板.xlsx'
|
||||
a.click()
|
||||
})
|
||||
.catch(() => ElMessage.error('模板下载失败'))
|
||||
}
|
||||
|
||||
function submitImportFile() {
|
||||
importUploadRef.value?.submit()
|
||||
}
|
||||
|
||||
function onImportProgress() { importing.value = true }
|
||||
|
||||
function onImportSuccess(res) {
|
||||
importing.value = false
|
||||
const result = (res && res.data) ? res.data : res
|
||||
if (!result || typeof result.okNum !== 'number') {
|
||||
ElMessage.error(res?.msg || '导入失败, 返回数据异常')
|
||||
importResult.value = null
|
||||
return
|
||||
}
|
||||
importResult.value = result
|
||||
importOpen.value = false
|
||||
importResultOpen.value = true
|
||||
// 刷新列表 (新参会人会出现)
|
||||
loadAttendees()
|
||||
}
|
||||
|
||||
function onImportError(err) {
|
||||
importing.value = false
|
||||
let msg = '上传失败, 请重试'
|
||||
if (err && err.response != null) {
|
||||
try {
|
||||
const body = typeof err.response === 'string' ? JSON.parse(err.response) : err.response
|
||||
msg = body.msg || msg
|
||||
} catch { /* JSON 解析失败用兜底 */ }
|
||||
} else if (err && err.message) {
|
||||
msg = err.message
|
||||
}
|
||||
ElMessage.error(msg)
|
||||
importResult.value = null
|
||||
}
|
||||
async function loadStaff() {
|
||||
try {
|
||||
const [sp, ex] = await Promise.all([
|
||||
|
||||
Reference in New Issue
Block a user