refactor: 合并 biz_support_unit/biz_execution_unit/biz_service_org 为 biz_org, 删除 2 张孤儿表, 改 biz_person.work_unit → org_id FK

主要改动:
- SQL: biz_support_unit → biz_org, 加 org_type, 加 business_nature; 删 biz_execution_unit, biz_service_org
- SQL: biz_project.support_unit_id/name + service_org_id/name → org_id/name/type
- SQL: biz_meeting.support_unit_name → org_name
- SQL: biz_person.work_unit → org_id (FK)
- 后端: 新 BizOrg entity/mapper/service/controller
- 后端: BizProject/BizMeeting 字段重命名
- 后端: 删 12 个 BizSupportUnit/BizExecutionUnit/BizServiceOrg Java 文件
- 后端: BizPersonImportVO.workUnit → orgName (导入时查 biz_org 取 org_id)
- 后端: BizAuthController.registerExecutor 完整实现 (原 registerSupplier stub)
- 前端: 新 admin/Orgs.vue + manager/Orgs.vue (原 SupportUnits)
- 前端: RegisterExecutor.vue (原 RegisterSupplier, 单页 2 步)
- 前端: sponsor/executor/manager 多个文件 workUnit → orgId/orgName 重命名
- 前端: 统一 supplier → executor, 业务命名 sponsor(赞助方) / executor(执行方=供应商)
- 前端: 全工程 execution → executor (company type / role / person unit_type)
This commit is contained in:
郭庆泰
2026-08-15 12:41:10 +08:00
commit cf5790229a
694 changed files with 102090 additions and 0 deletions
+317
View File
@@ -0,0 +1,317 @@
<template>
<div class="page-card">
<div class="breadcrumb">首页 / 会议列表</div>
<!-- ========== 筛选区 ( sidebar=项目管理/... 的支持方原型 会议管理.html) ========== -->
<el-form inline :model="q" class="filter-form">
<el-form-item label="项目编号">
<el-input v-model="q.projectNo" placeholder="项目编号" clearable style="width:160px" />
</el-form-item>
<el-form-item label="会议ID">
<el-input v-model="q.meetingId" placeholder="会议ID" clearable style="width:160px" />
</el-form-item>
<el-form-item label="会议名称">
<el-input v-model="q.meetingName" placeholder="会议名称" clearable style="width:180px" />
</el-form-item>
<el-form-item label="第">
<el-input v-model="q.periodNo" placeholder="期数" clearable style="width:80px" />
<span style="margin:0 4px"></span>
</el-form-item>
<el-form-item label="会议时间起">
<el-date-picker v-model="q.startTimeRange" type="datetimerange" range-separator="至"
start-placeholder="开始时间" end-placeholder="结束时间" style="width:340px" />
</el-form-item>
<el-form-item label="项目形式">
<el-select v-model="q.projectForm" clearable placeholder="全部" style="width:120px">
<el-option label="线上" value="线上" />
<el-option label="线下" value="线下" />
<el-option label="线上+线下" value="线上+线下" />
<el-option label="其他" value="其他" />
</el-select>
</el-form-item>
<el-form-item label="当前阶段">
<el-select v-model="q.currentStage" clearable placeholder="全部" style="width:120px">
<el-option label="已执行" value="已执行" />
<el-option label="未执行" value="未执行" />
<el-option label="执行中" value="执行中" />
</el-select>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="q.remark" placeholder="备注" clearable style="width:160px" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadList">查找</el-button>
<el-button @click="reset">重置</el-button>
</el-form-item>
</el-form>
<!-- ========== 批量按钮区 (top=232, 3 个按钮) ========== -->
<div class="batch-bar">
<el-button :disabled="!selected.length" @click="onBatch('labor')">批量下载劳务材料</el-button>
<el-button :disabled="!selected.length" @click="onBatch('meeting')">批量下载会务材料</el-button>
<el-button :disabled="!selected.length" type="warning" @click="onBatchAudit">批量审核</el-button>
</div>
<!-- ========== 表格区 (按原型列头) ========== -->
<el-table :data="list" border stripe v-loading="loading" @selection-change="onSelectionChange">
<el-table-column type="selection" width="44" />
<el-table-column prop="projectNo" label="项目编号" min-width="130" />
<el-table-column prop="meetingName" label="会议名称" min-width="180" show-overflow-tooltip />
<el-table-column label="总期数" prop="totalPeriod" width="80" />
<el-table-column label="期数" prop="periodNo" width="70" />
<el-table-column label="当前阶段" prop="currentStage" width="100" />
<el-table-column label="会议开始时间" prop="startTime" min-width="160" />
<el-table-column label="会议结束时间" prop="endTime" min-width="160" />
<el-table-column label="提交剩余时间" width="140">
<template #default="{ row }">{{ calcRemain(row) }}</template>
</el-table-column>
<el-table-column label="项目形式" prop="projectForm" width="100" />
<el-table-column label="操作" width="420" fixed="right">
<template #default="{ row }">
<el-button size="small" link type="primary" @click="onView(row)">查看</el-button>
<el-button size="small" link type="primary" @click="onEdit(row)">修改</el-button>
<el-button size="small" link type="warning" :disabled="!canSettle(row)" @click="onSettle(row)">结算</el-button>
<el-button size="small" link type="primary" @click="onAudit(row)">审核</el-button>
<el-button size="small" link @click="onDownload(row, 'labor')">劳务下载</el-button>
<el-button size="small" link @click="onDownload(row, 'meeting')">会务下载</el-button>
<el-button size="small" link type="danger" :disabled="row.currentStage !== '冻结中'" @click="onUnfreeze(row)">解冻</el-button>
</template>
</el-table-column>
</el-table>
<div class="pager">
<el-pagination v-model:current-page="page.pageNum" v-model:page-size="page.pageSize"
:total="page.total" :page-sizes="[10,20,50]" layout="total, sizes, prev, pager, next, jumper"
@current-change="loadList" @size-change="loadList" />
</div>
<!-- ========== Dialog: 详情 ========== -->
<el-dialog v-model="viewOpen" title="会议详情" width="600px">
<el-descriptions :column="2" border>
<el-descriptions-item label="项目编号">{{ view.projectNo }}</el-descriptions-item>
<el-descriptions-item label="项目形式">{{ view.projectForm }}</el-descriptions-item>
<el-descriptions-item label="会议名称" :span="2">{{ view.meetingName }}</el-descriptions-item>
<el-descriptions-item label="会议ID">{{ view.businessId }}</el-descriptions-item>
<el-descriptions-item label="当前阶段">{{ view.currentStage }}</el-descriptions-item>
<el-descriptions-item label="开始时间" :span="2">{{ view.startTime }}</el-descriptions-item>
<el-descriptions-item label="结束时间" :span="2">{{ view.endTime }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ view.remark }}</el-descriptions-item>
</el-descriptions>
<template #footer>
<el-button @click="viewOpen = false">关闭</el-button>
</template>
</el-dialog>
<!-- ========== Dialog: 修改 ========== -->
<el-dialog v-model="editOpen" title="修改会议" width="560px">
<el-form :model="editForm" label-width="100px">
<el-form-item label="会议名称"><el-input v-model="editForm.meetingName" /></el-form-item>
<el-form-item label="会议开始时间"><el-date-picker v-model="editForm.startTime" type="datetime" style="width:100%" /></el-form-item>
<el-form-item label="会议结束时间"><el-date-picker v-model="editForm.endTime" type="datetime" style="width:100%" /></el-form-item>
<el-form-item label="项目形式">
<el-select v-model="editForm.projectForm" style="width:100%">
<el-option label="线上" value="线上" />
<el-option label="线下" value="线下" />
<el-option label="线上+线下" value="线上+线下" />
<el-option label="其他" value="其他" />
</el-select>
</el-form-item>
<el-form-item label="备注"><el-input v-model="editForm.remark" type="textarea" rows="3" /></el-form-item>
</el-form>
<template #footer>
<el-button @click="editOpen = false">取消</el-button>
<el-button type="primary" :loading="editSubmitting" @click="submitEdit">确定</el-button>
</template>
</el-dialog>
<!-- ========== Dialog: 审核 (原型 u3136: 通过/退回 + 意见) ========== -->
<el-dialog v-model="auditOpen" title="审核" width="520px">
<el-form :model="auditForm" label-width="100px">
<el-form-item label="会议名称">
<span>{{ auditRow?.meetingName }}</span>
</el-form-item>
<el-form-item label="审核结果">
<el-radio-group v-model="auditForm.action">
<el-radio value="pass">通过</el-radio>
<el-radio value="reject">退回</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="审核意见">
<p style="margin:0 0 6px;font-size:12px;color:#909399">*意见为非必填项,未填写则默认显示无</p>
<el-input v-model="auditForm.opinion" type="textarea" rows="4" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="auditOpen = false">取消</el-button>
<el-button type="primary" :loading="auditSubmitting" @click="submitAudit">确定</el-button>
</template>
</el-dialog>
<!-- ========== Dialog: 解冻确认 ========== -->
<el-dialog v-model="unfreezeOpen" title="解冻确认" width="420px">
<p>您确定要解冻吗?</p>
<p style="margin-top:8px;color:#606266">会议: {{ unfreezeRow?.meetingName }}</p>
<template #footer>
<el-button @click="unfreezeOpen = false">取消</el-button>
<el-button type="primary" :loading="unfreezeSubmitting" @click="submitUnfreeze">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { bizList, bizUpdate } from '@/api/public'
import { ElMessage, ElMessageBox } from 'element-plus'
const list = ref([])
const loading = ref(false)
const selected = ref([])
const q = reactive({
projectNo: '', meetingId: '', meetingName: '', periodNo: '',
startTimeRange: null, projectForm: '', currentStage: '', remark: ''
})
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const view = ref({})
const viewOpen = ref(false)
const editOpen = ref(false)
const editSubmitting = ref(false)
const editForm = reactive({ meetingId: null, meetingName: '', startTime: null, endTime: null, projectForm: '', remark: '' })
const auditOpen = ref(false)
const auditRow = ref(null)
const auditSubmitting = ref(false)
const auditForm = reactive({ action: 'pass', opinion: '' })
const unfreezeOpen = ref(false)
const unfreezeRow = ref(null)
const unfreezeSubmitting = ref(false)
async function loadList() {
loading.value = true
try {
const params = {
pageNum: page.pageNum, pageSize: page.pageSize,
...q,
startTime: q.startTimeRange?.[0] || '',
endTime: q.startTimeRange?.[1] || ''
}
delete params.startTimeRange
const r = await bizList('meeting', params)
list.value = r.rows || []
page.total = r.total || 0
} catch (e) { list.value = []; page.total = 0 }
finally { loading.value = false }
}
function reset() {
Object.assign(q, { projectNo: '', meetingId: '', meetingName: '', periodNo: '', startTimeRange: null, projectForm: '', currentStage: '', remark: '' })
page.pageNum = 1
loadList()
}
function onSelectionChange(rows) { selected.value = rows }
function onView(row) { view.value = row; viewOpen.value = true }
function onEdit(row) {
Object.assign(editForm, row)
editForm.startTime = row.startTime || null
editForm.endTime = row.endTime || null
editOpen.value = true
}
async function submitEdit() {
editSubmitting.value = true
try {
await bizUpdate('meeting', editForm)
ElMessage.success('修改成功')
editOpen.value = false
loadList()
} catch (e) { ElMessage.error(e?.msg || '修改失败') }
finally { editSubmitting.value = false }
}
function onAudit(row) {
auditRow.value = row
auditForm.action = 'pass'
auditForm.opinion = ''
auditOpen.value = true
}
async function submitAudit() {
if (!auditRow.value) return
auditSubmitting.value = true
try {
const text = auditForm.opinion || '无'
await bizUpdate('meeting', {
meetingId: auditRow.value.meetingId,
auditStatus: auditForm.action === 'pass' ? '通过' : '退回',
auditOpinion: text
})
ElMessage.success('审核成功')
auditOpen.value = false
loadList()
} catch (e) { ElMessage.error(e?.msg || '审核失败') }
finally { auditSubmitting.value = false }
}
function canSettle(row) {
return row && ['未执行', '执行中'].includes(row.currentStage)
}
function onSettle(row) {
ElMessageBox.confirm(`确定发起「${row.meetingName}」结算吗?`, '结算', { type: 'warning' })
.then(async () => {
await bizUpdate('meeting', { meetingId: row.meetingId, currentStage: '已完结' })
ElMessage.success('已发起结算')
loadList()
}).catch(() => {})
}
function onDownload(row, type) {
const url = type === 'labor' ? row.laborUrl : row.meetingUrl
if (!url) { ElMessage.warning('暂无文件'); return }
window.open(url, '_blank')
}
function onUnfreeze(row) {
unfreezeRow.value = row
unfreezeOpen.value = true
}
async function submitUnfreeze() {
if (!unfreezeRow.value) return
unfreezeSubmitting.value = true
try {
await bizUpdate('meeting', { meetingId: unfreezeRow.value.meetingId, currentStage: '执行中' })
ElMessage.success('解冻成功')
unfreezeOpen.value = false
loadList()
} catch (e) { ElMessage.error(e?.msg || '解冻失败') }
finally { unfreezeSubmitting.value = false }
}
function onBatch(type) {
if (!selected.value.length) return
selected.value.forEach(r => onDownload(r, type))
}
function onBatchAudit() {
if (!selected.value.length) return
auditRow.value = selected.value[0]
auditOpen.value = true
}
function calcRemain(row) {
if (!row.endTime || !row.submitDeadline) return '-'
const diff = new Date(row.submitDeadline) - new Date(row.endTime)
if (diff <= 0) return '冻结中'
const h = Math.floor(diff / 3600000)
return h > 24 ? `${Math.floor(h/24)}${h%24}小时` : `${h}小时`
}
onMounted(loadList)
</script>
<style scoped>
/* 1:1 抄 doctor/Submissions.vue + SponsorPeople.vue */
.page-card { background: #fff; padding: 16px 20px; border-radius: 6px; border: 1px solid #f0f0f0; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
.filter-form { display: flex; flex-wrap: wrap; gap: 0 16px; padding-bottom: 12px; border-bottom: 1px solid #f0f0f0; margin-bottom: 12px; }
.batch-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
</style>