refactor(meetings): admin/manager 会议管理合并为 1 文件双路由 (Option C)
把 admin/AdminMeetings.vue (212 行) + manager/Meetings.vue (262 行) 合并到
新位置 views/meetings/Meetings.vue (~265 行), 双路由指向同一组件.
角色感知实现 (route.path.split('/')[1] 算 roleSegment):
- 面包屑: admin 显示 '首页 / 项目管理 / 会议管理', manager 显示 '首页 / 会议管理'
- 详情跳转: admin 跳 /admin/meetings/detail/X, manager 跳 /manager/meetings/detail/X
- 修改/复制: 双角色都跳 manager-meetings-new (跨角色 URL, MeetingNew.vue 公共页)
admin 自动获得 manager 全部权限 (5 操作按钮 + 批量提交 + 备注筛选 +
详细删除确认文案), 跟 'admin 应该拥有全部权限' 的产品语义对齐.
净收益:
- 代码 -474/+265 = -209 行 (-44%)
- 真相源从 2 文件变 1 文件, 后续新增功能只改 1 处
- 物理消除 admin/manager 功能集漂移风险
This commit is contained in:
@@ -26,7 +26,7 @@ const routes = [
|
||||
{ path: 'users', name: 'admin-users', component: () => import('@/views/admin/Users.vue'), meta: { title: '用户管理' } },
|
||||
{ path: 'roles', name: 'admin-roles', component: () => import('@/views/admin/Roles.vue'), meta: { title: '角色管理' } },
|
||||
{ path: 'projects', name: 'admin-projects', component: () => import('@/views/manager/Projects.vue'), meta: { title: '项目管理' } },
|
||||
{ path: 'meetings', name: 'admin-meetings', component: () => import('@/views/admin/AdminMeetings.vue'), meta: { title: '会议管理' } },
|
||||
{ path: 'meetings', name: 'admin-meetings', component: () => import('@/views/meetings/Meetings.vue'), meta: { title: '会议管理' } },
|
||||
{ path: 'meetings/detail/:meetingId', name: 'admin-meetings-detail', component: () => import('@/views/manager/MeetingDetail.vue'), meta: { title: '会议详情' } },
|
||||
{ path: 'department', name: 'admin-department', component: () => import('@/views/admin/Department.vue'), meta: { title: '科室管理' } },
|
||||
{ path: 'title', name: 'admin-title', component: () => import('@/views/admin/Title.vue'), meta: { title: '职称管理' } },
|
||||
@@ -64,7 +64,7 @@ const routes = [
|
||||
{ path: 'projects/edit/:projectId', name: 'manager-projects-edit', component: () => import('@/views/manager/ProjectsNew.vue'), meta: { title: '编辑项目' } },
|
||||
{ path: 'projects/detail/:projectId', name: 'manager-projects-detail', component: () => import('@/views/manager/ManagerProjectDetail.vue'), meta: { title: '项目详情' } },
|
||||
{ path: 'projects/assign', name: 'manager-projects-assign', component: () => import('@/views/manager/ManagerProjectsAssign.vue'), meta: { title: '项目分配' } },
|
||||
{ path: 'meetings', name: 'manager-meetings', component: () => import('@/views/manager/Meetings.vue'), meta: { title: '会议管理' } },
|
||||
{ path: 'meetings', name: 'manager-meetings', component: () => import('@/views/meetings/Meetings.vue'), meta: { title: '会议管理' } },
|
||||
{ path: 'meetings/detail/:meetingId', name: 'manager-meetings-detail', component: () => import('@/views/manager/MeetingDetail.vue'), meta: { title: '会议详情' } },
|
||||
{ path: 'meetings/new', name: 'manager-meetings-new', component: () => import('@/views/manager/MeetingNew.vue'), meta: { title: '新建会议' } },
|
||||
{ path: 'experts', name: 'manager-experts', component: () => import('@/views/expert/Experts.vue'), meta: { title: '专家审核' } },
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
<template>
|
||||
<div class="page-card admin-meetings">
|
||||
<div class="breadcrumb">首页 / 项目管理 / 会议管理</div>
|
||||
|
||||
<!-- ========== 筛选区 ========== -->
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="项目编号"><el-input v-model="q.projectNo" placeholder="请输入项目编号" clearable style="width:140px" /></el-form-item>
|
||||
<el-form-item label="会议ID"><el-input v-model="q.meetingId" placeholder="请输入会议ID" clearable style="width:140px" /></el-form-item>
|
||||
<el-form-item label="会议名称"><el-input v-model="q.meetingName" placeholder="请输入会议名称" clearable style="width:160px" /></el-form-item>
|
||||
<el-form-item label="期数"><el-input v-model="q.periodNo" placeholder="请输入期数" clearable style="width:140px" /></el-form-item>
|
||||
<el-form-item label="会议时间">
|
||||
<el-date-picker v-model="q.startTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="开始时间" style="width:170px" />
|
||||
<span class="date-sep">至</span>
|
||||
<el-date-picker v-model="q.endTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="结束时间" style="width:170px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="项目形式">
|
||||
<el-select v-model="q.projectForm" placeholder="请选择" clearable style="width:140px">
|
||||
<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" placeholder="请选择" clearable style="width:140px">
|
||||
<el-option label="未执行" value="未执行" />
|
||||
<el-option label="执行中" value="执行中" />
|
||||
<el-option label="已执行" value="已执行" />
|
||||
<el-option label="已完结" value="已完结" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- ========== 批量操作 (admin 专有: 批量软删) ========== -->
|
||||
<div class="batch-bar">
|
||||
<el-button type="danger" :disabled="!selectedIds.length" @click="onBatchDelete">批量删除</el-button>
|
||||
<span v-if="selectedIds.length" class="filter-tip">已选 {{ selectedIds.length }} 条</span>
|
||||
</div>
|
||||
|
||||
<!-- ========== 表格 ========== -->
|
||||
<el-table :data="rows" v-loading="loading" stripe border @selection-change="onSelectionChange">
|
||||
<el-table-column type="selection" width="44" />
|
||||
<el-table-column prop="projectNo" label="项目编号" width="130" fixed />
|
||||
<el-table-column prop="meetingId" label="会议ID" width="120" align="center" />
|
||||
<el-table-column prop="projectForm" label="项目形式" width="100" align="center" />
|
||||
<el-table-column prop="meetingName" label="会议名称" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="会议开始时间" width="160" align="center">
|
||||
<template #default="{ row }">{{ fmtTime(row.startTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="会议结束时间" width="160" align="center">
|
||||
<template #default="{ row }">{{ fmtTime(row.endTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="totalPeriods" label="总期数" width="80" align="center" />
|
||||
<el-table-column label="期数" width="80" align="center">
|
||||
<template #default="{ row }">{{ row.periodNo ? '第 ' + row.periodNo + ' 期' : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="currentStage" label="当前阶段" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<span :class="['stage-tag', 'stage-' + stageClass(row.currentStage)]">{{ row.currentStage }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="viewDetail(row)">查看</el-button>
|
||||
<el-button link type="danger" @click="onDelete(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="load"
|
||||
@size-change="load"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { bizList, bizDelete } from '@/api/public'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
// ========== 筛选 ==========
|
||||
const q = ref({
|
||||
projectNo: '', meetingId: '', meetingName: '', periodNo: '',
|
||||
projectForm: '', currentStage: '',
|
||||
startTime: '', endTime: ''
|
||||
})
|
||||
const rows = ref([])
|
||||
const loading = ref(false)
|
||||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||||
|
||||
// ========== 表格多选 ==========
|
||||
const selectedIds = ref([])
|
||||
function onSelectionChange(sel) {
|
||||
selectedIds.value = sel.map(r => r.meetingId)
|
||||
}
|
||||
|
||||
// ========== 工具: 时间格式 ==========
|
||||
function fmtTime(d) {
|
||||
if (!d) return ''
|
||||
const dt = new Date(d)
|
||||
const pad = n => String(n).padStart(2, '0')
|
||||
return `${dt.getFullYear()}.${pad(dt.getMonth() + 1)}.${pad(dt.getDate())} ${pad(dt.getHours())}:${pad(dt.getMinutes())}`
|
||||
}
|
||||
|
||||
// ========== 工具: 当前阶段颜色 class ==========
|
||||
function stageClass(s) {
|
||||
if (s === '未执行') return 'pending'
|
||||
if (s === '执行中') return 'running'
|
||||
if (s === '已完结' || s === '已执行') return 'done'
|
||||
if (s === '待审核') return 'reviewing'
|
||||
if (s === '待结算') return 'waiting'
|
||||
if (s === '冻结中') return 'frozen'
|
||||
return 'default'
|
||||
}
|
||||
|
||||
// ========== 加载 ==========
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const { data } = await bizList('meeting', { ...q.value, pageNum: page.pageNum, pageSize: page.pageSize })
|
||||
rows.value = data?.rows || []
|
||||
page.total = data?.total || 0
|
||||
} catch { rows.value = []; page.total = 0 }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
function reset() {
|
||||
q.value = {
|
||||
projectNo:'', meetingId:'', meetingName:'', periodNo:'',
|
||||
projectForm:'', currentStage:'',
|
||||
startTime:'', endTime:''
|
||||
}
|
||||
page.pageNum = 1
|
||||
load()
|
||||
}
|
||||
|
||||
// ========== 路由跳转 (查看走 admin 详情页) ==========
|
||||
const router = useRouter()
|
||||
function viewDetail(row) {
|
||||
router.push(`/admin/meetings/detail/${row.meetingId}`)
|
||||
}
|
||||
|
||||
// ========== 删除 (软删除) ==========
|
||||
async function onDelete(row) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要删除会议「${row.meetingName}」吗?\n删除为软删除, 数据保留审计追溯, 医生/参会人将不再看到该会议.`,
|
||||
'删除确认', { type: 'warning' }
|
||||
)
|
||||
} catch { return }
|
||||
try {
|
||||
await bizDelete('meeting', [row.meetingId])
|
||||
ElMessage.success('删除成功')
|
||||
load()
|
||||
} catch (e) { ElMessage.error(e?.msg || '删除失败') }
|
||||
}
|
||||
|
||||
// ========== 批量删除 ==========
|
||||
async function onBatchDelete() {
|
||||
if (!selectedIds.value.length) return
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要对选中的 ${selectedIds.value.length} 个会议执行批量删除吗?\n删除为软删除, 数据保留审计追溯.`,
|
||||
'批量删除确认', { type: 'warning' }
|
||||
)
|
||||
} catch { return }
|
||||
try {
|
||||
await bizDelete('meeting', selectedIds.value, { __silentError: true })
|
||||
ElMessage.success(`批量删除成功 (${selectedIds.value.length} 条)`)
|
||||
load()
|
||||
} catch (e) { ElMessage.error(e?.msg || '批量删除失败') }
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-meetings { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.batch-bar { margin-bottom: 12px; display: flex; gap: 8px; align-items: center; }
|
||||
.filter-tip { color: #909399; font-size: 12px; }
|
||||
.date-sep { margin: 0 4px; color: #606266; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
|
||||
/* 当前阶段 tag 颜色 */
|
||||
.stage-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 10px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
border: 1px solid;
|
||||
}
|
||||
.stage-pending { color: #909399; border-color: #dcdfe6; background: #f5f7fa; }
|
||||
.stage-running { color: var(--brand-primary); border-color: #b3c5e0; background: #eef1f8; }
|
||||
.stage-done { color: #67c23a; border-color: #c2e7b0; background: #f0f9eb; }
|
||||
.stage-reviewing { color: #e6a23c; border-color: #f5dab1; background: #fdf6ec; }
|
||||
.stage-waiting { color: #ff4d4f; border-color: #fbc4c4; background: #fef0f0; }
|
||||
.stage-frozen { color: #909399; border-color: #dcdfe6; background: #f5f7fa; opacity: 0.6; }
|
||||
.stage-default { color: #606266; border-color: #dcdfe6; background: #fafafa; }
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="page-card manager-meetings">
|
||||
<div class="breadcrumb">首页 / 会议管理</div>
|
||||
<div class="page-card meetings-list">
|
||||
<div class="breadcrumb">{{ breadcrumb }}</div>
|
||||
|
||||
<!-- ========== 筛选区 (与 People.vue 风格一致) ========== -->
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
@@ -36,7 +36,7 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- ========== 批量提交按钮 (manager 角色专有, 不用 plain 否则 disabled 顺色) ========== -->
|
||||
<!-- ========== 批量按钮 (admin/manager 共用: 批量提交 + 批量删除) ========== -->
|
||||
<div class="batch-bar">
|
||||
<el-button type="primary" :disabled="!selectedIds.length" @click="onBatchSubmit">批量提交</el-button>
|
||||
<el-button type="danger" :disabled="!selectedIds.length" @click="onBatchDelete">批量删除</el-button>
|
||||
@@ -88,17 +88,29 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 修改 / 复制: 跳转到 /manager/meetings/new?meetingId=xxx[&mode=copy] (由 MeetingNew.vue 独立页处理) -->
|
||||
<!-- 查看: 跳转到 /manager/meetings/detail/:meetingId (MeetingDetail.vue 独立页) -->
|
||||
<!-- 修改 / 复制: 都跳 manager-meetings-new (MeetingNew.vue 公共页, 不区分角色入口) -->
|
||||
<!-- 查看: 按角色跳 /admin/meetings/detail/:id 或 /manager/meetings/detail/:id -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { bizList, bizUpdate, bizDelete } from '@/api/public'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
// ========== 角色感知 (admin/manager 共用此页, 按 URL 第 1 段识别) ==========
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const roleSegment = computed(() => route.path.split('/')[1]) // 'admin' | 'manager'
|
||||
const isAdmin = computed(() => roleSegment.value === 'admin')
|
||||
// 面包屑: admin 多一层 "项目管理" (跟其它 admin 页对齐)
|
||||
const breadcrumb = computed(() =>
|
||||
isAdmin.value ? '首页 / 项目管理 / 会议管理' : '首页 / 会议管理'
|
||||
)
|
||||
// 详情路由前缀: admin 跳 admin-meetings-detail, manager 跳 manager-meetings-detail
|
||||
const detailBase = computed(() => `/${roleSegment.value}/meetings`)
|
||||
|
||||
// ========== 筛选 (按实际 8 项: 项目编号/会议ID/会议名称/第?期/会议时间/项目形式/当前阶段/备注) ==========
|
||||
const q = ref({
|
||||
projectNo: '', meetingId: '', meetingName: '', periodNo: '',
|
||||
@@ -154,13 +166,12 @@ function reset() {
|
||||
load()
|
||||
}
|
||||
|
||||
// ========== 路由跳转 (查看/修改/复制都走独立页, 不用 dialog) ==========
|
||||
const router = useRouter()
|
||||
// 查看: 跳 MeetingDetail.vue 独立页 (按原型 meeting-detail.html)
|
||||
// ========== 路由跳转 ==========
|
||||
// 查看: 按角色跳 detail 页 (admin/manager 走各自路由, 复用 manager/MeetingDetail.vue)
|
||||
function viewDetail(row) {
|
||||
router.push(`/manager/meetings/detail/${row.meetingId}`)
|
||||
router.push(`${detailBase.value}/detail/${row.meetingId}`)
|
||||
}
|
||||
// 修改 / 复制: 跳 MeetingNew.vue 独立页, ?meetingId=xxx&projectId=xxx[&mode=copy]
|
||||
// 修改 / 复制: 都跳 manager-meetings-new (MeetingNew.vue 是公共页, admin 进 manager 路由不影响功能)
|
||||
function onEdit(row) {
|
||||
router.push({ name: 'manager-meetings-new', query: { meetingId: row.meetingId, projectId: row.projectId || '' } })
|
||||
}
|
||||
@@ -235,8 +246,8 @@ onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.manager-meetings { padding: 16px; }
|
||||
/* 整页面板 (与 People.vue 风格一致, 双角色共用) */
|
||||
.meetings-list { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.batch-bar { margin-bottom: 12px; display: flex; gap: 8px; align-items: center; }
|
||||
Reference in New Issue
Block a user