Files
guoju0808/ry-vue3/src/views/leader/Projects.vue
T
郭庆泰 b52a334184 chore: biz_org 合并 / BizProject 重构 / 各角色页面调整 (上次重构遗留)
涵盖改动范围:
- 后端 BizOrg / BizProject / BizProjectRating / BizSpecialPlan controller + domain + mapper + service
- 前端 admin (BizSpecialPlanAdmin, ProjectCategory) / executor / leader / manager / sponsor 多页调整
- api/dict.js + AdminLayout.vue 同步
2026-08-18 00:30:34 +08:00

275 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<!--
原型: 项目列表_1.html
SIDEBAR: ['项目列表', '人员管理', '会议列表']
批量按钮 (top ~220, left): 项目分配 / 批量评价
列头 (top 285-335, left): 项目编号 / 项目名称 / 总场次/总期数 / 已执行 / 未执行 / 总金额 /
可用金额 / 已支付劳务费 / 已支付会务费 / 执行单位评价支持方 /
是否结题 / 项目开始时间 / 项目结束时间 / 操作
操作列 (top 352, left): 查看 / 项目评价 / 分配
提示:
- 支持方管理员分配时只能分配监察员支持方);领导方可分配执行人员
- 项目评价: 4 维度评分标准与供应商系统一致
-->
<div class="leader-projects">
<p class="page-sub">查看您负责执行的项目</p>
<div class="filter-card">
<el-form :inline="true" :model="q" size="default">
<el-form-item label="项目编号">
<el-input v-model="q.projectNo" clearable placeholder="项目编号" style="width:160px" />
</el-form-item>
<el-form-item label="项目名称">
<el-input v-model="q.projectName" clearable placeholder="项目名称" style="width:180px" />
</el-form-item>
<el-form-item label="项目形式">
<el-select v-model="q.projectForm" placeholder="全部" clearable 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.isFinished" placeholder="全部" clearable style="width:100px">
<el-option label="已结题" value="1" />
<el-option label="未结题" value="0" />
</el-select>
</el-form-item>
<el-form-item label="是否结算">
<el-select v-model="q.isSettled" placeholder="全部" clearable style="width:100px">
<el-option label="已结算" value="Y" />
<el-option label="未结算" value="N" />
</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>
</div>
<div class="page-card">
<!-- 批量按钮 (按原型 left 顺序): 项目分配 / 批量评价 -->
<div class="batch-bar">
<el-button type="primary" @click="openBatchAssign">项目分配</el-button>
<!-- 评价入口仅 manager / sponsor 可见, leader 不可评分 -->
<span class="hint">已选 {{ selected.length }} </span>
</div>
<el-table :data="rows" border stripe v-loading="loading" @selection-change="onSelect">
<el-table-column type="selection" width="48" />
<el-table-column prop="projectNo" label="项目编号" min-width="140" fixed>
<template #default="{ row }">
<el-link type="primary" :underline="false" @click="openDetail(row)">{{ row.projectNo }}</el-link>
</template>
</el-table-column>
<el-table-column prop="projectName" label="项目名称" min-width="200" show-overflow-tooltip />
<el-table-column prop="totalSessions" label="总场次/总期数" width="120" align="center" />
<el-table-column prop="doneSessions" label="已执行" width="80" align="center" />
<el-table-column prop="todoSessions" label="未执行" width="80" align="center" />
<el-table-column label="总金额" width="120" align="right">
<template #default="{ row }">¥{{ formatMoney(row.totalAmount) }}</template>
</el-table-column>
<el-table-column label="可用金额" width="120" align="right">
<template #default="{ row }">¥{{ formatMoney(row.availableAmount) }}</template>
</el-table-column>
<el-table-column label="已支付劳务费" width="120" align="right">
<template #default="{ row }">¥{{ formatMoney(row.paidLaborAmount) }}</template>
</el-table-column>
<el-table-column label="已支付会务费" width="120" align="right">
<template #default="{ row }">¥{{ formatMoney(row.paidMeetingAmount) }}</template>
</el-table-column>
<el-table-column prop="managerScore" label="执行单位评价(合规)" width="150" align="center" />
<el-table-column label="是否结题" width="90" align="center">
<template #default="{ row }">
<el-tag :type="row.isFinished === '1' ? 'success' : 'info'" size="small">
{{ row.isFinished === '1' ? '已结题' : '未结题' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="startTime" label="项目开始时间" min-width="160" />
<el-table-column prop="endTime" label="项目结束时间" min-width="160" />
<!-- 操作列按原型 left 顺序: 查看 / 分配 -->
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button size="small" link type="primary" @click="openDetail(row)">查看</el-button>
<el-button size="small" link type="primary" @click="openAssign(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>
<!-- 详情 -->
<el-dialog v-model="detailOpen" title="项目详情" width="640px">
<el-descriptions :column="2" border>
<el-descriptions-item label="项目编号">{{ detail.projectNo }}</el-descriptions-item>
<el-descriptions-item label="项目形式">{{ detail.projectForm }}</el-descriptions-item>
<el-descriptions-item label="项目名称" :span="2">{{ detail.projectName }}</el-descriptions-item>
<el-descriptions-item label="总场次/总期数">{{ detail.totalSessions || 0 }}</el-descriptions-item>
<el-descriptions-item label="已执行/未执行">{{ detail.doneSessions || 0 }} / {{ detail.todoSessions || 0 }}</el-descriptions-item>
<el-descriptions-item label="总金额">¥{{ formatMoney(detail.totalAmount) }}</el-descriptions-item>
<el-descriptions-item label="可用金额">¥{{ formatMoney(detail.availableAmount) }}</el-descriptions-item>
<el-descriptions-item label="已支付劳务费">¥{{ formatMoney(detail.paidLaborAmount) }}</el-descriptions-item>
<el-descriptions-item label="已支付会务费">¥{{ formatMoney(detail.paidMeetingAmount) }}</el-descriptions-item>
<el-descriptions-item label="执行单位评价(合规)">{{ detail.managerScore || '-' }}</el-descriptions-item>
<el-descriptions-item label="是否结题">
<el-tag :type="detail.isFinished === '1' ? 'success' : 'info'">
{{ detail.isFinished === '1' ? '已结题' : '未结题' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="项目开始时间">{{ detail.startTime || '-' }}</el-descriptions-item>
<el-descriptions-item label="项目结束时间">{{ detail.endTime || '-' }}</el-descriptions-item>
</el-descriptions>
<template #footer><el-button @click="detailOpen=false">关闭</el-button></template>
</el-dialog>
<!-- 分配 dialog -->
<el-dialog v-model="assignOpen" :title="assignMode==='batch' ? '批量项目分配' : '项目分配'" width="520px">
<el-form :model="assignForm" label-width="100px">
<el-form-item v-if="assignMode==='single'" label="项目编号 / 名称">
<span>{{ assignForm.projectNo }} / {{ assignForm.projectName }}</span>
</el-form-item>
<el-form-item v-else label="分配项目">
<span> {{ assignForm.projectIds.length }} 个项目</span>
</el-form-item>
<el-form-item label="执行人员">
<el-select v-model="assignForm.executorId" filterable placeholder="请选择执行人员" style="width:100%">
<el-option v-for="p in personList" :key="p.personId" :label="p.name" :value="p.personId" />
</el-select>
</el-form-item>
<el-form-item label="分配备注">
<el-input v-model="assignForm.remark" type="textarea" rows="3" placeholder="可选" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="assignOpen = false">取消</el-button>
<el-button type="primary" @click="submitAssign">确认分配</el-button>
</template>
</el-dialog>
<!-- 项目评价 dialog 已移除: leader 不可评分 -->
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { bizList, bizUpdate, bizAdd } from '@/api/public'
import { ElMessage, ElMessageBox } from 'element-plus'
const q = reactive({ projectNo: '', projectName: '', projectForm: '', isFinished: '', isSettled: '' })
const rows = ref([])
const loading = ref(false)
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
const selected = ref([])
const detail = ref({})
const detailOpen = ref(false)
// 分配
const assignOpen = ref(false)
const assignMode = ref('single') // 'single' | 'batch'
const assignForm = reactive({
projectId: null, projectNo: '', projectName: '',
projectIds: [], executorId: '', remark: ''
})
const personList = ref([])
// 评价相关状态已移除: leader 不可评分
function formatMoney(v) { return Number(v || 0).toFixed(2) }
async function load() {
loading.value = true
try {
const r = await bizList('project', { ...q, pageNum: page.pageNum, pageSize: page.pageSize })
rows.value = (r.data && r.data.rows) || r.rows || []
page.total = (r.data && r.data.total) || r.total || 0
} catch (e) { rows.value = []; page.total = 0 }
finally { loading.value = false }
}
async function loadPersons() {
try {
const r = await bizList('person', { pageNum: 1, pageSize: 100 })
personList.value = (r.data && r.data.rows) || r.rows || []
} catch (e) { personList.value = [] }
}
function reset() {
Object.assign(q, { projectNo: '', projectName: '', projectForm: '', isFinished: '', isSettled: '' })
page.pageNum = 1
load()
}
function onSelect(arr) { selected.value = arr }
function openDetail(row) { detail.value = row; detailOpen.value = true }
// ─── 分配 ───────────────────────────────────────────────
function openAssign(row) {
assignMode.value = 'single'
Object.assign(assignForm, {
projectId: row.projectId,
projectNo: row.projectNo,
projectName: row.projectName,
projectIds: [],
executorId: '',
remark: ''
})
assignOpen.value = true
}
function openBatchAssign() {
if (!selected.value.length) { ElMessage.warning('请先勾选项目'); return }
assignMode.value = 'batch'
Object.assign(assignForm, {
projectId: null, projectNo: '', projectName: '',
projectIds: selected.value.map(r => r.projectId),
executorId: '',
remark: ''
})
assignOpen.value = true
}
async function submitAssign() {
if (!assignForm.executorId) { ElMessage.warning('请选择执行人员'); return }
const ids = assignMode.value === 'single'
? [assignForm.projectId]
: assignForm.projectIds
let ok = 0
for (const pid of ids) {
try {
await bizUpdate('project', {
projectId: pid,
executorId: assignForm.executorId,
assignRemark: assignForm.remark
})
ok++
} catch (e) { /* 跳过失败项 */ }
}
ElMessage.success(`已分配 ${ok} 项`)
assignOpen.value = false
load()
}
// 项目评价相关函数已移除: leader 不可评分
onMounted(() => { load(); loadPersons() })
</script>
<style scoped>
.leader-projects { padding: 16px; }
.page-sub { font-size: 13px; color: #909399; margin-bottom: 16px; }
.filter-card, .page-card { background: #fff; border-radius: 4px; padding: 16px; border: 1px solid #ebeef5; margin-bottom: 12px; }
.batch-bar { margin-bottom: 12px; display: flex; gap: 8px; align-items: center; }
.hint { color: #909399; font-size: 12px; margin-left: 8px; }
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
.score-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.dialog-hint { color: #909399; font-size: 12px; margin: -8px 0 0; }
</style>