413 lines
17 KiB
Vue
413 lines
17 KiB
Vue
<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-number v-model="q.periodNo" :min="1" placeholder="期数" style="width:110px" />
|
|
</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:140px">
|
|
<el-option label="未执行" value="NOT_STARTED" />
|
|
<el-option label="执行中" value="RUNNING" />
|
|
<el-option label="待合规审核" value="AWAITING_COMPLIANCE" />
|
|
<el-option label="待支持方审核" value="AWAITING_SPONSOR" />
|
|
<el-option label="待结算" value="AWAITING_SETTLEMENT" />
|
|
<el-option label="已完结" value="SETTLED" />
|
|
<el-option label="冻结中" value="FROZEN" />
|
|
</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>
|
|
|
|
<!-- ========== 表格区 (按原型列头) ========== -->
|
|
<GrTable :data="list" border stripe v-loading="loading" @selection-change="onSelectionChange"
|
|
:main-cols="['projectNo', 'meetingName']"
|
|
>
|
|
<el-table-column type="selection" width="44" />
|
|
<el-table-column prop="projectNo" label="项目编号" min-width="170" />
|
|
<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 }"><div class="table-actions">
|
|
<el-link :underline="false" size="small" type="primary" @click="onView(row)">查看</el-link>
|
|
<el-link :underline="false" size="small" type="primary" @click="onEdit(row)">修改</el-link>
|
|
<el-link :underline="false" size="small" type="warning" :disabled="!canSettle(row)" @click="onSettle(row)">结算</el-link>
|
|
<el-link :underline="false" size="small" type="primary" @click="onAudit(row)">审核</el-link>
|
|
<el-link :underline="false" size="small" @click="onDownload(row, 'labor')">劳务下载</el-link>
|
|
<el-link :underline="false" size="small" @click="onDownload(row, 'meeting')">会务下载</el-link>
|
|
<el-link :underline="false" size="small" type="danger" :disabled="row.currentStage !== 'FROZEN'" @click="onUnfreeze(row)">解冻</el-link>
|
|
</div></template>
|
|
</el-table-column>
|
|
</GrTable>
|
|
<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 GrTable from '@/components/GrTable.vue'
|
|
import { useRoute } from 'vue-router'
|
|
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: null,
|
|
startTimeRange: null, projectForm: '', currentStage: '', remark: ''
|
|
})
|
|
const page = reactive({ pageNum: 1, pageSize: 20, 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)
|
|
|
|
// 读 URL query 写入 q (Home KPI 卡跳转时带 ?currentStage=RUNNING/NOT_STARTED, 让列表页自动应用筛选)
|
|
// 只读不改 URL — Home 是 source of truth, 列表页内 reset()/search 不反向写 URL
|
|
const route = useRoute()
|
|
function readQueryFromRoute() {
|
|
const q2 = route.query
|
|
if (q2.currentStage != null && q2.currentStage !== '') q.currentStage = String(q2.currentStage)
|
|
}
|
|
|
|
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.data && r.data.rows) || r.rows || []
|
|
page.total = (r.data && r.data.total) || r.total || 0
|
|
} catch (e) { list.value = []; page.total = 0 }
|
|
finally { loading.value = false }
|
|
}
|
|
|
|
function reset() {
|
|
Object.assign(q, { projectNo: '', meetingId: '', meetingName: '', periodNo: null, 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 && ['NOT_STARTED', 'RUNNING'].includes(row.currentStage)
|
|
}
|
|
function onSettle(row) {
|
|
ElMessageBox.confirm(`确定发起「${row.meetingName}」结算吗?`, '结算', { type: 'warning' })
|
|
.then(async () => {
|
|
await bizUpdate('meeting', { meetingId: row.meetingId, currentStage: 'SETTLED' })
|
|
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: 'RUNNING' })
|
|
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(() => { readQueryFromRoute(); 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; }
|
|
.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; }
|
|
|
|
/* ========================================
|
|
移动端适配 (≤768px)
|
|
- 卡片 padding 收窄
|
|
- 工具栏横向滚动 (6 个按钮铺不下, 用 overflow-x)
|
|
- filter-form: 保持 label 左 + input 右横向布局
|
|
- 表格字号收紧
|
|
======================================== */
|
|
@media (max-width: 768px) {
|
|
/* 卡片 padding */
|
|
.page-card { padding: 12px !important; border-radius: 4px !important; }
|
|
.breadcrumb { font-size: 12px !important; margin-bottom: 8px !important; }
|
|
|
|
/* 工具栏: 横向滚动 (按钮多时不换行) */
|
|
.toolbar {
|
|
flex-wrap: nowrap !important;
|
|
overflow-x: auto;
|
|
-webkit-overflow-scrolling: touch;
|
|
padding-bottom: 6px;
|
|
margin-bottom: 8px !important;
|
|
scrollbar-width: thin;
|
|
}
|
|
.toolbar::-webkit-scrollbar { height: 4px; }
|
|
.toolbar::-webkit-scrollbar-thumb { background: var(--brand-slate-200); border-radius: 2px; }
|
|
.toolbar :deep(.action-btn),
|
|
.toolbar :deep(.el-button) {
|
|
flex-shrink: 0;
|
|
font-size: 12px !important;
|
|
padding: 0 10px !important;
|
|
height: 30px !important;
|
|
}
|
|
|
|
/* filter-form: label 与输入框横向 (label 左, input 右) */
|
|
.filter-form {
|
|
display: flex !important;
|
|
flex-direction: column !important;
|
|
align-items: stretch !important;
|
|
gap: 10px !important;
|
|
}
|
|
.filter-form :deep(.el-form-item) {
|
|
display: flex !important;
|
|
align-items: center !important;
|
|
margin-right: 0 !important;
|
|
margin-bottom: 0 !important;
|
|
}
|
|
.filter-form :deep(.el-form-item__label) {
|
|
float: none !important;
|
|
width: auto !important;
|
|
min-width: 80px !important;
|
|
text-align: right !important;
|
|
padding: 0 8px 0 0 !important;
|
|
font-size: 13px !important;
|
|
color: var(--el-text-color-regular) !important;
|
|
line-height: 32px !important;
|
|
height: 32px !important;
|
|
}
|
|
.filter-form :deep(.el-form-item__content) {
|
|
margin-left: 0 !important;
|
|
line-height: 32px !important;
|
|
flex: 1 !important;
|
|
min-width: 0 !important;
|
|
}
|
|
|
|
/* 强制所有控件全宽, 覆盖 inline 260px / 140px / 120px */
|
|
.filter-form :deep(.el-select),
|
|
.filter-form :deep(.el-input),
|
|
.filter-form :deep(.el-date-editor),
|
|
.filter-form :deep(.el-button),
|
|
.filter-form :deep(.el-cascader) {
|
|
width: 100% !important;
|
|
min-width: 0 !important;
|
|
margin-left: 0 !important;
|
|
margin-right: 0 !important;
|
|
display: block !important;
|
|
}
|
|
.filter-form :deep(.el-button + .el-button) {
|
|
margin-top: 8px !important;
|
|
}
|
|
|
|
/* 表格字号收紧 */
|
|
:deep(.el-table) { font-size: 12px !important; }
|
|
}
|
|
</style> |