refactor: 合并 biz_support_unit/biz_execution_unit/biz_service_org 为 biz_org, 删除 2 张孤儿表, 改 biz_person.work_unit → org_id FK
主要改动: - SQL: biz_support_unit → biz_org, 加 org_type, 加 business_nature; 删 biz_execution_unit, biz_service_org - SQL: biz_project.support_unit_id/name + service_org_id/name → org_id/name/type - SQL: biz_meeting.support_unit_name → org_name - SQL: biz_person.work_unit → org_id (FK) - 后端: 新 BizOrg entity/mapper/service/controller - 后端: BizProject/BizMeeting 字段重命名 - 后端: 删 12 个 BizSupportUnit/BizExecutionUnit/BizServiceOrg Java 文件 - 后端: BizPersonImportVO.workUnit → orgName (导入时查 biz_org 取 org_id) - 后端: BizAuthController.registerExecutor 完整实现 (原 registerSupplier stub) - 前端: 新 admin/Orgs.vue + manager/Orgs.vue (原 SupportUnits) - 前端: RegisterExecutor.vue (原 RegisterSupplier, 单页 2 步) - 前端: sponsor/executor/manager 多个文件 workUnit → orgId/orgName 重命名 - 前端: 统一 supplier → executor, 业务命名 sponsor(赞助方) / executor(执行方=供应商) - 前端: 全工程 execution → executor (company type / role / person unit_type)
This commit is contained in:
@@ -0,0 +1,351 @@
|
||||
<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="1" />
|
||||
<el-option label="未结算" value="0" />
|
||||
</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>
|
||||
<el-button type="primary" :disabled="!selected.length" @click="onBatchRate">批量评价</el-button>
|
||||
<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="ratingScore" 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="240" 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>
|
||||
<el-button size="small" link type="primary" @click="openRate(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.ratingScore || '-' }}</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 -->
|
||||
<el-dialog v-model="rateOpen" :title="rateMode==='batch' ? '批量项目评价' : '项目评价'" width="520px">
|
||||
<el-form :model="rateForm" label-width="100px">
|
||||
<el-form-item v-if="rateMode==='single'" label="项目编号 / 名称">
|
||||
<span>{{ rateForm.projectNo }} / {{ rateForm.projectName }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item v-else label="评价项目">
|
||||
<span>共 {{ rateForm.projectIds.length }} 个项目</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="评价维度">
|
||||
<div class="score-row"><span style="width:90px">履约质量:</span><el-rate v-model="rateForm.q1" :max="5" /></div>
|
||||
<div class="score-row"><span style="width:90px">时效响应:</span><el-rate v-model="rateForm.q2" :max="5" /></div>
|
||||
<div class="score-row"><span style="width:90px">配合度:</span><el-rate v-model="rateForm.q3" :max="5" /></div>
|
||||
<div class="score-row"><span style="width:90px">合规安全:</span><el-rate v-model="rateForm.q4" :max="5" /></div>
|
||||
</el-form-item>
|
||||
<el-form-item label="评价备注">
|
||||
<el-input v-model="rateForm.remark" type="textarea" rows="3" placeholder="可选" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<p class="dialog-hint">*评价标准与供应商系统保持一致</p>
|
||||
<template #footer>
|
||||
<el-button @click="rateOpen = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitRate">提交评价</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</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([])
|
||||
|
||||
// 评价
|
||||
const rateOpen = ref(false)
|
||||
const rateMode = ref('single') // 'single' | 'batch'
|
||||
const rateForm = reactive({
|
||||
projectId: null, projectNo: '', projectName: '',
|
||||
projectIds: [], q1: 0, q2: 0, q3: 0, q4: 0, remark: ''
|
||||
})
|
||||
|
||||
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.rows || []
|
||||
page.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.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()
|
||||
}
|
||||
|
||||
// ─── 项目评价 ───────────────────────────────────────────
|
||||
function openRate(row) {
|
||||
rateMode.value = 'single'
|
||||
Object.assign(rateForm, {
|
||||
projectId: row.projectId,
|
||||
projectNo: row.projectNo,
|
||||
projectName: row.projectName,
|
||||
projectIds: [],
|
||||
q1: 0, q2: 0, q3: 0, q4: 0,
|
||||
remark: ''
|
||||
})
|
||||
rateOpen.value = true
|
||||
}
|
||||
|
||||
async function onBatchRate() {
|
||||
if (!selected.value.length) return
|
||||
rateMode.value = 'batch'
|
||||
Object.assign(rateForm, {
|
||||
projectId: null, projectNo: '', projectName: '',
|
||||
projectIds: selected.value.map(r => r.projectId),
|
||||
q1: 3, q2: 3, q3: 3, q4: 3,
|
||||
remark: '批量评价'
|
||||
})
|
||||
rateOpen.value = true
|
||||
}
|
||||
|
||||
async function submitRate() {
|
||||
if (!rateForm.q1 && !rateForm.q2 && !rateForm.q3 && !rateForm.q4) {
|
||||
ElMessage.warning('请至少完成一项评分'); return
|
||||
}
|
||||
const items = rateMode.value === 'single'
|
||||
? [{ ...rateForm }]
|
||||
: rateForm.projectIds.map(pid => ({
|
||||
projectId: pid,
|
||||
q1: rateForm.q1, q2: rateForm.q2,
|
||||
q3: rateForm.q3, q4: rateForm.q4,
|
||||
remark: rateForm.remark
|
||||
}))
|
||||
let ok = 0
|
||||
for (const it of items) {
|
||||
try { await bizAdd('projectRating', it); ok++ } catch (e) { /* 跳过失败项 */ }
|
||||
}
|
||||
ElMessage.success(`已提交评价 ${ok} 项`)
|
||||
rateOpen.value = false
|
||||
load()
|
||||
}
|
||||
|
||||
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>
|
||||
Reference in New Issue
Block a user