feat: OA(ecology)项目拉取 + 会议邀请函PDF/微信分享 + 参会人/执行意向增强

OA ecology:
- BizEcologyController + EcologyProjectService: 项目编号搜索/详情回填, 负责人静默建档
- ComplianceLoginService: 合规/负责人登录校验 + 视图 role 匹配
- application-prod/test.yml: ecology 只读库数据源

会议邀请函/分享:
- InvitationPdfService + WxShareService: 邀请函 PDF 生成 + 微信分享
- PdfViewer.vue (pdfjs-dist) + MeetingInvitation.vue: PDF 预览 + 邀请函页

参会人/执行意向:
- BizMeetingAttendee*: 参会人模块增强
- BizExecutionIntent*: 执行意向增强

签到/劳务/导入:
- BizSignServiceImpl + SignFill.vue: 签到劳务
- FieldMismatch/ImportMismatch: 导入校验 VO

公示/会议详情:
- PublicityDetail.vue / MeetingDetail.vue 等前端增强

配置:
- test 短信 mock + 供应商/招标接口切生产域名
This commit is contained in:
郭庆泰
2026-09-03 20:12:15 +08:00
parent beb9799a06
commit 56615f9806
55 changed files with 3296 additions and 516 deletions
+14 -13
View File
@@ -65,7 +65,7 @@
<el-button @click="reset">重置</el-button>
<el-button v-if="!isLeader" @click="doExport">导出</el-button>
<el-button v-if="!isLeader" @click="doExportRating">项目评价导出</el-button>
<el-button v-if="isManager" @click="doExportSignupExpert">已报名专家导出</el-button>
<el-button v-if="isManager || isAdmin" @click="doExportSignupExpert">已报名专家导出</el-button>
</el-form-item>
</el-form>
@@ -155,7 +155,7 @@
<el-dropdown-item v-if="row.isFinished !== '1' && row.isFinished !== 1" command="close">结题</el-dropdown-item>
<el-dropdown-item command="view">查看</el-dropdown-item>
<el-dropdown-item command="edit">编辑</el-dropdown-item>
<el-dropdown-item command="activate">开通</el-dropdown-item>
<el-dropdown-item v-if="false" command="activate">开通</el-dropdown-item> <!-- 暂时隐藏开通功能 (勿删, 需要时去掉 v-if="false") -->
<el-dropdown-item command="delAnn">删除公告</el-dropdown-item>
<el-dropdown-item command="rate">执行单位评分</el-dropdown-item>
<el-dropdown-item v-if="canDelete" command="delete">删除项目</el-dropdown-item>
@@ -300,6 +300,8 @@ import { useUserStore } from '@/store/user'
const userStore = useUserStore()
const router = useRouter()
const route = useRoute()
// 本列表页被 admin/manager/leader 三角色共用, 跳子页面须按当前角色前缀 (否则被 beforeEach 角色守卫拦回工作台)
const basePath = computed(() => userStore.role === 'admin' ? '/admin' : userStore.role === 'leader' ? '/leader' : '/manager')
const q = ref({ sponsorOrgIds: [], execOrgIds: [], projectNo: '', projectName: '', projectForm: '', startTime: '', endTime: '', isFinished: '', isSettled: '' })
const rows = ref([])
@@ -336,14 +338,14 @@ const canDelete = computed(() => {
const r = userStore.role
return r === 'admin'
})
function doCreateMeeting(row) { router.push(`/manager/meetings/new?projectId=${row.projectId}`) }
function doCreateMeeting(row) { router.push(`${basePath.value}/meetings/new?projectId=${row.projectId}`) }
function isFinishedRow(row) { return row.isFinished === '1' || row.isFinished === 1 }
function doAssign(row) {
if (isFinishedRow(row)) return ElMessage.warning('已结题项目不能分配')
// 跳转独立子页面 /manager/projects/assign?projectId=X
router.push({ path: '/manager/projects/assign', query: { projectId: row.projectId } })
router.push({ path: `${basePath.value}/projects/assign`, query: { projectId: row.projectId } })
}
function doEdit(row) { router.push(`/manager/projects/edit/${row.projectId}`) }
function doEdit(row) { router.push(`${basePath.value}/projects/edit/${row.projectId}`) }
// 删除项目 (软删除 + 级联置 5 张项目表 + 项目下所有会议 is_deleted=1)
// 后端 BizProjectController.remove 已强制仅 admin, 前端只做可见性 v-if
@@ -395,8 +397,9 @@ function readQueryFromRoute() {
if (q2.isSettled != null && q2.isSettled !== '') q.value.isSettled = String(q2.isSettled)
}
// 仅 manager 可见「已报名专家导出」 (前端 v-if, 后端 controller 再兜底 role 校验)
// 「已报名专家导出」: manager + admin 可见 (前端 v-if, 后端 controller 再兜底 role 校验)
const isManager = computed(() => userStore.role === 'manager')
const isAdmin = computed(() => userStore.role === 'admin')
// 项目负责人(leader): 只读, 复用本列表页 (数据由后端 list 按 lead_user_id 隔离), 隐藏所有写操作
const isLeader = computed(() => userStore.role === 'leader')
@@ -549,18 +552,16 @@ async function confirmSingleScore() {
}
function viewDetail(row) {
// admin/leader 复用本列表页, 详情须跳到各自前缀 (否则被 beforeEach 角色守卫拦截)
const base = userStore.role === 'admin' ? '/admin' : userStore.role === 'leader' ? '/leader' : '/manager'
router.push(`${base}/projects/detail/${row.projectId}`)
router.push(`${basePath.value}/projects/detail/${row.projectId}`)
}
// 点击「总场次/已执行/未执行」列 → 跳对应角色的会议列表, 按项目 + 阶段筛选 (会议列表后端按 role 隔离数据权限)
// scope: 'all'(全部) | 'done'(已执行) | 'todo'(未执行)
function goMeetings(row, scope) {
const base = userStore.role === 'admin' ? '/admin' : userStore.role === 'leader' ? '/leader' : '/manager'
const q = { projectId: row.projectId }
if (scope === 'done') q.currentStageNotIn = 'NOT_STARTED,IN_PROGRESS'
if (scope === 'todo') q.currentStage = 'NOT_STARTED'
router.push({ path: `${base}/meetings`, query: q })
router.push({ path: `${basePath.value}/meetings`, query: q })
}
// 点击列表「执行单位评分(合规/支持)」列 → 只读显示该角色 4 维度评分明细
@@ -626,7 +627,7 @@ const scoreDetailTotal = ref('—')
// 新建项目已迁到独立页面 /manager/projects/new (ManagerProjectsNew.vue)
function openNewProject() {
router.push('/manager/projects/new')
router.push(`${basePath.value}/projects/new`)
}
function openAssign() {
if (!selection.value.length) {
@@ -641,10 +642,10 @@ function openAssign() {
// 单条: ?projectId=X → 子页面 v-if 单条分支
// 批量: ?projectIds=1,2,3 → 子页面 v-else 批量分支
if (targets.length === 1) {
router.push({ path: '/manager/projects/assign', query: { projectId: targets[0].projectId } })
router.push({ path: `${basePath.value}/projects/assign`, query: { projectId: targets[0].projectId } })
} else {
const ids = targets.map(r => r.projectId).join(',')
router.push({ path: '/manager/projects/assign', query: { projectIds: ids } })
router.push({ path: `${basePath.value}/projects/assign`, query: { projectIds: ids } })
}
if (skipCount > 0) ElMessage.warning(`已跳过 ${skipCount} 个已结题项目`)
}