feat: 会议ID应用赋值 + 会议表单/详情优化 + admin工作台统计与用户管理

- meetingId 由 DB 自增改为应用赋值 10 位数字 (yyMMdd + 4 位序列, Redis 按日期计数)
- 会议开始时间不能晚于结束时间校验; 参会人表单身份证附件/角色单独一行
- 会议详情左右列比例 7:3 -> 8:2 (材料审核列收窄)
- admin/workbench 用户总数/角色类型数/各角色分布统计修复 (改用 listByRole 按 role_type 过滤)
- 新增 admin 用户管理接口 + 门户页脚 + H5 签到/上传优化
This commit is contained in:
郭庆泰
2026-08-25 23:33:14 +08:00
parent 591a43fe61
commit ea2024d083
66 changed files with 1454 additions and 1228 deletions
@@ -33,7 +33,7 @@
<el-col :span="12"><el-form-item label="项目负责人"><span class="info-value">{{ row.leadUserName || '-' }}</span></el-form-item></el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="12"><el-form-item label="支持公司"><span class="info-value">{{ row.sponsorAdminUserName || '-' }}</span></el-form-item></el-col>
<el-col :span="12"><el-form-item label="支持公司"><span class="info-value">{{ row.sponsorOrgName || '-' }}</span></el-form-item></el-col>
<el-col :span="12"><el-form-item label="项目评价"><span class="info-value">{{ fmtScore(row.managerScore) }}</span></el-form-item></el-col>
</el-row>
<el-row :gutter="12">
@@ -89,18 +89,18 @@
<!-- (已分配执行方 已折叠进上方"项目信息"section, 此处不再重复) -->
<!-- ================= 4. 已发布公告 (只读, 仅查看) ================= -->
<div class="new-card-title">已发布公告</div>
<el-table v-loading="loading" :data="announcements" border>
<el-table-column prop="title" label="公告标题" min-width="200" show-overflow-tooltip />
<el-table-column prop="createTime" label="发布时间" width="160" align="center" :formatter="(r)=>fmtDate(r.createTime)" />
<el-table-column prop="creatorName" label="发布人" width="120" align="center" />
<el-table-column label="操作" width="100" align="center">
<template #default="{ row }">
<el-button link type="primary" @click="openPreview(row.fileUrl || row.url, row.title)">查看</el-button>
</template>
</el-table-column>
</el-table>
<!-- ================= 4. 公告文件 (只读, 仅查看: 邀请函/支持函/通知/日程) ================= -->
<div class="new-card-title">公告文件</div>
<div class="notice-list">
<div v-for="(n, idx) in notices" :key="idx" class="notice-row">
<span class="notice-label">{{ n.label }}</span>
<span v-if="n.url" class="notice-file">
<span class="file-name">{{ n.name || '已上传' }}</span>
<el-button link type="primary" @click="openPreview(n.url, n.label)">查看</el-button>
</span>
<span v-else class="file-empty">未上传</span>
</div>
</div>
<!-- 公告预览 (只读) -->
<Preview v-model="previewOpen" :url="previewUrl" :title="previewTitle" />
@@ -108,9 +108,9 @@
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ref, reactive, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { bizGet, bizList } from '@/api/public'
import { bizGet } from '@/api/public'
import { listExecutorOrgs } from '@/api/system'
import request from '@/utils/request'
import Preview from '@/components/Preview.vue'
@@ -135,8 +135,22 @@ const form = reactive({
// 监督员 (从 biz_project.monitors JSON 字段读)
const monitors = ref([])
// 已发布公告
const announcements = ref([])
// 公告文件 (邀请函/支持函/通知/日程) — 数据源 biz_project 4 个独立列, 与 ProjectsNew 反显逻辑一致
// 注: 后端无 publicityFiles 字段 (前端传了但不落库), 实际落库是 invitation_url/support_letter_url/publish_url/schedule_url
const notices = computed(() => {
const r = row.value || {}
const items = [
{ label: '邀请函', url: r.invitationUrl },
{ label: '支持函', url: r.supportLetterUrl },
{ label: '通知', url: r.publishUrl },
{ label: '日程', url: r.scheduleUrl }
]
return items.map(m => ({
label: m.label,
url: m.url,
name: m.url ? (m.url.split('/').pop() || '') : ''
}))
})
// 公告预览
const previewOpen = ref(false)
@@ -186,17 +200,6 @@ async function loadProject() {
}
}
async function loadAnnouncements() {
try {
const resp = await bizList('publicity', { projectId: projectId.value })
const arr = (resp && resp.rows) || ((resp && resp.data) && (resp && resp.data).rows) || (resp && resp.data) || []
announcements.value = Array.isArray(arr) ? arr : []
} catch (e) {
console.error('[project-detail] announcements load failed', e)
announcements.value = []
}
}
// ===================== 已分配执行方 (服务公司) =====================
/**
* 数据源同 manager/projects/edit/{id} 的 loadAssigns:
@@ -272,7 +275,6 @@ function openPreview(url, title) {
onMounted(async () => {
await loadProject()
await Promise.all([loadAssigns(), loadMonitors()])
await loadAnnouncements()
})
</script>
@@ -326,6 +328,14 @@ onMounted(async () => {
.info-table td { padding: 10px 12px; border-bottom: 1px solid #f5f5f5; color: #595959; vertical-align: middle; }
.empty-row { text-align: center; color: #c0c4cc; }
/* 公告文件 (只读 4 行: 邀请函/支持函/通知/日程) */
.notice-list { display: flex; flex-direction: column; gap: 6px; }
.notice-row { display: flex; align-items: center; gap: 12px; padding: 6px 0; }
.notice-label { width: 90px; flex-shrink: 0; color: #606266; font-size: 14px; }
.notice-file { flex: 1; display: flex; align-items: center; gap: 12px; min-width: 0; }
.file-name { color: #1a1a1a; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-empty { color: #c0c4cc; font-size: 13px; }
/* 提示 (灰色, 跟 ProjectsNew 一致) */
.hint-text { font-size: 12px; color: #909399; margin: 8px 0; line-height: 1.6; }
.hint-text p { margin-bottom: 4px; }