|
|
@@ -1,8 +1,60 @@
|
|
|
<template>
|
|
|
<div class="app-container">
|
|
|
+ <!-- 核算公式展示 -->
|
|
|
+ <el-alert :closable="false" show-icon type="info" class="mb16">
|
|
|
+ <template #title>
|
|
|
+ <span style="font-weight: bold">绩效核算公式:</span>
|
|
|
+ <span>部门当月绩效总额 = 该部门当月所有审核通过项目产值总和 × </span>
|
|
|
+ <span style="font-weight: bold; color: #409EFF; font-size: 16px">{{ performanceRatio }}%</span>
|
|
|
+ <el-divider direction="vertical" />
|
|
|
+ <span style="color: #909399; font-size: 12px">核算比例由院班子在系统配置中设定,当前为默认值</span>
|
|
|
+ </template>
|
|
|
+ </el-alert>
|
|
|
+
|
|
|
+ <!-- 核算概览区 -->
|
|
|
+ <el-row :gutter="16" class="mb16">
|
|
|
+ <el-col :span="6" v-for="(card, idx) in summaryCards" :key="card.key">
|
|
|
+ <el-card shadow="hover" class="summary-card" :class="`summary-card--${idx}`">
|
|
|
+ <div class="summary-card__label">{{ card.label }}</div>
|
|
|
+ <div class="summary-card__value">{{ card.value }}</div>
|
|
|
+ <div class="summary-card__unit" v-if="card.unit">{{ card.unit }}</div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <!-- 图表区域 -->
|
|
|
+ <el-row :gutter="20" class="mb16">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-card shadow="hover">
|
|
|
+ <PerformanceChart
|
|
|
+ type="bar"
|
|
|
+ :xData="barChartData.deptNames"
|
|
|
+ :seriesData="barChartData.series"
|
|
|
+ title="各部门当月绩效对比"
|
|
|
+ yName="万元"
|
|
|
+ :height="350"
|
|
|
+ />
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-card shadow="hover">
|
|
|
+ <PerformanceChart
|
|
|
+ type="line"
|
|
|
+ :xData="lineChartData.months"
|
|
|
+ :seriesData="lineChartData.series"
|
|
|
+ title="各部门绩效月度趋势"
|
|
|
+ yName="万元"
|
|
|
+ :height="350"
|
|
|
+ :smooth="true"
|
|
|
+ />
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <!-- 搜索区域 -->
|
|
|
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
|
|
|
- <el-form-item label="部门" prop="deptId">
|
|
|
- <el-tree-select v-model="queryParams.deptId" :data="deptOptions" :props="{ value: 'id', label: 'label', children: 'children' }" value-key="id" placeholder="请选择部门" clearable check-strictly style="width: 200px" />
|
|
|
+ <el-form-item label="部门" prop="deptId" v-if="isAdminView">
|
|
|
+ <DeptSelector v-model="queryParams.deptId" :useTree="false" placeholder="请选择部门" width="200px" />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="核算月份" prop="month">
|
|
|
<el-date-picker v-model="queryParams.month" type="month" value-format="YYYY-MM" placeholder="请选择月份" clearable style="width: 200px" />
|
|
|
@@ -15,50 +67,75 @@
|
|
|
|
|
|
<el-row :gutter="10" class="mb8">
|
|
|
<el-col :span="1.5">
|
|
|
- <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['performance:calculate:export']">导出</el-button>
|
|
|
- </el-col>
|
|
|
- <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
|
|
- </el-row>
|
|
|
-
|
|
|
- <!-- 绩效汇总卡片 -->
|
|
|
- <el-row :gutter="20" class="mb8">
|
|
|
- <el-col :span="6" v-for="card in summaryCards" :key="card.key">
|
|
|
- <el-card shadow="hover">
|
|
|
- <div class="summary-card">
|
|
|
- <div class="summary-card__label">{{ card.label }}</div>
|
|
|
- <div class="summary-card__value">{{ card.value }}</div>
|
|
|
- </div>
|
|
|
- </el-card>
|
|
|
+ <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['performance:calculate:export']">导出Excel</el-button>
|
|
|
</el-col>
|
|
|
- </el-row>
|
|
|
-
|
|
|
- <!-- 绩效核算图表 -->
|
|
|
- <el-row :gutter="20" class="mb8">
|
|
|
- <el-col :span="12">
|
|
|
- <el-card shadow="hover">
|
|
|
- <template #header><span>各部门当月绩效对比</span></template>
|
|
|
- <div ref="barChartRef" style="height: 350px"></div>
|
|
|
- </el-card>
|
|
|
- </el-col>
|
|
|
- <el-col :span="12">
|
|
|
- <el-card shadow="hover">
|
|
|
- <template #header><span>绩效月度趋势</span></template>
|
|
|
- <div ref="lineChartRef" style="height: 350px"></div>
|
|
|
- </el-card>
|
|
|
+ <el-col :span="1.5" v-if="isAdminView">
|
|
|
+ <el-button type="primary" plain icon="Setting" @click="handleRatioSetting" v-hasPermi="['performance:calculate:ratio']">核算比例设置</el-button>
|
|
|
</el-col>
|
|
|
+ <right-toolbar v-model:showSearch="showSearch" @queryTable="handleQuery"></right-toolbar>
|
|
|
</el-row>
|
|
|
|
|
|
- <el-table v-loading="loading" :data="calculateList" border>
|
|
|
- <el-table-column label="部门" align="center" prop="deptName" />
|
|
|
- <el-table-column label="核算月份" align="center" prop="month" />
|
|
|
- <el-table-column label="当月产值总和(万元)" align="center" prop="totalOutput" />
|
|
|
- <el-table-column label="核算比例(%)" align="center" prop="ratio" />
|
|
|
- <el-table-column label="绩效总额(万元)" align="center" prop="performanceAmount">
|
|
|
+ <!-- 核算明细表(支持展开) -->
|
|
|
+ <el-table
|
|
|
+ v-loading="loading"
|
|
|
+ :data="calculateList"
|
|
|
+ border
|
|
|
+ row-key="deptId"
|
|
|
+ @expand-change="handleExpandChange"
|
|
|
+ >
|
|
|
+ <el-table-column type="expand">
|
|
|
<template #default="scope">
|
|
|
- <span style="font-weight: bold; color: #409EFF">{{ scope.row.performanceAmount }}</span>
|
|
|
+ <div class="expand-projects">
|
|
|
+ <div class="expand-projects__title">{{ scope.row.deptName }} — 审核通过项目产值明细</div>
|
|
|
+ <el-table :data="scope.row.projects || []" border size="small" v-loading="scope.row._loading">
|
|
|
+ <el-table-column label="项目名称" align="center" prop="projectName" :show-overflow-tooltip="true" />
|
|
|
+ <el-table-column label="项目类型" align="center" width="100">
|
|
|
+ <template #default="s">{{ typeMap[s.row.projectType] || s.row.projectType }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="当月进度(%)" align="center" prop="currentProgress" width="110" />
|
|
|
+ <el-table-column label="当月产值(万元)" align="center" prop="currentOutput" width="130" />
|
|
|
+ <el-table-column label="协作产值(万元)" align="center" prop="coopOutput" width="130" />
|
|
|
+ <el-table-column label="审核结果" align="center" width="100">
|
|
|
+ <template #default="s">
|
|
|
+ <el-tag :type="s.row.reviewResult === 'APPROVED' ? 'success' : 'danger'" size="small">
|
|
|
+ {{ s.row.reviewResult === 'APPROVED' ? '通过' : '不通过' }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="贡献绩效(万元)" align="center" width="120">
|
|
|
+ <template #default="s">
|
|
|
+ <span style="font-weight: bold; color: #409EFF">
|
|
|
+ {{ ((s.row.currentOutput || 0) * ((scope.row.ratio || 20) / 100)).toFixed(4) }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ <el-empty v-if="!scope.row.projects || scope.row.projects.length === 0" description="暂无明细数据" :image-size="60" />
|
|
|
+ </div>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="操作" align="center" width="100">
|
|
|
+ <el-table-column label="部门" align="center" prop="deptName" width="160" />
|
|
|
+ <el-table-column label="核算月份" align="center" prop="month" width="120" />
|
|
|
+ <el-table-column label="审核通过项目数" align="center" prop="approvedProjectCount" width="120" />
|
|
|
+ <el-table-column label="当月产值总和(万元)" align="center" prop="totalOutput" width="140">
|
|
|
+ <template #default="scope">
|
|
|
+ <span style="font-weight: 500">{{ scope.row.totalOutput }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="核算比例(%)" align="center" prop="ratio" width="100">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tag type="warning" effect="plain">{{ scope.row.ratio || 20 }}%</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="绩效总额(万元)" align="center" prop="performanceAmount" width="140">
|
|
|
+ <template #default="scope">
|
|
|
+ <span class="performance-amount">{{ scope.row.performanceAmount }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="核算时间" align="center" prop="calculateTime" width="160">
|
|
|
+ <template #default="scope">{{ parseTime(scope.row.createTime) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" align="center" width="80">
|
|
|
<template #default="scope">
|
|
|
<el-button link type="primary" icon="View" @click="handleDetail(scope.row)">详情</el-button>
|
|
|
</template>
|
|
|
@@ -66,83 +143,130 @@
|
|
|
</el-table>
|
|
|
|
|
|
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
|
|
+
|
|
|
+ <!-- 核算比例设置对话框 -->
|
|
|
+ <el-dialog title="核算比例设置" v-model="ratioOpen" width="450px" append-to-body>
|
|
|
+ <el-form :model="ratioForm" ref="ratioRef" label-width="120px">
|
|
|
+ <el-form-item label="绩效核算比例(%)" prop="ratio">
|
|
|
+ <el-input-number v-model="ratioForm.ratio" :min="1" :max="100" :precision="2" style="width: 100%" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-alert title="注意" type="warning" :closable="false" show-icon>
|
|
|
+ 修改核算比例将重新计算所有部门的绩效总额,请谨慎操作。历史核算结果不受影响。
|
|
|
+ </el-alert>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <div class="dialog-footer">
|
|
|
+ <el-button type="primary" @click="submitRatio">确认修改</el-button>
|
|
|
+ <el-button @click="ratioOpen = false">取 消</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup name="PerformanceCalculate">
|
|
|
-import * as echarts from 'echarts'
|
|
|
-import { listCalculate, getCalculateSummary } from '@/api/performance/calculate'
|
|
|
+import { listCalculate, getCalculateSummary, getCalculateChart, updateCalculateRatio } from '@/api/performance/calculate'
|
|
|
+import { PerformanceChart, DeptSelector } from '@/components/performance'
|
|
|
+import useUserStore from '@/store/modules/user'
|
|
|
|
|
|
const { proxy } = getCurrentInstance()
|
|
|
+const router = useRouter()
|
|
|
|
|
|
+// 权限判断
|
|
|
+const userStore = useUserStore()
|
|
|
+const isAdminView = computed(() => userStore.roles.includes('admin') || userStore.roles.includes('ROLE_ADMIN'))
|
|
|
+
|
|
|
+const typeMap = {
|
|
|
+ 'ENGINEERING': '工程项目',
|
|
|
+ 'TECHNICAL_SERVICE': '技术服务',
|
|
|
+ 'CONSULTING': '咨询服务',
|
|
|
+ 'OTHER': '其他'
|
|
|
+}
|
|
|
+
|
|
|
+// 默认核算比例(从配置获取)
|
|
|
+const performanceRatio = ref(20)
|
|
|
+
|
|
|
+// 状态变量
|
|
|
const calculateList = ref([])
|
|
|
-const loading = ref(true)
|
|
|
+const loading = ref(false)
|
|
|
const showSearch = ref(true)
|
|
|
const total = ref(0)
|
|
|
-const deptOptions = ref([])
|
|
|
-const barChartRef = ref(null)
|
|
|
-const lineChartRef = ref(null)
|
|
|
-let barChart = null
|
|
|
-let lineChart = null
|
|
|
+const ratioOpen = ref(false)
|
|
|
|
|
|
const summaryCards = ref([
|
|
|
- { key: 'totalOutput', label: '全院当月产值总和(万元)', value: '--' },
|
|
|
- { key: 'totalPerformance', label: '全院当月绩效总额(万元)', value: '--' },
|
|
|
- { key: 'deptCount', label: '已核算部门数', value: '--' },
|
|
|
- { key: 'projectCount', label: '已核算项目数', value: '--' }
|
|
|
+ { key: 'totalOutput', label: '全院当月审核通过产值总和', value: '--', unit: '万元' },
|
|
|
+ { key: 'totalPerformance', label: '全院当月绩效总额', value: '--', unit: '万元' },
|
|
|
+ { key: 'deptCount', label: '已核算部门数', value: '--', unit: '' },
|
|
|
+ { key: 'approvedProjectCount', label: '审核通过项目总数', value: '--', unit: '个' }
|
|
|
])
|
|
|
|
|
|
+const barChartData = reactive({
|
|
|
+ deptNames: [],
|
|
|
+ series: [{ name: '绩效总额(万元)', data: [], color: '#409EFF' }]
|
|
|
+})
|
|
|
+
|
|
|
+const lineChartData = reactive({
|
|
|
+ months: [],
|
|
|
+ series: []
|
|
|
+})
|
|
|
+
|
|
|
const data = reactive({
|
|
|
queryParams: {
|
|
|
pageNum: 1,
|
|
|
pageSize: 10,
|
|
|
deptId: undefined,
|
|
|
month: undefined
|
|
|
- }
|
|
|
+ },
|
|
|
+ ratioForm: { ratio: 20 }
|
|
|
})
|
|
|
|
|
|
-const { queryParams } = toRefs(data)
|
|
|
+const { queryParams, ratioForm } = toRefs(data)
|
|
|
|
|
|
+// 数据加载
|
|
|
function getList() {
|
|
|
loading.value = true
|
|
|
listCalculate(queryParams.value).then(res => {
|
|
|
- calculateList.value = res.rows
|
|
|
- total.value = res.total
|
|
|
+ const rows = (res.rows || []).map(row => ({
|
|
|
+ ...row,
|
|
|
+ _loading: false,
|
|
|
+ projects: []
|
|
|
+ }))
|
|
|
+ calculateList.value = rows
|
|
|
+ total.value = res.total || 0
|
|
|
loading.value = false
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function getSummary() {
|
|
|
getCalculateSummary(queryParams.value).then(res => {
|
|
|
- const data = res.data
|
|
|
+ const d = res.data || {}
|
|
|
summaryCards.value = [
|
|
|
- { key: 'totalOutput', label: '全院当月产值总和(万元)', value: data.totalOutput || '--' },
|
|
|
- { key: 'totalPerformance', label: '全院当月绩效总额(万元)', value: data.totalPerformance || '--' },
|
|
|
- { key: 'deptCount', label: '已核算部门数', value: data.deptCount || '--' },
|
|
|
- { key: 'projectCount', label: '已核算项目数', value: data.projectCount || '--' }
|
|
|
+ { key: 'totalOutput', label: '全院当月审核通过产值总和', value: d.totalOutput || '--', unit: '万元' },
|
|
|
+ { key: 'totalPerformance', label: '全院当月绩效总额', value: d.totalPerformance || '--', unit: '万元' },
|
|
|
+ { key: 'deptCount', label: '已核算部门数', value: d.deptCount || '--', unit: '' },
|
|
|
+ { key: 'approvedProjectCount', label: '审核通过项目总数', value: d.approvedProjectCount || '--', unit: '个' }
|
|
|
]
|
|
|
+ performanceRatio.value = d.ratio || 20
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-function initBarChart() {
|
|
|
- if (!barChartRef.value) return
|
|
|
- barChart = echarts.init(barChartRef.value)
|
|
|
- barChart.setOption({
|
|
|
- tooltip: { trigger: 'axis' },
|
|
|
- xAxis: { type: 'category', data: [] },
|
|
|
- yAxis: { type: 'value', name: '绩效总额(万元)' },
|
|
|
- series: [{ type: 'bar', data: [], itemStyle: { color: '#409EFF' } }]
|
|
|
- })
|
|
|
-}
|
|
|
+function getChart() {
|
|
|
+ getCalculateChart(queryParams.value).then(res => {
|
|
|
+ const d = res.data || {}
|
|
|
|
|
|
-function initLineChart() {
|
|
|
- if (!lineChartRef.value) return
|
|
|
- lineChart = echarts.init(lineChartRef.value)
|
|
|
- lineChart.setOption({
|
|
|
- tooltip: { trigger: 'axis' },
|
|
|
- xAxis: { type: 'category', data: [] },
|
|
|
- yAxis: { type: 'value', name: '绩效总额(万元)' },
|
|
|
- series: [{ type: 'line', data: [], smooth: true, itemStyle: { color: '#67C23A' } }]
|
|
|
+ // 柱状图:各部门当月绩效对比
|
|
|
+ barChartData.deptNames = d.barDeptNames || []
|
|
|
+ barChartData.series = [
|
|
|
+ { name: '绩效总额(万元)', data: d.barPerformanceData || [], color: '#409EFF' }
|
|
|
+ ]
|
|
|
+
|
|
|
+ // 折线图:历史趋势
|
|
|
+ lineChartData.months = d.lineMonths || []
|
|
|
+ lineChartData.series = (d.lineDeptSeries || []).map(s => ({
|
|
|
+ name: s.deptName,
|
|
|
+ data: s.data || [],
|
|
|
+ smooth: true
|
|
|
+ }))
|
|
|
})
|
|
|
}
|
|
|
|
|
|
@@ -150,6 +274,7 @@ function handleQuery() {
|
|
|
queryParams.value.pageNum = 1
|
|
|
getList()
|
|
|
getSummary()
|
|
|
+ getChart()
|
|
|
}
|
|
|
|
|
|
function resetQuery() {
|
|
|
@@ -157,41 +282,91 @@ function resetQuery() {
|
|
|
handleQuery()
|
|
|
}
|
|
|
|
|
|
+// 展开行加载项目明细
|
|
|
+function handleExpandChange(row, expandedRows) {
|
|
|
+ if (expandedRows.some(r => r.deptId === row.deptId)) {
|
|
|
+ row._loading = true
|
|
|
+ listCalculate({ deptId: row.deptId, month: row.month }).then(res => {
|
|
|
+ row.projects = (res.rows || []).flatMap(r => r.projects || [])
|
|
|
+ row._loading = false
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
function handleDetail(row) {
|
|
|
- // TODO: 查看部门绩效详情
|
|
|
+ router.push(`/performance/calculate-detail/index/${row.deptId}/${row.month}`)
|
|
|
}
|
|
|
|
|
|
function handleExport() {
|
|
|
proxy.download('/performance/calculate/export', { ...queryParams.value }, `calculate_${new Date().getTime()}.xlsx`)
|
|
|
}
|
|
|
|
|
|
-onMounted(() => {
|
|
|
- getList()
|
|
|
- getSummary()
|
|
|
- nextTick(() => {
|
|
|
- initBarChart()
|
|
|
- initLineChart()
|
|
|
+function handleRatioSetting() {
|
|
|
+ ratioForm.value.ratio = performanceRatio.value
|
|
|
+ ratioOpen.value = true
|
|
|
+}
|
|
|
+
|
|
|
+function submitRatio() {
|
|
|
+ updateCalculateRatio(ratioForm.value.ratio).then(() => {
|
|
|
+ proxy.$modal.msgSuccess('核算比例已更新,绩效核算结果将重新计算')
|
|
|
+ ratioOpen.value = false
|
|
|
+ performanceRatio.value = ratioForm.value.ratio
|
|
|
+ handleQuery()
|
|
|
})
|
|
|
-})
|
|
|
+}
|
|
|
|
|
|
-onUnmounted(() => {
|
|
|
- barChart?.dispose()
|
|
|
- lineChart?.dispose()
|
|
|
+onMounted(() => {
|
|
|
+ handleQuery()
|
|
|
})
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
+.mb16 {
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
.summary-card {
|
|
|
text-align: center;
|
|
|
+
|
|
|
+ &--0 { border-top: 3px solid #409EFF; }
|
|
|
+ &--1 { border-top: 3px solid #67C23A; }
|
|
|
+ &--2 { border-top: 3px solid #E6A23C; }
|
|
|
+ &--3 { border-top: 3px solid #F56C6C; }
|
|
|
+
|
|
|
&__label {
|
|
|
- font-size: 14px;
|
|
|
+ font-size: 13px;
|
|
|
color: #909399;
|
|
|
- margin-bottom: 8px;
|
|
|
+ margin-bottom: 10px;
|
|
|
}
|
|
|
+
|
|
|
&__value {
|
|
|
- font-size: 24px;
|
|
|
+ font-size: 26px;
|
|
|
font-weight: bold;
|
|
|
color: #303133;
|
|
|
}
|
|
|
+
|
|
|
+ &__unit {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #C0C4CC;
|
|
|
+ margin-top: 4px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.performance-amount {
|
|
|
+ font-weight: bold;
|
|
|
+ color: #409EFF;
|
|
|
+ font-size: 15px;
|
|
|
+}
|
|
|
+
|
|
|
+.expand-projects {
|
|
|
+ padding: 16px 20px;
|
|
|
+ background: #F5F7FA;
|
|
|
+
|
|
|
+ &__title {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ }
|
|
|
}
|
|
|
</style>
|