课时补助核算两个tab页

This commit is contained in:
2026-08-23 16:20:07 +08:00
parent 97562579dc
commit 370cb615d0
3 changed files with 277 additions and 226 deletions
+42
View File
@@ -0,0 +1,42 @@
import request from '@/utils/request'
// ======================== 课时补助核算:课时统计 / 课时费统计 ========================
// 接口前缀 /ks,经 vue.config.js 代理转发为 /api/ks;返回行为 HourStatisticsVO。
// 分页查询课时统计(支持 nd 年度、xq 学期序号、jyxm 教员姓名)
export function listHourStatistics(query) {
return request({
url: '/ks/hour-stat/list',
method: 'get',
params: query
})
}
// 导出课时统计 Excel(返回 Blob,文件名 课时统计.xlsx)
export function exportHourStatistics(query) {
return request({
url: '/ks/hour-stat/export',
method: 'get',
params: query,
responseType: 'blob'
})
}
// 分页查询课时费统计(支持 nd、xq、jyxm、ksfbzbh 课时费标准编号)
export function listFeeStatistics(query) {
return request({
url: '/ks/fee-stat/list',
method: 'get',
params: query
})
}
// 导出课时费统计 Excel(返回 Blob,文件名 课时费统计.xlsx)
export function exportFeeStatistics(query) {
return request({
url: '/ks/fee-stat/export',
method: 'get',
params: query,
responseType: 'blob'
})
}
+22 -14
View File
@@ -1,21 +1,27 @@
/** /**
* 全局视口等比缩放 —— 让整个系统在不同大小屏幕(桌面 1280~1920)下布局不崩塌。 * 全局视口等比缩放 —— 让整个系统在不同大小屏幕(桌面 1280~4K)下布局不崩塌。
* *
* 原理: * 原理:
* 以 DESIGN_WIDTH 为设计基准宽度(视觉稿基准)。当视口宽度小于该值时 * 以 DESIGN_WIDTH1920为设计基准宽度。将视口宽度与设计宽的比值作为缩放倍数
* 对整个文档根节点 <html> 应用 zoom = 视口宽度 / DESIGN_WIDTH(不超过 1), * 作用到文档根节点 <html> zoom 上:
* 使页面内部所有元素(含 Element UI 挂载到 body 的弹窗/提示/下拉等)等比缩小, * - 视口 < 设计宽(如 1280):zoom < 1,整体等比缩小,避免窄屏布局被撑破;
* 从而在保持原有表格/表单固定布局不塌陷的前提下,整体适配到当前屏幕。 * - 视口 = 设计宽(1920):zoom = 1,原生 1:1 显示;
* - 视口 > 设计宽(如 4K 3840):zoom = 2,整体等比放大,大屏不再留白/字小。
* 缩放倍数被限制在 MIN_SCALE ~ MAX_SCALE 之间,MAX_SCALE=2 恰好覆盖 4K3840/1920)。
* *
* 说明: * 采用 zoom 挂在 documentElement 上,可覆盖包括 Element UI 挂载到 body 的
* 1. 仅在“需要缩小”时写入 zoom,宽度 ≥ 设计宽时清除,保证大屏原生 1:1 显示; * 弹窗/提示/下拉选择在内的所有内容,保证整套界面一致缩放、不塌陷。
* 2. zoom 挂在 documentElement 上,能覆盖包括 Element 弹层在内的所有内容; *
* 3. modern Chromium / Edge / Safari 及 Firefox(≥126) 均支持 zoom。 * 兼容性:modern Chromium / Edge / Safari 及 Firefox(≥126) 均支持 zoom。
*/ */
let initialized = false let initialized = false
export const DEFAULT_DESIGN_WIDTH = 1920 export const DEFAULT_DESIGN_WIDTH = 1920
// 最小缩放倍数:极窄视口的安全下限,避免文字过小/布局异常
export const MIN_SCALE = 0.5
// 最大缩放倍数:2 对应 4K(3840/1920=2),更高分辨率也封顶在此,避免过度放大
export const MAX_SCALE = 2
/** /**
* 初始化全局自适应。可在任意入口调用,重复调用幂等。 * 初始化全局自适应。可在任意入口调用,重复调用幂等。
@@ -31,12 +37,14 @@ export function initAdaptive(designWidth = DEFAULT_DESIGN_WIDTH) {
const applyScale = () => { const applyScale = () => {
const root = document.documentElement const root = document.documentElement
const width = (root && root.clientWidth) || window.innerWidth if (!root) {
const scale = Math.min(1, width / designWidth) return
// 小于设计宽才缩放,否则清除,避免触发整页缩放动画/重置
if (root) {
root.style.zoom = scale < 1 ? String(scale) : ''
} }
const width = root.clientWidth || window.innerWidth
const ratio = width / designWidth
// 收敛到 [MIN_SCALE, MAX_SCALE],规避极端宽高的异常比例
const scale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, ratio))
root.style.zoom = scale === 1 ? '' : String(scale)
} }
const onResize = () => { const onResize = () => {
+211 -210
View File
@@ -1,223 +1,250 @@
<template> <template>
<div class="app-container subsidy-page"> <div class="app-container subsidy-page">
<div class="list-title">课时补助核算</div> <div class="list-title">课时补助核算</div>
<div class="top-tip">{{ tipText }}</div>
<!-- ==================== 1. 新增核算区域 ==================== --> <!-- ==================== Tab 切换 ==================== -->
<el-card shadow="never" class="search-card"> <el-tabs v-model="activeTab" @tab-click="handleTabClick">
<div class="form-title">新增</div> <el-tab-pane label="课时统计" name="hour" />
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" class="search-form"> <el-tab-pane label="课时费统计" name="fee" />
<el-row :gutter="0"> </el-tabs>
<!-- 左栏 -->
<el-col :span="12"> <!-- ==================== 1. 查询条件 ==================== -->
<el-form-item label="学期名称" prop="semesterName"> <!-- 课时统计 -->
<el-input v-model="form.semesterName" placeholder="请输入学期名称" clearable /> <el-card v-if="activeTab === 'hour'" shadow="never" class="search-card">
</el-form-item> <el-form :model="hourForm" label-width="100px" class="search-form" @submit.native.prevent>
<el-form-item label="开始日期" prop="startDate"> <el-row :gutter="24">
<el-input v-model="form.startDate" placeholder="如:2016-03-01" clearable /> <el-col :xs="24" :sm="12" :md="6">
</el-form-item> <el-form-item label="学期年度">
<el-form-item label="课时补助标准"> <el-input v-model="hourForm.nd" placeholder="如 2026" clearable />
<el-select v-model="form.subsidyStandard" placeholder="请选择" class="w-full">
<el-option v-for="item in subsidyStandardOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="6">
<!-- 右栏 --> <el-form-item label="学期序号">
<el-col :span="12"> <el-input v-model="hourForm.xq" placeholder="如 1" clearable />
<el-form-item label="任务类别">
<el-radio-group v-model="form.taskCategory">
<el-radio v-for="item in taskCategoryOptions" :key="item.value" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label="结束日期" prop="endDate"> </el-col>
<el-input v-model="form.endDate" placeholder="如:2016-08-31" clearable /> <el-col :xs="24" :sm="12" :md="6">
</el-form-item> <el-form-item label="教员姓名">
<el-form-item label="课时核算标准"> <el-input v-model="hourForm.jyxm" placeholder="请输入教员姓名" clearable @keyup.enter.native="handleQuery" />
<el-select v-model="form.accountStandard" placeholder="请选择" class="w-full">
<el-option v-for="item in accountStandardOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="6" class="search-actions-col">
<div class="search-actions">
<el-button type="primary" @click="handleQuery">查询</el-button>
<el-button type="primary" plain @click="handleExport">导出 Excel</el-button>
</div>
</el-col>
</el-row> </el-row>
</el-form>
</el-card>
<!-- 课时费统计 -->
<el-card v-else shadow="never" class="search-card">
<el-form :model="feeForm" label-width="100px" class="search-form" @submit.native.prevent>
<el-row :gutter="24">
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="学期年度">
<el-input v-model="feeForm.nd" placeholder="如 2026" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="学期序号">
<el-input v-model="feeForm.xq" placeholder="如 1" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="教员姓名">
<el-input v-model="feeForm.jyxm" placeholder="请输入教员姓名" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="标准编号">
<el-input v-model="feeForm.ksfbzbh" placeholder="课时费标准编号" clearable />
</el-form-item>
</el-col>
<el-col :span="24" class="search-actions-col">
<div class="search-actions"> <div class="search-actions">
<el-button type="primary" :loading="addLoading" @click="handleAdd">添加</el-button> <el-button type="primary" @click="handleQuery">查询</el-button>
<el-button type="primary" plain @click="handleExport">导出 Excel</el-button>
</div> </div>
</el-col>
</el-row>
</el-form> </el-form>
</el-card> </el-card>
<!-- ==================== 2. 数据表格 ==================== --> <!-- ==================== 2. 数据表格 ==================== -->
<el-card shadow="never" class="table-card"> <el-card shadow="never" class="table-card">
<el-table v-loading="queryLoading" :data="tableData" border stripe style="width: 100%"> <div class="table-title">{{ activeTab === 'hour' ? '课时统计列表' : '课时费统计列表' }}</div>
<el-table-column label="详细" width="80" fixed align="center"> <el-table v-loading="currentState.loading" :data="currentState.list" border stripe style="width: 100%">
<template slot-scope="{ row }"> <el-table-column type="index" label="序号" width="55" align="center" />
<el-button type="text" @click="handleDetail(row)">详细</el-button> <el-table-column prop="jysdh" label="教研室代号" width="100" align="center" show-overflow-tooltip />
</template> <el-table-column prop="jysmc" label="教研室名称" width="120" show-overflow-tooltip />
<el-table-column prop="jybh" label="教员编号" width="110" align="center" show-overflow-tooltip />
<el-table-column prop="jyxm" label="教员姓名" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="zc" label="职称" width="90" align="center" show-overflow-tooltip />
<el-table-column prop="nf" label="年份" width="70" align="center" />
<el-table-column prop="sbn" label="上半年" width="90" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.sbn) }}</template>
</el-table-column> </el-table-column>
<el-table-column prop="semesterName" label="学期名称" width="120" /> <el-table-column prop="xbn" label="下半年" width="90" align="right">
<el-table-column prop="taskCategory" label="任务类别" width="180" /> <template slot-scope="{ row }">{{ fmtValue(row.xbn) }}</template>
<el-table-column prop="startDate" label="核算开始日期" width="120" /> </el-table-column>
<el-table-column prop="endDate" label="核算结束日期" width="120" /> <el-table-column prop="zj" label="总计" width="90" align="right">
<el-table-column prop="totalHours" label="总课时量" width="100" align="right" /> <template slot-scope="{ row }">{{ fmtValue(row.zj) }}</template>
<el-table-column prop="qualityHours" label="优质课时量" width="100" align="right" /> </el-table-column>
<el-table-column prop="qualityRatio" label="优质课时量比例" width="120" align="right" /> <el-table-column prop="bzksl" label="标准课时量" width="100" align="right">
<el-table-column prop="totalSubsidy" label="总补助金额" width="120" align="right" /> <template slot-scope="{ row }">{{ fmtValue(row.bzksl) }}</template>
<el-table-column prop="accountStandard" label="课时核算标准" show-overflow-tooltip /> </el-table-column>
<el-table-column prop="subsidyStandard" label="课时补助标准" width="180" show-overflow-tooltip /> <el-table-column prop="cks" label="课时" width="90" align="right">
<el-table-column prop="status" label="状态" width="100" align="center"> <template slot-scope="{ row }">{{ fmtValue(row.cks) }}</template>
<template slot-scope="{ row }"> </el-table-column>
<span :class="row.status === '已核算' ? 'status-done' : 'status-doing'"> <el-table-column prop="jsgs" label="计算过程" min-width="140" show-overflow-tooltip />
{{ row.status }} <el-table-column prop="jg" label="结果" width="90" align="right">
</span> <template slot-scope="{ row }">{{ fmtValue(row.jg) }}</template>
</template> </el-table-column>
<el-table-column prop="ksfbzmc" label="课时费标准" width="110" align="center" show-overflow-tooltip />
<el-table-column prop="ksfbz" label="课时费单价" width="100" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.ksfbz) }}</template>
</el-table-column>
<el-table-column prop="clksfbz" label="超量课时费单价" width="110" align="right">
<template slot-scope="{ row }">{{ fmtValue(row.clksfbz) }}</template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<div class="table-footer"> <el-pagination
<span>总记录数{{ tableData.length }} </span> :current-page="currentState.pageNum"
<span>总课时量{{ totalHours }}</span> :page-size="currentState.pageSize"
<span>优质课时量{{ totalQualityHours }}</span> :total="currentState.total"
<span>总补助金额{{ totalSubsidy }} </span> :page-sizes="[10, 20, 50, 100]"
</div> layout="total, sizes, prev, pager, next, jumper"
class="pagination"
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</el-card> </el-card>
<!-- ==================== 3. 详情对话框 ==================== -->
<el-dialog
:visible="detailVisible"
title="课时补助核算详情"
width="560px"
:close-on-click-modal="false"
@update:visible="val => detailVisible = val"
>
<el-descriptions v-if="detailRow" :column="1" border>
<el-descriptions-item label="学期名称">{{ detailRow.semesterName }}</el-descriptions-item>
<el-descriptions-item label="任务类别">{{ detailRow.taskCategory }}</el-descriptions-item>
<el-descriptions-item label="核算开始日期">{{ detailRow.startDate }}</el-descriptions-item>
<el-descriptions-item label="核算结束日期">{{ detailRow.endDate }}</el-descriptions-item>
<el-descriptions-item label="总课时量">{{ detailRow.totalHours }}</el-descriptions-item>
<el-descriptions-item label="优质课时量">{{ detailRow.qualityHours }}</el-descriptions-item>
<el-descriptions-item label="优质课时量比例">{{ detailRow.qualityRatio }}</el-descriptions-item>
<el-descriptions-item label="总补助金额">{{ detailRow.totalSubsidy }} </el-descriptions-item>
<el-descriptions-item label="课时核算标准">{{ detailRow.accountStandard }}</el-descriptions-item>
<el-descriptions-item label="课时补助标准">{{ detailRow.subsidyStandard }}</el-descriptions-item>
<el-descriptions-item label="状态">{{ detailRow.status }}</el-descriptions-item>
</el-descriptions>
<div slot="footer">
<el-button @click="detailVisible = false">关闭</el-button>
</div>
</el-dialog>
</div> </div>
</template> </template>
<script> <script>
// 模拟核算列表数据(后端接口未提供,接口就绪后可删除并改为接口加载) import { saveAs } from 'file-saver'
const mockSummaryData = [ import {
{ bh: 'H001', mc: '2022年秋季学期课时补助核算', rwlb: '全军院校训练规划内任务', hsksrq: '2022-09-01T00:00:00', hsjsrq: '2023-01-15T00:00:00', zksl: 1200, yzksl: 800, yzkslbl: 0.6667, zbzje: 24000, zt: '已核算' }, listHourStatistics,
{ bh: 'H002', mc: '2022年春季学期课时补助核算', rwlb: '其他任务(含研究生导师指导)', hsksrq: '2022-03-01T00:00:00', hsjsrq: '2022-08-31T00:00:00', zksl: 900, yzksl: 540, yzkslbl: 0.6, zbzje: 18000, zt: '核算中' }, exportHourStatistics,
{ bh: 'H003', mc: '2021年秋季学期课时补助核算', rwlb: '全军院校训练规划内任务', hsksrq: '2021-09-01T00:00:00', hsjsrq: '2022-01-15T00:00:00', zksl: 1100, yzksl: 770, yzkslbl: 0.7, zbzje: 22000, zt: '已核算' } listFeeStatistics,
] exportFeeStatistics
} from '@/api/classHour/hourStat'
export default { export default {
name: 'SubsidyIndex', name: 'SubsidyIndex',
data() { data() {
return { return {
// ==================== 顶部提示条 ==================== // ==================== Tab ====================
tipText: '提示:"课时核算"和"补助核算"要花费稍微长一点时间,操作后请耐心等待系统"操作成功"的提示!', activeTab: 'hour',
// ==================== 下拉选项 ==================== // ==================== 课时统计查询条件 ====================
subsidyStandardOptions: ['2022年秋季学期', '2022年春季学期', '2021年秋季学期', '2021年春季学期'], hourForm: {
accountStandardOptions: ['2022年秋季学期课时补助核算', '2022年春季学期课时补助核算', '2021年秋季学期课时补助核算', '2021年春季学期课时补助核算'], nd: '',
taskCategoryOptions: [ xq: '',
{ label: '全军院校训练规划内任务', value: '全军院校训练规划内任务' }, jyxm: ''
{ label: '其他任务(含研究生导师指导)', value: '其他任务(含研究生导师指导)' }
],
// ==================== 新增表单 ====================
form: {
semesterName: '',
taskCategory: '全军院校训练规划内任务',
startDate: '',
endDate: '',
subsidyStandard: '2022年秋季学期',
accountStandard: '2022年秋季学期课时补助核算'
}, },
rules: {
semesterName: [{ required: true, message: '请输入学期名称', trigger: 'blur' }], // ==================== 课时费统计查询条件 ====================
startDate: [{ required: true, message: '请输入开始日期', trigger: 'blur' }], feeForm: {
endDate: [{ required: true, message: '请输入结束日期', trigger: 'blur' }] nd: '',
xq: '',
jyxm: '',
ksfbzbh: ''
}, },
addLoading: false,
// ==================== 表格数据 ==================== // ==================== 各 Tab 列表状态 ====================
tableData: [], hourState: this.createEmptyState(),
queryLoading: false, feeState: this.createEmptyState()
// ==================== 详情对话框 ====================
detailVisible: false,
detailRow: null
} }
}, },
computed: { computed: {
totalHours() { currentForm() {
return this.tableData.reduce((sum, r) => sum + Number(r.totalHours || 0), 0) return this.activeTab === 'hour' ? this.hourForm : this.feeForm
}, },
totalQualityHours() { currentState() {
return this.tableData.reduce((sum, r) => sum + Number(r.qualityHours || 0), 0) return this.activeTab === 'hour' ? this.hourState : this.feeState
},
totalSubsidy() {
return this.tableData.reduce((sum, r) => sum + Number(r.totalSubsidy || 0), 0)
} }
}, },
mounted() { mounted() {
this.handleQuery() this.fetchList()
}, },
methods: { methods: {
// ==================== 查询核算列表 ==================== createEmptyState() {
// TODO: 后端接口未提供,暂用模拟数据;接口就绪后替换为 /log/summary/list return { list: [], loading: false, pageNum: 1, pageSize: 20, total: 0 }
handleQuery() {
this.queryLoading = true
setTimeout(() => {
this.tableData = mockSummaryData.map((item, idx) => ({
id: idx + 1,
_bh: item.bh || '',
semesterName: item.mc || '-',
taskCategory: item.rwlb || '-',
startDate: (item.hsksrq || '').substring(0, 10) || '-',
endDate: (item.hsjsrq || '').substring(0, 10) || '-',
totalHours: item.zksl || 0,
qualityHours: item.yzksl || 0,
qualityRatio: item.zksl > 0 ? ((item.yzkslbl || 0) * 100).toFixed(2) + '%' : '0.00%',
totalSubsidy: item.zbzje || 0,
accountStandard: item.mc || '-',
subsidyStandard: '-',
status: item.zt || '-'
}))
this.$message.success(`查询完成,共 ${this.tableData.length} 条记录(前端模拟)`)
this.queryLoading = false
}, 200)
}, },
// ==================== 添加 ==================== /** 构建查询参数,仅传非空字段(与后端 Query 字段一致) */
// TODO: 后端接口未提供,提交为前端模拟;接口就绪后替换为 /log/subsidy-project/add buildParams(form) {
handleAdd() { const params = {}
this.$refs.formRef.validate((valid) => { if (form.nd && form.nd.trim()) params.nd = form.nd.trim()
if (!valid) { if (form.xq && form.xq.trim()) params.xq = form.xq.trim()
this.$message.warning('请完善必填项后再提交') if (form.jyxm && form.jyxm.trim()) params.jyxm = form.jyxm.trim()
return if (form.ksfbzbh && form.ksfbzbh.trim()) params.ksfbzbh = form.ksfbzbh.trim()
} return params
this.addLoading = true },
setTimeout(() => {
this.$message.success('核算提交成功(前端模拟)') // ==================== 查询 ====================
this.handleQuery() fetchList() {
this.addLoading = false const state = this.currentState
}, 300) const form = this.currentForm
const params = Object.assign(
{ pageNum: state.pageNum, pageSize: state.pageSize },
this.buildParams(form)
)
state.loading = true
const req = this.activeTab === 'hour' ? listHourStatistics(params) : listFeeStatistics(params)
req.then(res => {
const data = (res && res.data) || {}
state.list = data.records || []
state.total = data.total || 0
}).catch(() => {
state.list = []
state.total = 0
}).finally(() => {
state.loading = false
}) })
}, },
// ==================== 详情对话框 ==================== handleTabClick() {
handleDetail(row) { this.currentState.pageNum = 1
this.detailRow = row this.fetchList()
this.detailVisible = true },
handleQuery() {
this.currentState.pageNum = 1
this.fetchList()
},
handlePageChange(page) {
this.currentState.pageNum = page
this.fetchList()
},
handleSizeChange(size) {
this.currentState.pageSize = size
this.currentState.pageNum = 1
this.fetchList()
},
// ==================== 导出 ====================
handleExport() {
const clean = this.buildParams(this.currentForm)
const isHour = this.activeTab === 'hour'
const req = isHour ? exportHourStatistics(clean) : exportFeeStatistics(clean)
const fileName = isHour ? '课时统计.xlsx' : '课时费统计.xlsx'
req.then(blob => {
saveAs(blob, fileName)
this.$message.success('导出成功')
}).catch(() => {})
},
/** 数值空值显示为 '-'(保留 0 */
fmtValue(val) {
return val === null || val === undefined ? '-' : val
} }
} }
} }
@@ -236,63 +263,37 @@ export default {
margin-bottom: 12px; margin-bottom: 12px;
} }
.top-tip { // ==================== 1. 查询条件区域 ====================
color: #f56c6c;
font-size: 13px;
margin-bottom: 16px;
line-height: 1.6;
}
.search-card { .search-card {
margin-bottom: 16px; margin-bottom: 16px;
.form-title {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #ebeef5;
}
.search-form { .search-form {
.w-full { .search-actions-col {
width: 100%; display: flex;
align-items: center;
} }
.search-actions { .search-actions {
display: flex; display: flex;
justify-content: flex-end; gap: 12px;
margin-top: 8px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
} }
} }
} }
// ==================== 2. 数据表格 ====================
.table-card { .table-card {
.el-table { .table-title {
width: 100%; font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
} }
.table-footer { .pagination {
display: flex; display: flex;
flex-wrap: wrap; justify-content: flex-end;
gap: 24px;
margin-top: 12px; margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
font-size: 12px;
color: #606266;
} }
} }
.status-done {
color: #67c23a;
}
.status-doing {
color: #e6a23c;
}
} }
</style> </style>