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
+70 -6
View File
@@ -10,13 +10,18 @@
<div class="new-card-title">项目信息</div>
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="form.projectName" placeholder="输入项目名称" />
<el-form-item label="项目编号" prop="projectNo">
<el-select v-model="form.projectNo" placeholder="输入项目编号搜索" filterable allow-create default-first-option
:filter-method="searchProjectNos" :loading="projectNoLoading" clearable style="width:100%"
@change="onProjectNoChange">
<el-option v-for="p in projectNoOptions" :key="p.projectNo"
:label="p.projectName ? `${p.projectNo}${p.projectName}` : p.projectNo" :value="p.projectNo" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目编号" prop="projectNo">
<el-input v-model="form.projectNo" placeholder="请输入项目编号" />
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="form.projectName" placeholder="请输入项目名称" />
</el-form-item>
</el-col>
</el-row>
@@ -132,7 +137,7 @@
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { bizAdd, bizUpdate, bizGet } from '@/api/public'
import { bizAdd, bizUpdate, bizGet, searchEcologyProjects, pullEcologyProject } from '@/api/public'
import { listExecutor, listLeaders } from '@/api/system'
import { ElMessage, ElMessageBox } from 'element-plus'
import request from '@/utils/request'
@@ -211,6 +216,61 @@ function syncProjectData() {
ElMessage.info('同步数据:从外部数据源拉取最新项目信息 (后端支持时启用)')
}
// ===== ecology 项目编号远程搜索 + 自动拉取 =====
const projectNoOptions = ref([])
const projectNoLoading = ref(false)
let _projectNoTimer = null
// 项目编号边输入边搜索 (空关键词清空, 不发请求)
function searchProjectNos(query) {
if (_projectNoTimer) clearTimeout(_projectNoTimer)
_projectNoTimer = setTimeout(async () => {
const kw = (query || '').trim()
if (!kw) {
projectNoOptions.value = []
projectNoLoading.value = false
return
}
projectNoLoading.value = true
try {
const r = await searchEcologyProjects({ xmbh: kw })
projectNoOptions.value = (r?.data) || []
} catch (e) {
projectNoOptions.value = []
} finally {
projectNoLoading.value = false
}
}, 200)
}
// 选中项目编号 → 拉取完整信息并覆盖表单 (负责人静默建档由后端完成)
async function onProjectNoChange(val) {
if (!val) return
// 仅当所选值来自 ecology 下拉时才拉取 (allow-create 自由输入不拉, 避免无谓请求)
const matched = projectNoOptions.value.find(p => p.projectNo === val)
if (!matched) return
try {
const res = await pullEcologyProject(val, { __silentError: true })
const d = res?.data || res
if (!d) return
form.projectName = d.projectName || ''
form.projectForm = d.projectForm || ''
form.totalAmount = d.totalAmount != null ? Number(d.totalAmount) : 0
form.manageFee = d.manageFee != null ? Number(d.manageFee) : 0
form.startTime = d.startTime || ''
form.endTime = d.endTime || ''
if (d.leaderUserId != null) {
form.leadUserId = d.leaderUserId
// 把自动建档的负责人塞进 leaderOptions, 避免 el-select 选中后 label 空白
if (!leaderOptions.value.find(u => u.userId === d.leaderUserId)) {
leaderOptions.value = [{ userId: d.leaderUserId, nickName: d.leaderName, userName: d.leaderPhone }, ...leaderOptions.value]
}
}
} catch (e) {
// ecology 未找到/不可达: 静默, 保留用户手填内容
}
}
// 编辑模式:加载已有项目数据并反显到表单
async function loadProject() {
if (!isEdit || !projectId) return
@@ -221,6 +281,8 @@ async function loadProject() {
if (!p) return
form.projectName = p.projectName || ''
form.projectNo = p.projectNo || ''
// 编辑模式: 项目编号 select 预置当前项, 避免远程搜索 select 无选项时 label 空白
if (form.projectNo) projectNoOptions.value = [{ projectNo: form.projectNo, projectName: form.projectName }]
form.projectForm = p.projectForm || ''
form.totalSessions = p.totalSessions || 0
form.totalAmount = p.totalAmount || 0
@@ -293,7 +355,9 @@ onMounted(() => {
})
function goBack() {
// 带 _t 强制 Projects.vue watch 触发 reload, 即便路由一样也重载
router.push({ path: '/manager/projects', query: { _t: Date.now() } })
// 本页被 admin/manager 复用, 按当前路由前缀回各自项目列表 (否则 admin 会被角色守卫拦回工作台)
const base = '/' + route.path.split('/')[1]
router.push({ path: `${base}/projects`, query: { _t: Date.now() } })
}
function confirmCancel() {
ElMessageBox.confirm(`确定取消${isEdit ? '编辑' : '新建'}项目?未保存的内容将丢失`, '提示', { type: 'warning' })