feat: 会议提交剩余时间+解冻 + 会务/劳务材料批量下载 + 多角色人员/账号模块完善

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
郭庆泰
2026-08-25 06:53:18 +08:00
co-authored by Claude
parent 0c882ae846
commit 591a43fe61
126 changed files with 3789 additions and 1334 deletions
@@ -37,7 +37,6 @@
<div class="batch-bar">
<el-button type="primary" @click="goNew">新建人员</el-button>
<el-button @click="openImport">批量导入</el-button>
<span class="filter-tip">*批量导入模板与列表表项一致</span>
</div>
<!-- ========== 表格 ========== -->
@@ -52,6 +51,16 @@
<el-tag :type="statusTagType(row.status)" disable-transitions>{{ row.status === '1' ? '禁用' : '正常' }}</el-tag>
</template>
</el-table-column>
<!-- 管理员 switch: admin 可见, 指定机构管理员 (MAIN) -->
<el-table-column v-if="isAdmin" label="管理员" width="90" align="center">
<template #default="{ row }">
<el-switch
:model-value="row.accountType === 'MAIN'"
:disabled="switchingId === row.personId"
@change="onChangeAdmin(row)"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="260" fixed="right">
<template #default="{ row }">
<!-- 查看 (只读详情): 两角色都有, 跳独立详情页 -->
@@ -78,32 +87,42 @@
/>
</div>
<!-- ========== 批量导入 ========== -->
<el-dialog v-model="importOpen" title="批量导入" width="520px" :close-on-click-modal="false">
<el-form label-width="100px">
<el-form-item label="选择文件">
<el-upload ref="uploadRef" :auto-upload="false" :limit="1" accept=".xls,.xlsx" :on-change="onFileChange" :on-remove="onFileRemove">
<el-button>选择文件</el-button>
</el-upload>
</el-form-item>
<el-form-item label="">
<el-button link type="primary" @click="downloadTemplate">批量导入模板下载</el-button>
</el-form-item>
<el-form-item><span class="filter-tip">*导入列: 姓名/手机号/所属公司/部门/职务/角色</span></el-form-item>
</el-form>
<!-- ========== 批量导入 (沿用 executor-people 模式: drag + 模板下载在框外 + ImportResultDialog) ========== -->
<el-dialog v-model="importOpen" title="批量导入" width="400px" append-to-body :close-on-click-modal="false">
<el-upload
v-if="importOpen"
ref="uploadRef"
:limit="1"
accept=".xlsx, .xls"
:on-change="onFileChange"
:on-remove="onFileRemove"
:auto-upload="false"
drag
>
<el-icon class="el-icon--upload"><upload /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
</el-upload>
<!-- 模板下载: 必须放在 el-upload 框外(下面), 否则点击会被 drag 区域吞掉, 触发的是文件选择器 -->
<div style="margin-top:10px;font-size:13px;text-align:center">
<span style="color:#909399;margin-right:8px">仅允许导入 xlsxlsx 格式文件</span>
<el-link type="primary" :underline="false" @click="downloadTemplate">下载模板</el-link>
</div>
<template #footer>
<el-button @click="importOpen = false">取消</el-button>
<el-button type="primary" :loading="importing" @click="submitImport">确定</el-button>
<el-button @click="importOpen=false"> </el-button>
<el-button type="primary" :loading="importing" @click="submitImport"> </el-button>
</template>
</el-dialog>
<ImportResultDialog v-model="importResultOpen" :result="importResult" />
</div>
</template>
<script setup>
import { ref, reactive, onMounted, computed, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { bizList, bizUpdate, importPerson, downloadImportTemplate } from '@/api/public'
import { bizList, bizUpdate, importPerson, downloadImportTemplate, changePersonAdmin } from '@/api/public'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Upload } from '@element-plus/icons-vue'
import ImportResultDialog from '@/components/ImportResultDialog.vue'
const route = useRoute()
const router = useRouter()
@@ -171,6 +190,30 @@ async function onToggleStatus(row) {
} catch (e) { ElMessage.error(e?.msg || '操作失败') }
}
// ========== 更换机构管理员 (仅 admin) ==========
const switchingId = ref('')
async function onChangeAdmin(row) {
// switch 受控 (:model-value), 当前管理员 (MAIN) 的 switch 恒为开, 无法直接关掉
if (row.accountType === 'MAIN') { ElMessage.info('已是该机构管理员'); return }
try {
await ElMessageBox.confirm(
`确定将「${row.name}」设为该机构管理员吗?`,
'更换管理员',
{ type: 'warning' }
)
} catch { return }
switchingId.value = row.personId
try {
await changePersonAdmin(row.personId)
ElMessage.success('已更换管理员')
load()
} catch (e) {
ElMessage.error(e?.msg || e?.message || '更换失败')
} finally {
switchingId.value = ''
}
}
// ========== 跳转 (按角色) ==========
function goNew() {
router.push({
@@ -193,8 +236,10 @@ const importOpen = ref(false)
const importing = ref(false)
const uploadRef = ref()
const importFile = ref(null)
const importResultOpen = ref(false)
const importResult = ref(null)
function openImport() { importFile.value = null; importOpen.value = true }
function openImport() { importFile.value = null; importResult.value = null; importOpen.value = true }
function onFileChange(file) { importFile.value = file.raw }
function onFileRemove() { importFile.value = null }
async function downloadTemplate() {
@@ -213,21 +258,18 @@ async function submitImport() {
if (!importFile.value) { ElMessage.warning('请先选择文件'); return }
importing.value = true
try {
const res = await importPerson('sponsor', importFile.value)
const res = await importPerson('sponsor', importFile.value, orgId.value)
const payload = res.data || res
const ok = payload.ok ?? 0
const total = payload.total ?? 0
const failed = (payload.results || []).filter(r => !r.ok)
if (failed.length === 0) {
ElMessage.success(`导入成功 ${ok}/${total}`)
} else {
ElMessageBox.alert(
`成功 ${ok} 条, 失败 ${failed.length} 条:\n` +
failed.map(f => `${f.rowNo}${f.name || ''}: ${f.message}`).join('\n'),
'导入结果', { type: 'warning' }
)
// 映射为 ImportResultDialog 期望的形状 { okNum, ngNum, ngList: [{rowNum, message}] }
importResult.value = {
okNum: ok,
ngNum: failed.length,
ngList: failed.map(f => ({ rowNum: f.rowNo, message: (f.name ? f.name + ': ' : '') + (f.message || '') }))
}
importOpen.value = false
importResultOpen.value = true
load()
} catch (e) {
ElMessage.error(e?.msg || '导入失败')