准备验证

This commit is contained in:
2026-01-19 12:49:30 +08:00
parent 083d9d5c13
commit 41c2b106d7
100 changed files with 21014 additions and 31 deletions

View File

@@ -0,0 +1,178 @@
<template>
<el-dialog
title="编辑评语"
v-model="visible"
:width="700"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="学号">
<el-input v-model="form.stuNo" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="姓名">
<el-input v-model="form.realName" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="学年">
<el-input v-model="form.schoolYear" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="学期">
<el-input v-model="form.schoolTerm" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="评语" prop="comment">
<el-input
v-model="form.comment"
type="textarea"
:rows="6"
placeholder="请输入评语"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="家长寄语" prop="parentalMsg">
<el-input
v-model="form.parentalMsg"
type="textarea"
:rows="6"
placeholder="请输入家长寄语"
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="QualityReportEditDialog">
import { ref, reactive, nextTick } from 'vue'
import { useMessage } from '/@/hooks/message'
import { updatePY } from '/@/api/ems/qualityReport'
import { getDicts } from '/@/api/admin/dict'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const schoolTermList = ref<any[]>([])
// 提交表单数据
const form = reactive({
stuNo: '',
realName: '',
schoolYear: '',
schoolTerm: '',
comment: '',
parentalMsg: ''
})
// 定义校验规则
const dataRules = {
// comment 和 parentalMsg 非必填,不需要验证规则
}
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 打开弹窗
const openDialog = async (row: any) => {
visible.value = true
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.stuNo = row.stuNo || ''
form.realName = row.realName || ''
form.schoolYear = row.schoolYear || ''
form.schoolTerm = row.schoolTerm || ''
form.comment = row.comment || ''
form.parentalMsg = row.parentalMsg || ''
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
const submitData = {
stuNo: form.stuNo,
comment: form.comment || '',
parentalMsg: form.parentalMsg || '',
schoolYear: form.schoolYear,
schoolTerm: form.schoolTerm
}
await updatePY(submitData)
useMessage().success('保存成功')
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || '保存失败')
} finally {
loading.value = false
}
})
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 初始化
getSchoolTermDict()
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,508 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="state.queryForm.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 200px"
@change="handleDeptChange">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学号" prop="stuNo">
<el-input
v-model="state.queryForm.stuNo"
placeholder="请输入学号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="state.queryForm.realName"
placeholder="请输入姓名"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="state.queryForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="state.queryForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班级" prop="classCode">
<el-select
v-model="state.queryForm.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Upload"
type="primary"
class="ml10"
@click="handleImportComment">
评语导入
</el-button>
<el-button
icon="Calendar"
type="primary"
class="ml10"
@click="handleSetClassStartTime">
设置班级开学时间
</el-button>
<el-button
icon="Download"
type="primary"
class="ml10"
@click="handleExport">
名单导出
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip align="center" />
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" />
<el-table-column prop="comment" label="评语" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="parentalMsg" label="家长寄语" show-overflow-tooltip align="center" min-width="200" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 编辑评语弹窗 -->
<edit-dialog ref="editDialogRef" @refresh="getDataList" />
<!-- 评语导入弹窗 -->
<el-dialog
title="评语导入"
v-model="importDialogVisible"
:width="500"
:close-on-click-modal="false"
draggable>
<el-upload
ref="uploadRef"
:auto-upload="false"
:on-change="handleFileChange"
:limit="1"
accept=".xlsx,.xls"
drag>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
只能上传 xlsx/xls 文件
</div>
</template>
</el-upload>
<template #footer>
<span class="dialog-footer">
<el-button @click="importDialogVisible = false"> </el-button>
<el-button type="primary" @click="handleImportSubmit" :disabled="!importFile || importLoading"> </el-button>
</span>
</template>
</el-dialog>
<!-- 设置班级开学时间弹窗 -->
<el-dialog
title="设置班级开学时间"
v-model="startTimeDialogVisible"
:width="500"
:close-on-click-modal="false"
draggable>
<el-form
ref="startTimeFormRef"
:model="startTimeForm"
:rules="startTimeRules"
label-width="120px"
v-loading="startTimeLoading">
<el-form-item label="班级" prop="classCode">
<el-select
v-model="startTimeForm.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="开学时间" prop="startTime">
<el-date-picker
v-model="startTimeForm.startTime"
type="date"
placeholder="选择开学时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="startTimeDialogVisible = false"> </el-button>
<el-button type="primary" @click="handleStartTimeSubmit" :disabled="startTimeLoading"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="QualityReport">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, importComment, setClassStartTime, exportExcel } from "/@/api/ems/qualityReport";
import { getDeptList } from "/@/api/basic/basicclass";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { useMessage } from "/@/hooks/message";
import { UploadFilled } from '@element-plus/icons-vue'
import EditDialog from './edit.vue'
// 定义变量内容
const editDialogRef = ref()
const uploadRef = ref()
const startTimeFormRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const importDialogVisible = ref(false)
const importFile = ref<File | null>(null)
const importLoading = ref(false)
const startTimeDialogVisible = ref(false)
const startTimeLoading = ref(false)
// 设置班级开学时间表单
const startTimeForm = reactive({
classCode: '',
startTime: ''
})
// 设置班级开学时间表单验证规则
const startTimeRules = {
classCode: [
{ required: true, message: '请选择班级', trigger: 'change' }
],
startTime: [
{ required: true, message: '请选择开学时间', trigger: 'change' }
]
}
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
deptCode: '',
stuNo: '',
realName: '',
schoolYear: '',
schoolTerm: '',
classCode: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true // 页面加载时自动获取数据
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 学院变化
const handleDeptChange = () => {
// 可以根据学院筛选班级,这里暂时不实现
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.deptCode = ''
state.queryForm.stuNo = ''
state.queryForm.realName = ''
state.queryForm.schoolYear = ''
state.queryForm.schoolTerm = ''
state.queryForm.classCode = ''
getDataList()
}
// 评语导入
const handleImportComment = () => {
importDialogVisible.value = true
importFile.value = null
uploadRef.value?.clearFiles()
}
// 文件变化
const handleFileChange = (file: any) => {
importFile.value = file.raw
}
// 提交导入
const handleImportSubmit = async () => {
if (!importFile.value) {
useMessage().warning('请选择要导入的文件')
return
}
importLoading.value = true
try {
await importComment(importFile.value)
useMessage().success('导入成功')
importDialogVisible.value = false
importFile.value = null
uploadRef.value?.clearFiles()
getDataList()
} catch (err: any) {
useMessage().error(err.msg || '导入失败')
} finally {
importLoading.value = false
}
}
// 设置班级开学时间
const handleSetClassStartTime = () => {
startTimeDialogVisible.value = true
startTimeForm.classCode = ''
startTimeForm.startTime = ''
startTimeFormRef.value?.resetFields()
}
// 提交设置班级开学时间
const handleStartTimeSubmit = async () => {
if (!startTimeFormRef.value) return
await startTimeFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
startTimeLoading.value = true
try {
await setClassStartTime({
classCode: startTimeForm.classCode,
startTime: startTimeForm.startTime
})
useMessage().success('设置成功')
startTimeDialogVisible.value = false
} catch (err: any) {
useMessage().error(err.msg || '设置失败')
} finally {
startTimeLoading.value = false
}
})
}
// 名单导出
const handleExport = async () => {
try {
const res = await exportExcel(state.queryForm)
// 创建blob对象
const blob = new Blob([res as unknown as BlobPart], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
// 创建下载链接
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
// 设置文件名
const fileName = `素质报告单名单_${new Date().getTime()}.xlsx`
link.setAttribute('download', fileName)
// 触发下载
document.body.appendChild(link)
link.click()
// 清理
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
useMessage().success('导出成功')
} catch (err: any) {
console.error('导出失败', err)
useMessage().error(err.msg || '导出失败')
}
}
// 编辑
const handleEdit = (row: any) => {
editDialogRef.value?.openDialog(row)
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptList()
if (res.data && Array.isArray(res.data)) {
deptList.value = res.data
} else {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getDeptListData()
getClassListData()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,268 @@
<template>
<el-dialog
:title="form.id ? '编辑获奖活动信息' : '新增获奖活动信息'"
v-model="visible"
:width="700"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="活动主题" prop="activityInfoId">
<el-select
v-model="form.activityInfoId"
placeholder="请选择活动主题"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in activityInfoList"
:key="item.id"
:label="item.activityTheme"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学号" prop="userName">
<el-input
v-model="form.userName"
placeholder="请输入学号"
clearable
@blur="handleStuNoBlur"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="姓名" prop="realName">
<el-input
v-model="form.realName"
placeholder="请输入姓名"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="获奖信息" prop="awards">
<el-input
v-model="form.awards"
type="textarea"
:rows="4"
placeholder="请输入获奖信息"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="获奖时间" prop="awardTime">
<el-date-picker
v-model="form.awardTime"
type="date"
placeholder="请选择获奖时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="备注" prop="remarks">
<el-input
v-model="form.remarks"
type="textarea"
:rows="4"
placeholder="请输入备注"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ActivityAwardsFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail, getActivityInfoList } from '/@/api/stuwork/activityawards'
import { queryAllStudentByStuNo } from '/@/api/basic/basicstudent'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add')
const activityInfoList = ref<any[]>([])
// 提交表单数据
const form = reactive({
id: '',
activityInfoId: '',
userName: '',
realName: '',
awards: '',
awardTime: '',
remarks: ''
})
// 定义校验规则
const dataRules = {
activityInfoId: [
{ required: true, message: '请选择活动主题', trigger: 'change' }
],
userName: [
{ required: true, message: '请输入学号', trigger: 'blur' }
],
realName: [
{ required: true, message: '请输入姓名', trigger: 'blur' }
],
awards: [
{ required: true, message: '请输入获奖信息', trigger: 'blur' }
],
awardTime: [
{ required: true, message: '请选择获奖时间', trigger: 'change' }
]
}
// 学号失焦时自动填充姓名
const handleStuNoBlur = async () => {
if (!form.userName) return
try {
const res = await queryAllStudentByStuNo({ stuNo: form.userName })
if (res.data && res.data.length > 0) {
const student = res.data[0]
form.realName = student.realName || ''
}
} catch (err) {
console.error('查询学生信息失败', err)
// 不显示错误,允许手动输入姓名
}
}
// 获取活动主题列表
const getActivityInfoListData = async () => {
try {
const res = await getActivityInfoList()
if (res.data && Array.isArray(res.data)) {
activityInfoList.value = res.data
} else {
activityInfoList.value = []
}
} catch (err) {
console.error('获取活动主题列表失败', err)
activityInfoList.value = []
}
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
operType.value = type
visible.value = true
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.activityInfoId = ''
form.userName = ''
form.realName = ''
form.awards = ''
form.awardTime = ''
form.remarks = ''
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.activityInfoId = row.activityInfoId || ''
form.userName = row.userName || ''
form.realName = row.realName || ''
form.awards = row.awards || ''
form.awardTime = row.awardTime || row.month || ''
form.remarks = row.remarks || ''
// 如果需要获取详情
if (row.id && (!row.userName || !row.awards)) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.activityInfoId = res.data.activityInfoId || ''
form.userName = res.data.userName || ''
form.realName = res.data.realName || ''
form.awards = res.data.awards || ''
form.awardTime = res.data.awardTime || res.data.month || ''
form.remarks = res.data.remarks || ''
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (valid) {
loading.value = true
try {
const submitData = {
activityInfoId: form.activityInfoId,
userName: form.userName,
realName: form.realName,
awards: form.awards,
awardTime: form.awardTime,
remarks: form.remarks || ''
}
if (operType.value === 'edit' && form.id) {
await editObj({ ...submitData, id: form.id })
useMessage().success('编辑成功')
} else {
await addObj(submitData)
useMessage().success('新增成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'edit' ? '编辑失败' : '新增失败'))
} finally {
loading.value = false
}
}
})
}
// 初始化
onMounted(() => {
getActivityInfoListData()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,245 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<el-button
icon="Upload"
type="primary"
class="ml10"
@click="handleImport">
导入
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="activityTheme" label="活动主题" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="userName" label="学号" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" width="100" />
<el-table-column prop="awards" label="获奖信息" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="awardTime" label="获奖时间" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ parseTime(scope.row.awardTime || scope.row.month, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="备注" show-overflow-tooltip align="center" min-width="200" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
<!-- 导入弹窗 -->
<el-dialog
title="导入获奖活动信息"
v-model="importDialogVisible"
:width="500"
:close-on-click-modal="false"
draggable>
<div style="margin-bottom: 15px;">
<el-button
icon="Download"
type="success"
@click="handleDownloadTemplate">
下载模板
</el-button>
</div>
<el-upload
ref="uploadRef"
:auto-upload="false"
:on-change="handleFileChange"
:limit="1"
accept=".xlsx,.xls"
drag>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
只能上传 xlsx/xls 文件
</div>
</template>
</el-upload>
<template #footer>
<span class="dialog-footer">
<el-button @click="importDialogVisible = false"> </el-button>
<el-button type="primary" @click="handleImportSubmit" :disabled="!importFile || importLoading"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="ActivityAwards">
import { reactive, ref } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, importExcel } from "/@/api/stuwork/activityawards";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
import { UploadFilled } from '@element-plus/icons-vue'
import FormDialog from './form.vue'
// 定义变量内容
const formDialogRef = ref()
const uploadRef = ref()
const showSearch = ref(false)
const importDialogVisible = ref(false)
const importFile = ref<File | null>(null)
const importLoading = ref(false)
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true // 页面加载时自动获取数据
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该获奖活动信息吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 导入
const handleImport = () => {
importDialogVisible.value = true
importFile.value = null
uploadRef.value?.clearFiles()
}
// 下载模板
const handleDownloadTemplate = async () => {
try {
const fileName = '获奖活动信息导入模板.xlsx'
// 使用动态导入获取文件URL从 views/stuwork/activityawards 到 assets/file 的相对路径
const fileUrl = new URL(`../../../assets/file/${fileName}`, import.meta.url).href
const response = await fetch(fileUrl)
if (!response.ok) {
throw new Error('文件下载失败')
}
const blob = await response.blob()
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = fileName
document.body.appendChild(link)
link.click()
window.URL.revokeObjectURL(url)
document.body.removeChild(link)
useMessage().success('模板下载成功')
} catch (error) {
console.error('模板下载失败', error)
useMessage().error('模板下载失败,请检查模板文件是否存在')
}
}
// 文件变化
const handleFileChange = (file: any) => {
importFile.value = file.raw
}
// 提交导入
const handleImportSubmit = async () => {
if (!importFile.value) {
useMessage().warning('请选择要导入的文件')
return
}
importLoading.value = true
try {
await importExcel(importFile.value)
useMessage().success('导入成功')
importDialogVisible.value = false
importFile.value = null
uploadRef.value?.clearFiles()
getDataList()
} catch (err: any) {
useMessage().error(err.msg || '导入失败')
} finally {
importLoading.value = false
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,76 @@
<template>
<el-dialog
title="查看详情"
v-model="visible"
:close-on-click-modal="false"
draggable
width="800px">
<div v-loading="loading" class="detail-container" v-if="detailData">
<el-descriptions :column="2" border>
<el-descriptions-item label="活动主题" :span="2">
{{ detailData.activityTheme || '-' }}
</el-descriptions-item>
<el-descriptions-item label="活动说明" :span="2">
{{ detailData.remarks || '-' }}
</el-descriptions-item>
<el-descriptions-item label="活动兼报数">
{{ detailData.maxSub !== undefined && detailData.maxSub !== null ? detailData.maxSub : '-' }}
</el-descriptions-item>
<el-descriptions-item label="开始时间">
{{ parseTime(detailData.startTime, '{y}-{m}-{d}') }}
</el-descriptions-item>
<el-descriptions-item label="结束时间">
{{ parseTime(detailData.endTime, '{y}-{m}-{d}') }}
</el-descriptions-item>
</el-descriptions>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ActivityInfoDetailDialog">
import { ref } from 'vue'
import { getDetail } from '/@/api/stuwork/activityinfo'
import { parseTime } from '/@/utils/formatTime'
import { useMessage } from '/@/hooks/message'
// 定义变量内容
const visible = ref(false)
const loading = ref(false)
const detailData = ref<any>({})
// 打开弹窗
const openDialog = async (id: string) => {
visible.value = true
loading.value = true
detailData.value = {}
try {
const res = await getDetail(id)
if (res.data) {
detailData.value = res.data
}
} catch (err: any) {
useMessage().error(err.msg || '获取详情失败')
visible.value = false
} finally {
loading.value = false
}
}
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.detail-container {
padding: 20px 0;
}
</style>

View File

@@ -0,0 +1,167 @@
<template>
<el-dialog
:title="form.id ? '编辑活动' : '新增活动'"
v-model="visible"
:width="700"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="活动主题" prop="activityTheme">
<el-input
v-model="form.activityTheme"
placeholder="请输入活动主题"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="活动说明" prop="remarks">
<el-input
v-model="form.remarks"
type="textarea"
:rows="4"
placeholder="请输入活动说明"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="活动兼报数" prop="maxSub">
<el-input-number
v-model="form.maxSub"
:min="0"
:max="100"
placeholder="请输入活动兼报数"
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ActivityInfoFormDialog">
import { ref, reactive, nextTick } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/activityinfo'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add')
// 提交表单数据
const form = reactive({
id: '',
activityTheme: '',
remarks: '',
maxSub: undefined as number | undefined
})
// 定义校验规则
const dataRules = {
activityTheme: [
{ required: true, message: '请输入活动主题', trigger: 'blur' }
],
remarks: [
{ required: true, message: '请输入活动说明', trigger: 'blur' }
]
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
operType.value = type
visible.value = true
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.activityTheme = ''
form.remarks = ''
form.maxSub = undefined
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.activityTheme = row.activityTheme || ''
form.remarks = row.remarks || ''
form.maxSub = row.maxSub !== undefined && row.maxSub !== null ? row.maxSub : undefined
// 如果需要获取详情
if (row.id && (!row.activityTheme || !row.remarks)) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.activityTheme = res.data.activityTheme || ''
form.remarks = res.data.remarks || ''
form.maxSub = res.data.maxSub !== undefined && res.data.maxSub !== null ? res.data.maxSub : undefined
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (valid) {
loading.value = true
try {
const submitData = {
activityTheme: form.activityTheme,
remarks: form.remarks,
maxSub: form.maxSub !== undefined && form.maxSub !== null ? form.maxSub : undefined
}
if (operType.value === 'edit' && form.id) {
await editObj({ ...submitData, id: form.id })
useMessage().success('编辑成功')
} else {
await addObj(submitData)
useMessage().success('新增成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'edit' ? '编辑失败' : '新增失败'))
} finally {
loading.value = false
}
}
})
}
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,244 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="activityTheme" label="活动主题" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="remarks" label="活动说明" show-overflow-tooltip align="center" min-width="300" />
<el-table-column prop="maxSub" label="活动兼报数" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ scope.row.maxSub || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="startTime" label="开始时间" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column prop="endTime" label="结束时间" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="300" align="center" fixed="right">
<template #default="scope">
<el-button
icon="View"
text
type="primary"
@click="handleView(scope.row)">
查看详情
</el-button>
<el-button
icon="Upload"
text
type="success"
@click="handleImportSub(scope.row)">
导入子项目
</el-button>
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
<!-- 查看详情弹窗 -->
<detail-dialog ref="detailDialogRef" />
<!-- 导入子项目弹窗 -->
<el-dialog
title="导入子项目"
v-model="importDialogVisible"
:width="500"
:close-on-click-modal="false"
draggable>
<div style="margin-bottom: 15px;">
<el-text>活动主题{{ currentActivity?.activityTheme || '-' }}</el-text>
</div>
<el-upload
ref="uploadRef"
:auto-upload="false"
:on-change="handleFileChange"
:limit="1"
accept=".xlsx,.xls"
drag>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
只能上传 xlsx/xls 文件
</div>
</template>
</el-upload>
<template #footer>
<span class="dialog-footer">
<el-button @click="importDialogVisible = false"> </el-button>
<el-button type="primary" @click="handleImportSubmit" :disabled="!importFile || importLoading"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="ActivityInfo">
import { reactive, ref } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, importSub } from "/@/api/stuwork/activityinfo";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
import { UploadFilled } from '@element-plus/icons-vue'
import FormDialog from './form.vue'
import DetailDialog from './detail.vue'
// 定义变量内容
const formDialogRef = ref()
const detailDialogRef = ref()
const uploadRef = ref()
const showSearch = ref(false)
const importDialogVisible = ref(false)
const importFile = ref<File | null>(null)
const importLoading = ref(false)
const currentActivity = ref<any>(null)
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true // 页面加载时自动获取数据
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 查看详情
const handleView = (row: any) => {
detailDialogRef.value?.openDialog(row.id)
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该活动吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 导入子项目
const handleImportSub = (row: any) => {
currentActivity.value = row
importFile.value = null
importDialogVisible.value = true
uploadRef.value?.clearFiles()
}
// 文件变化
const handleFileChange = (file: any) => {
importFile.value = file.raw
}
// 提交导入
const handleImportSubmit = async () => {
if (!importFile.value) {
useMessage().warning('请选择要上传的文件!')
return
}
if (!currentActivity.value?.id) {
useMessage().warning('活动信息不存在!')
return
}
importLoading.value = true
try {
await importSub(currentActivity.value.id, importFile.value)
useMessage().success('导入成功')
importDialogVisible.value = false
importFile.value = null
uploadRef.value?.clearFiles()
} catch (err: any) {
useMessage().error(err.msg || '导入失败')
} finally {
importLoading.value = false
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,174 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="活动主题" prop="activityInfoId">
<el-select
v-model="state.queryForm.activityInfoId"
placeholder="请选择活动主题"
clearable
filterable
style="width: 300px">
<el-option
v-for="item in activityInfoList"
:key="item.id"
:label="item.activityTheme"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="activityTheme" label="活动主题" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="subTitle" label="子项目名称" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="projectDescription" label="项目描述" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="startTime" label="开始时间" show-overflow-tooltip align="center" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column prop="endTime" label="结束时间" show-overflow-tooltip align="center" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column prop="maxNum" label="报名人数限制" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ scope.row.maxNum !== undefined && scope.row.maxNum !== null ? scope.row.maxNum : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="活动说明" show-overflow-tooltip align="center" min-width="200" />
<el-table-column label="操作" width="100" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
</div>
</template>
<script setup lang="ts" name="ActivityInfoSub">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, getActivityInfoList } from "/@/api/stuwork/activityinfosub";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
// 定义变量内容
const searchFormRef = ref()
const showSearch = ref(true)
const activityInfoList = ref<any[]>([])
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
activityInfoId: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true // 页面加载时自动获取数据
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.activityInfoId = ''
getDataList()
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该活动子项目吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取活动主题列表
const getActivityInfoListData = async () => {
try {
const res = await getActivityInfoList()
if (res.data && Array.isArray(res.data)) {
activityInfoList.value = res.data
} else {
activityInfoList.value = []
}
} catch (err) {
console.error('获取活动主题列表失败', err)
activityInfoList.value = []
}
}
// 初始化
onMounted(() => {
getActivityInfoListData()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,260 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="活动主题" prop="activityInfoId">
<el-select
v-model="state.queryForm.activityInfoId"
placeholder="请选择活动主题"
clearable
filterable
style="width: 300px"
@change="handleActivityChange">
<el-option
v-for="item in activityInfoList"
:key="item.id"
:label="item.activityTheme"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="子项目" prop="activityInfoSubId">
<el-select
v-model="state.queryForm.activityInfoSubId"
placeholder="请选择子项目"
clearable
filterable
style="width: 300px">
<el-option
v-for="item in filteredSubList"
:key="item.id"
:label="item.subTitle"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学号/工号" prop="userName">
<el-input
v-model="state.queryForm.userName"
placeholder="请输入学号/工号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Download"
type="primary"
class="ml10"
@click="handleExport"
:loading="exportLoading">
导出
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="activityTheme" label="活动主题" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="subTitle" label="子项目" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="remarks" label="项目描述" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="userName" label="学号/工号" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" width="100" />
<el-table-column prop="deptName" label="学院名称" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="classMasterName" label="班主任" show-overflow-tooltip align="center" width="100" />
<el-table-column prop="phone" label="联系电话" show-overflow-tooltip align="center" width="120" />
<el-table-column label="操作" width="100" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
</div>
</template>
<script setup lang="ts" name="ActivityInfoSubSignup">
import { reactive, ref, onMounted, computed } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, exportExcel, getActivityInfoList, getActivityInfoSubList } from "/@/api/stuwork/activityinfosubsignup";
import { useMessage, useMessageBox } from "/@/hooks/message";
// 定义变量内容
const searchFormRef = ref()
const showSearch = ref(true)
const exportLoading = ref(false)
const activityInfoList = ref<any[]>([])
const subList = ref<any[]>([])
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
activityInfoId: '',
activityInfoSubId: '',
userName: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true // 页面加载时自动获取数据
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 根据活动主题过滤子项目列表
const filteredSubList = computed(() => {
if (!state.queryForm.activityInfoId) {
return subList.value
}
return subList.value.filter((item: any) => item.activityInfoId === state.queryForm.activityInfoId)
})
// 活动主题变化时,清空子项目选择并重新加载子项目列表
const handleActivityChange = async () => {
state.queryForm.activityInfoSubId = ''
if (state.queryForm.activityInfoId) {
await getSubListData(state.queryForm.activityInfoId)
} else {
await getSubListData()
}
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.activityInfoId = ''
state.queryForm.activityInfoSubId = ''
state.queryForm.userName = ''
getDataList()
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该活动报名记录吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 导出
const handleExport = async () => {
exportLoading.value = true
try {
const res = await exportExcel(state.queryForm)
// 创建 Blob 对象
const blob = new Blob([res.data as BlobPart], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
// 创建下载链接
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `活动报名表_${new Date().getTime()}.xlsx`
document.body.appendChild(link)
link.click()
window.URL.revokeObjectURL(url)
document.body.removeChild(link)
useMessage().success('导出成功')
} catch (err: any) {
useMessage().error(err.msg || '导出失败')
} finally {
exportLoading.value = false
}
}
// 获取活动主题列表
const getActivityInfoListData = async () => {
try {
const res = await getActivityInfoList()
if (res.data && Array.isArray(res.data)) {
activityInfoList.value = res.data
} else {
activityInfoList.value = []
}
} catch (err) {
console.error('获取活动主题列表失败', err)
activityInfoList.value = []
}
}
// 获取子项目列表
const getSubListData = async (activityInfoId?: string) => {
try {
const res = await getActivityInfoSubList(activityInfoId)
if (res.data && Array.isArray(res.data)) {
subList.value = res.data
} else {
subList.value = []
}
} catch (err) {
console.error('获取子项目列表失败', err)
subList.value = []
}
}
// 初始化
onMounted(() => {
getActivityInfoListData()
getSubListData()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,165 @@
<template>
<el-dialog
:title="form.id ? '编辑考核奖项' : '新增考核奖项'"
v-model="visible"
:width="600"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="100px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="考核项名称" prop="category">
<el-input
v-model="form.category"
placeholder="请输入考核项名称"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="考核类型" prop="type">
<el-select
v-model="form.type"
placeholder="请选择考核类型"
clearable
style="width: 100%">
<el-option
v-for="item in typeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="AssessmentCategoryFormDialog">
import { ref, reactive, nextTick } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/assessmentcategory'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add')
// 考核类型列表
const typeList = ref([
{ label: '常规考核', value: '0' },
{ label: '顶岗考核', value: '1' }
])
// 提交表单数据
const form = reactive({
id: '',
category: '',
type: ''
})
// 定义校验规则
const dataRules = {
category: [
{ required: true, message: '请输入考核项名称', trigger: 'blur' }
],
type: [
{ required: true, message: '请选择考核类型', trigger: 'change' }
]
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.category = ''
form.type = ''
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.category = row.category || ''
form.type = row.type || ''
// 如果需要获取详情
if (row.id && (!row.category || !row.type)) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.category = res.data.category || ''
form.type = res.data.type || ''
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
const submitData = {
category: form.category,
type: form.type
}
if (operType.value === 'add') {
await addObj(submitData)
useMessage().success('新增成功')
} else {
await editObj({
id: form.id,
...submitData
})
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,178 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="考核类型" prop="type">
<el-select
v-model="state.queryForm.type"
placeholder="请选择考核类型"
clearable
style="width: 200px">
<el-option
v-for="item in typeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="category" label="考核项名称" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="type" label="考核类型" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatType(scope.row.type) }}</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="备注" show-overflow-tooltip align="center" min-width="200" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="AssessmentCategory">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/assessmentcategory";
import { useMessage, useMessageBox } from "/@/hooks/message";
import FormDialog from './form.vue'
// 定义变量内容
const formDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
// 考核类型列表
const typeList = ref([
{ label: '常规考核', value: '0' },
{ label: '顶岗考核', value: '1' }
])
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
type: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true // 页面加载时自动获取数据
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 格式化考核类型
const formatType = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = typeList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.type = ''
getDataList()
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该考核奖项吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,214 @@
<template>
<el-dialog
:title="form.id ? '编辑考核指标' : '新增考核指标'"
v-model="visible"
:width="700"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="考核项" prop="categortyId">
<el-select
v-model="form.categortyId"
placeholder="请选择考核项"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in categoryList"
:key="item.id"
:label="item.category"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="指标名称" prop="pointName">
<el-input
v-model="form.pointName"
placeholder="请输入指标名称"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="评分标准" prop="standard">
<el-input
v-model="form.standard"
type="textarea"
:rows="4"
placeholder="请输入评分标准"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="备注" prop="remarks">
<el-input
v-model="form.remarks"
type="textarea"
:rows="4"
placeholder="请输入备注"
:maxlength="250"
show-word-limit
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="AssessmentPointFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/assessmentpoint'
import { getList } from '/@/api/stuwork/assessmentcategory'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add')
const categoryList = ref<any[]>([])
// 提交表单数据
const form = reactive({
id: '',
categortyId: '',
pointName: '',
standard: '',
remarks: ''
})
// 定义校验规则
const dataRules = {
categortyId: [
{ required: true, message: '请选择考核项', trigger: 'change' }
],
pointName: [
{ required: true, message: '请输入指标名称', trigger: 'blur' }
]
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.categortyId = ''
form.pointName = ''
form.standard = ''
form.remarks = ''
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.categortyId = row.categortyId || ''
form.pointName = row.pointName || ''
form.standard = row.standard || ''
form.remarks = row.remarks || ''
// 如果需要获取详情
if (row.id && (!row.pointName || !row.standard)) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.categortyId = res.data.categortyId || ''
form.pointName = res.data.pointName || ''
form.standard = res.data.standard || ''
form.remarks = res.data.remarks || ''
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
const submitData = {
categortyId: form.categortyId,
pointName: form.pointName,
standard: form.standard || '',
remarks: form.remarks || ''
}
if (operType.value === 'add') {
await addObj(submitData)
useMessage().success('新增成功')
} else {
await editObj({
id: form.id,
...submitData
})
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 获取考核项列表
const getCategoryList = async () => {
try {
const res = await getList()
if (res.data && Array.isArray(res.data)) {
categoryList.value = res.data
} else {
categoryList.value = []
}
} catch (err) {
console.error('获取考核项列表失败', err)
categoryList.value = []
}
}
// 初始化
onMounted(() => {
getCategoryList()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,159 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="指标名称" prop="pointName">
<el-input
v-model="state.queryForm.pointName"
placeholder="请输入指标名称"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="categoryName" label="考核项" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="pointName" label="指标名称" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="standard" label="评分标准" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="score" label="默认扣分值" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="remarks" label="备注" show-overflow-tooltip align="center" min-width="200" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="AssessmentPoint">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/assessmentpoint";
import { useMessage, useMessageBox } from "/@/hooks/message";
import FormDialog from './form.vue'
// 定义变量内容
const formDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
pointName: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true // 页面加载时自动获取数据
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.pointName = ''
getDataList()
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该考核指标吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 初始化
onMounted(() => {
// 初始化完成
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,335 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
:width="900"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="班号" prop="classCode">
<el-select
v-model="form.classCode"
placeholder="请选择班号"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="活动主题" prop="themeName">
<el-input
v-model="form.themeName"
placeholder="请输入活动主题"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="主持人" prop="author">
<el-input
v-model="form.author"
placeholder="请输入主持人"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="活动时间" prop="recordDate">
<el-date-picker
v-model="form.recordDate"
type="date"
placeholder="选择活动时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="活动地点" prop="address">
<el-input
v-model="form.address"
placeholder="请输入活动地点"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="参加人数" prop="attendNum">
<el-input-number
v-model="form.attendNum"
:min="0"
placeholder="请输入参加人数"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="活动内容" prop="content">
<Editor
v-model:getHtml="form.content"
:height="300"
placeholder="请输入活动内容" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="活动图片" prop="attachment">
<upload-file
v-model="form.attachment"
:limit="5"
:fileSize="10"
type="simple" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="活动图片2" prop="attachment2">
<upload-file
v-model="form.attachment2"
:limit="5"
:fileSize="10"
type="simple" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ClassActivityFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/classactivity'
import { getClassListByRole } from '/@/api/basic/basicclass'
import { queryAllSchoolYear } from '/@/api/basic/basicyear'
import { getDicts } from '/@/api/admin/dict'
import { CURRENT_SCHOOL_YEAR, CURRENT_SCHOOL_TERM } from '/@/config/global'
import Editor from '/@/components/Editor/index.vue'
import UploadFile from '/@/components/Upload/index.vue'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add')
const classList = ref<any[]>([])
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const currentSchoolYear = ref('')
const currentSchoolTerm = ref('')
// 提交表单数据
const form = reactive({
id: '',
classCode: '',
themeName: '',
author: '',
recordDate: '',
address: '',
attendNum: 0,
content: '',
attachment: '',
attachment2: ''
})
// 定义校验规则
const dataRules = {
classCode: [
{ required: true, message: '请选择班号', trigger: 'change' }
],
themeName: [
{ required: true, message: '请输入活动主题', trigger: 'blur' }
],
author: [
{ required: true, message: '请输入主持人', trigger: 'blur' }
],
address: [
{ required: true, message: '请输入活动地点', trigger: 'blur' }
],
attendNum: [
{ required: true, message: '请输入参加人数', trigger: 'blur' }
]
// activityTime 和 content 非必填,不需要验证规则
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.classCode = ''
form.themeName = ''
form.author = ''
form.recordDate = ''
form.address = ''
form.attendNum = 0
form.content = ''
form.attachment = ''
form.attachment2 = ''
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.classCode = row.classCode || ''
form.themeName = row.themeName || ''
form.author = row.author || ''
form.recordDate = row.activityTime || row.recordDate || ''
form.address = row.address || ''
form.attendNum = row.attendNum || 0
form.content = row.content || ''
form.attachment = row.attachment || ''
form.attachment2 = row.attachment2 || ''
// 如果需要获取详情
if (row.id && !row.content) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.content = res.data.content || ''
form.attachment = res.data.attachment || ''
form.attachment2 = res.data.attachment2 || ''
}
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
const submitData = {
classCode: form.classCode,
schoolYear: currentSchoolYear.value || CURRENT_SCHOOL_YEAR, // 使用当前学年
schoolTerm: currentSchoolTerm.value || CURRENT_SCHOOL_TERM, // 使用当前学期
themeName: form.themeName,
author: form.author,
recordDate: form.recordDate || '',
address: form.address,
attendNum: String(form.attendNum || 0), // API文档示例中是字符串类型
content: form.content || '',
attachment: form.attachment || '',
attachment2: form.attachment2 || ''
}
if (operType.value === 'add') {
await addObj(submitData)
useMessage().success('新增成功')
} else {
await editObj({
id: form.id,
...submitData
})
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
}
} catch (err) {
console.error('获取班级列表失败', err)
}
}
// 获取学年列表并设置当前学年
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
// 设置当前学年(如果列表中有的话)
const currentYear = schoolYearList.value.find((item: any) => item.year === CURRENT_SCHOOL_YEAR)
currentSchoolYear.value = currentYear ? currentYear.year : (schoolYearList.value.length > 0 ? schoolYearList.value[0].year : CURRENT_SCHOOL_YEAR)
} else {
currentSchoolYear.value = CURRENT_SCHOOL_YEAR
}
} catch (err) {
console.error('获取学年列表失败', err)
currentSchoolYear.value = CURRENT_SCHOOL_YEAR
}
}
// 获取学期字典并设置当前学期
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
// 设置当前学期
const currentTerm = schoolTermList.value.find((item: any) => item.value === CURRENT_SCHOOL_TERM)
currentSchoolTerm.value = currentTerm ? currentTerm.value : (schoolTermList.value.length > 0 ? schoolTermList.value[0].value : CURRENT_SCHOOL_TERM)
} else {
currentSchoolTerm.value = CURRENT_SCHOOL_TERM
}
} catch (err) {
console.error('获取学期字典失败', err)
currentSchoolTerm.value = CURRENT_SCHOOL_TERM
}
}
// 初始化
onMounted(() => {
getClassListData()
getSchoolYearList()
getSchoolTermDict()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.mb20 {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,316 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="state.queryForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="state.queryForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班号" prop="classCode">
<el-select
v-model="state.queryForm.classCode"
placeholder="请选择班号"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center" />
<el-table-column prop="themeName" label="活动主题" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="author" label="主持人" show-overflow-tooltip align="center" />
<el-table-column prop="activityTime" label="活动时间" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ scope.row.activityTime || scope.row.recordDate || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="address" label="活动地点" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="attendNum" label="参加人数" show-overflow-tooltip align="center" />
<el-table-column prop="attachment" label="活动图片" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<el-button
v-if="scope.row.attachment"
icon="Picture"
text
type="primary"
size="small"
@click="handleViewImage(scope.row.attachment)">
查看
</el-button>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="attachment2" label="活动图片2" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<el-button
v-if="scope.row.attachment2"
icon="Picture"
text
type="primary"
size="small"
@click="handleViewImage(scope.row.attachment2)">
查看
</el-button>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="ClassActivity">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/classactivity";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import FormDialog from './form.vue'
// 定义变量内容
const formDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const classList = ref<any[]>([])
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
schoolYear: '',
schoolTerm: '',
classCode: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true // 页面加载时自动获取数据
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.schoolYear = ''
state.queryForm.schoolTerm = ''
state.queryForm.classCode = ''
getDataList()
}
// 查看图片
const handleViewImage = (url: string) => {
if (url) {
const urls = typeof url === 'string' ? url.split(',') : [url]
urls.forEach((imgUrl: string) => {
if (imgUrl.trim()) {
window.open(imgUrl.trim(), '_blank')
}
})
}
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该活动记录吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getClassListData()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,368 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
:width="800"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="form.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 100%"
:disabled="!!form.id">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="form.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 100%"
:disabled="!!form.id">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="班级" prop="classCode">
<el-select
v-model="form.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="发生时间" prop="operatTime">
<el-date-picker
v-model="form.operatTime"
type="date"
placeholder="选择发生时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="类型" prop="type">
<el-select
v-model="form.type"
placeholder="请选择类型"
clearable
style="width: 100%">
<el-option
v-for="item in typeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="金额" prop="money">
<el-input-number
v-model="form.money"
:min="0"
:precision="2"
placeholder="请输入金额"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="经办人" prop="operator">
<el-input
v-model="form.operator"
placeholder="请输入经办人"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="用途" prop="purpose">
<el-input
v-model="form.purpose"
type="textarea"
:rows="4"
placeholder="请输入用途"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="附件" prop="attachment">
<upload-file
v-model="form.attachment"
:limit="5"
:fileSize="10"
type="simple" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ClassFeeLogFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/classfeelog'
import { getClassListByRole } from '/@/api/basic/basicclass'
import { queryAllSchoolYear } from '/@/api/basic/basicyear'
import { getDicts } from '/@/api/admin/dict'
import UploadFile from '/@/components/Upload/index.vue'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add')
const classList = ref<any[]>([])
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const typeList = ref<any[]>([])
// 提交表单数据
const form = reactive({
id: '',
schoolYear: '',
schoolTerm: '',
classCode: '',
operatTime: '',
type: '',
money: 0,
operator: '',
purpose: '',
attachment: ''
})
// 定义校验规则
const dataRules = {
schoolYear: [
{ required: true, message: '请选择学年', trigger: 'change' }
],
schoolTerm: [
{ required: true, message: '请选择学期', trigger: 'change' }
],
classCode: [
{ required: true, message: '请选择班级', trigger: 'change' }
],
operatTime: [
{ required: true, message: '请选择发生时间', trigger: 'change' }
],
type: [
{ required: true, message: '请选择类型', trigger: 'change' }
],
money: [
{ required: true, message: '请输入金额', trigger: 'blur' }
],
operator: [
{ required: true, message: '请输入经办人', trigger: 'blur' }
],
purpose: [
{ required: true, message: '请输入用途', trigger: 'blur' }
]
// attachment 非必填,不需要验证规则
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.schoolYear = ''
form.schoolTerm = ''
form.classCode = ''
form.operatTime = ''
form.type = ''
form.money = 0
form.operator = ''
form.purpose = ''
form.attachment = ''
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.schoolYear = row.schoolYear || ''
form.schoolTerm = row.schoolTerm || ''
form.classCode = row.classCode || ''
form.operatTime = row.operatTime || ''
form.type = row.type || ''
form.money = row.money || 0
form.operator = row.operator || ''
form.purpose = row.purpose || ''
form.attachment = row.attachment || ''
// 如果需要获取详情
if (row.id && (!row.purpose || !row.attachment)) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.attachment = res.data.attachment || ''
}
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
const submitData = {
schoolYear: form.schoolYear,
schoolTerm: form.schoolTerm,
classCode: form.classCode,
operatTime: form.operatTime,
type: form.type,
money: form.money,
operator: form.operator,
purpose: form.purpose,
attachment: form.attachment || ''
}
if (operType.value === 'add') {
await addObj(submitData)
useMessage().success('新增成功')
} else {
await editObj({
id: form.id,
...submitData
})
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
}
} catch (err) {
console.error('获取学年列表失败', err)
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
}
} catch (err) {
console.error('获取学期字典失败', err)
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
}
} catch (err) {
console.error('获取班级列表失败', err)
}
}
// 获取类型字典
const getTypeDict = async () => {
try {
const res = await getDicts('class_fee_type')
if (res.data && Array.isArray(res.data)) {
typeList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
}
} catch (err) {
console.error('获取类型字典失败', err)
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getClassListData()
getTypeDict()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.mb20 {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,430 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="state.queryForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="state.queryForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="state.queryForm.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 200px"
@change="handleDeptChange">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班级" prop="classCode">
<el-select
v-model="state.queryForm.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select
v-model="state.queryForm.type"
placeholder="请选择类型"
clearable
style="width: 200px">
<el-option
v-for="item in typeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<el-button
icon="Download"
type="primary"
class="ml10"
@click="handleExport">
导出
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" />
<el-table-column prop="classNo" label="班级" show-overflow-tooltip align="center" />
<el-table-column prop="operatTime" label="发生时间" show-overflow-tooltip align="center" width="180">
<template #default="scope">
<span>{{ scope.row.operatTime || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="type" label="类型" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatType(scope.row.type) }}</span>
</template>
</el-table-column>
<el-table-column prop="money" label="金额" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ scope.row.money !== null && scope.row.money !== undefined ? scope.row.money.toFixed(2) : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="operator" label="经办人" show-overflow-tooltip align="center" />
<el-table-column prop="purpose" label="用途" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="attachment" label="附件" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<el-button
v-if="scope.row.attachment"
icon="Document"
text
type="primary"
size="small"
@click="handleViewAttachment(scope.row)">
查看
</el-button>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="ClassFeeLog">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, exportExcel } from "/@/api/stuwork/classfeelog";
import { getDeptList } from "/@/api/basic/basicclass";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import FormDialog from './form.vue'
// 定义变量内容
const formDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const typeList = ref<any[]>([])
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
schoolYear: '',
schoolTerm: '',
deptCode: '',
classCode: '',
type: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化类型
const formatType = (value: string) => {
if (!value) return '-'
const item = typeList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 学院变化
const handleDeptChange = () => {
// 可以根据学院筛选班级,这里暂时不实现
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.schoolYear = ''
state.queryForm.schoolTerm = ''
state.queryForm.deptCode = ''
state.queryForm.classCode = ''
state.queryForm.type = ''
getDataList()
}
// 查看附件
const handleViewAttachment = (row: any) => {
if (row.attachment) {
const urls = typeof row.attachment === 'string' ? row.attachment.split(',') : [row.attachment]
urls.forEach((url: string) => {
if (url.trim()) {
window.open(url.trim(), '_blank')
}
})
}
}
// 导出
const handleExport = async () => {
try {
const res = await exportExcel(state.queryForm)
// 创建blob对象
const blob = new Blob([res], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
// 创建下载链接
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
// 设置文件名
const fileName = `班费记录_${new Date().getTime()}.xlsx`
link.setAttribute('download', fileName)
// 触发下载
document.body.appendChild(link)
link.click()
// 清理
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
useMessage().success('导出成功')
} catch (err: any) {
console.error('导出失败', err)
useMessage().error(err.msg || '导出失败')
}
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该班费记录吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptList()
if (res.data && Array.isArray(res.data)) {
deptList.value = res.data
} else {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取类型字典
const getTypeDict = async () => {
try {
const res = await getDicts('class_fee_type')
if (res.data && Array.isArray(res.data)) {
typeList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
typeList.value = []
}
} catch (err) {
console.error('获取类型字典失败', err)
typeList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getDeptListData()
getClassListData()
getTypeDict()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,179 @@
<template>
<el-dialog
title="归档"
v-model="visible"
:width="500"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="标题">
<el-input v-model="form.title" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="作者">
<el-input v-model="form.author" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="班号">
<el-input v-model="form.classNo" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="归档级别" prop="belong">
<el-select
v-model="form.belong"
placeholder="请选择归档级别"
clearable
style="width: 100%">
<el-option
v-for="item in belongList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ClassHonorBelongDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { editBelong, getDetail } from '/@/api/stuwork/classhonor'
import { getDicts } from '/@/api/admin/dict'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const belongList = ref<any[]>([])
// 提交表单数据
const form = reactive({
id: '',
title: '',
author: '',
classNo: '',
belong: ''
})
// 定义校验规则
const dataRules = {
belong: [
{ required: true, message: '请选择归档级别', trigger: 'change' }
]
}
// 打开弹窗
const openDialog = async (row: any) => {
visible.value = true
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.title = ''
form.author = ''
form.classNo = ''
form.belong = ''
if (row) {
form.id = row.id
form.title = row.title || ''
form.author = row.author || ''
form.classNo = row.classNo || ''
form.belong = row.belong || ''
// 如果没有归档级别,尝试获取详情
if (row.id && !row.belong) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.belong = res.data.belong || ''
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
await editBelong({
id: form.id,
belong: form.belong
})
useMessage().success('归档成功')
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || '归档失败')
} finally {
loading.value = false
}
})
}
// 获取归档级别字典
const getBelongDict = async () => {
try {
const res = await getDicts('honor_belong_type')
if (res.data && Array.isArray(res.data)) {
belongList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
}
} catch (err) {
console.error('获取归档级别字典失败', err)
}
}
// 初始化
onMounted(() => {
getBelongDict()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.mb20 {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,209 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
:width="800"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="班号" prop="classCode">
<el-select
v-model="form.classCode"
placeholder="请选择班号"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="作者" prop="author">
<el-input
v-model="form.author"
placeholder="请输入作者"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="标题" prop="title">
<el-input
v-model="form.title"
placeholder="请输入标题"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="附件" prop="attachment">
<upload-file
v-model="form.attachment"
:limit="5"
:fileSize="10"
type="simple" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ClassHonorFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/classhonor'
import { getClassListByRole } from '/@/api/basic/basicclass'
import UploadFile from '/@/components/Upload/index.vue'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add')
const classList = ref<any[]>([])
// 提交表单数据
const form = reactive({
id: '',
classCode: '',
author: '',
title: '',
attachment: ''
})
// 定义校验规则
const dataRules = {
classCode: [
{ required: true, message: '请选择班号', trigger: 'change' }
],
author: [
{ required: true, message: '请输入作者', trigger: 'blur' }
],
title: [
{ required: true, message: '请输入标题', trigger: 'blur' }
]
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.classCode = ''
form.author = ''
form.title = ''
form.attachment = ''
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.classCode = row.classCode || ''
form.author = row.author || ''
form.title = row.title || ''
form.attachment = row.attachment || ''
// 如果需要获取详情
if (row.id && (!row.title || !row.attachment)) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.attachment = res.data.attachment || ''
}
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
const submitData = {
classCode: form.classCode,
author: form.author,
title: form.title,
attachment: form.attachment || ''
}
if (operType.value === 'add') {
await addObj(submitData)
useMessage().success('新增成功')
} else {
await editObj({
id: form.id,
...submitData
})
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 获取班号列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
}
} catch (err) {
console.error('获取班号列表失败', err)
}
}
// 初始化
onMounted(() => {
getClassListData()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.mb20 {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,386 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="state.queryForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="state.queryForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="state.queryForm.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 200px"
@change="handleDeptChange">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班号" prop="classCode">
<el-select
v-model="state.queryForm.classCode"
placeholder="请选择班号"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" />
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center" />
<el-table-column prop="title" label="标题" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="author" label="作者" show-overflow-tooltip align="center" />
<el-table-column prop="updateTime" label="更新时间" show-overflow-tooltip align="center" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.updateTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column prop="belong" label="归档级别" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatBelong(scope.row.belong) }}</span>
</template>
</el-table-column>
<el-table-column prop="attachment" label="附件" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<el-button
v-if="scope.row.attachment"
icon="Document"
text
type="primary"
size="small"
@click="handleViewAttachment(scope.row)">
查看
</el-button>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="操作" width="250" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Folder"
text
type="warning"
@click="handleBelong(scope.row)">
归档
</el-button>
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
<!-- 归档弹窗 -->
<belong-dialog ref="belongDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="ClassHonor">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/classhonor";
import { getDeptList } from "/@/api/basic/basicclass";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
import FormDialog from './form.vue'
import BelongDialog from './belong.vue'
// 定义变量内容
const formDialogRef = ref()
const belongDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const belongList = ref<any[]>([])
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
schoolYear: '',
schoolTerm: '',
deptCode: '',
classCode: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化归档级别
const formatBelong = (value: string) => {
if (!value) return '-'
const item = belongList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 学院变化
const handleDeptChange = () => {
// 可以根据学院筛选班级,这里暂时不实现
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.schoolYear = ''
state.queryForm.schoolTerm = ''
state.queryForm.deptCode = ''
state.queryForm.classCode = ''
getDataList()
}
// 查看附件
const handleViewAttachment = (row: any) => {
if (row.attachment) {
const urls = typeof row.attachment === 'string' ? row.attachment.split(',') : [row.attachment]
urls.forEach((url: string) => {
if (url.trim()) {
window.open(url.trim(), '_blank')
}
})
}
}
// 归档
const handleBelong = (row: any) => {
belongDialogRef.value?.openDialog(row)
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该班级荣誉吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptList()
if (res.data && Array.isArray(res.data)) {
deptList.value = res.data
} else {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
// 获取班号列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}
// 获取归档级别字典
const getBelongDict = async () => {
try {
const res = await getDicts('honor_belong_type')
if (res.data && Array.isArray(res.data)) {
belongList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
belongList.value = []
}
} catch (err) {
console.error('获取归档级别字典失败', err)
belongList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getDeptListData()
getClassListData()
getBelongDict()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,276 @@
<template>
<el-dialog
title="查看详情"
v-model="visible"
:close-on-click-modal="false"
draggable
width="1000px">
<div v-loading="loading" class="article-container" v-if="detailData">
<!-- 报纸文章样式 -->
<div class="article-header">
<h1 class="article-title">{{ detailData.title || '-' }}</h1>
<div class="article-meta">
<div class="meta-left">
<span class="meta-item">类型{{ formatType(detailData.type) }}</span>
<span class="meta-item">发表刊物{{ detailData.journal || '-' }}</span>
<span class="meta-item">发表页码{{ detailData.page || '-' }}</span>
</div>
<div class="meta-right">
<span class="meta-item">班号{{ detailData.classNo || '-' }}</span>
<span class="meta-item">班主任{{ detailData.teacherRealName || '-' }}</span>
</div>
</div>
</div>
<div class="article-content" v-html="detailData.description || '-'"></div>
<!-- 附件区域 -->
<div v-if="attachmentList.length > 0" class="article-attachment">
<h3 class="attachment-title">附件</h3>
<div class="attachment-list">
<div
v-for="(url, index) in attachmentList"
:key="index"
class="attachment-item">
<el-icon class="attachment-icon"><Document /></el-icon>
<span class="attachment-name">{{ getFileName(url) }}</span>
<el-button
icon="Download"
text
type="primary"
size="small"
@click="handleDownload(url)">
下载
</el-button>
</div>
</div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ClassPaperDetailDialog">
import { ref, computed, onMounted } from 'vue'
import { getDetail } from '/@/api/stuwork/classpaper'
import { getDicts } from '/@/api/admin/dict'
import { Document, Download } from '@element-plus/icons-vue'
import other from '/@/utils/other'
// 定义变量内容
const visible = ref(false)
const loading = ref(false)
const detailData = ref<any>(null)
const schoolTermList = ref<any[]>([])
const typeList = ref<any[]>([
{ label: '德育论文', value: '0' },
{ label: '教案', value: '1' }
])
// 附件列表
const attachmentList = computed(() => {
if (!detailData.value || !detailData.value.attachment) return []
const urls = typeof detailData.value.attachment === 'string'
? detailData.value.attachment.split(',').filter((url: string) => url.trim())
: []
return urls
})
// 格式化类型
const formatType = (value: string) => {
if (!value) return '-'
const item = typeList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 获取文件名
const getFileName = (url: string): string => {
if (!url) return '未知文件'
return other.getQueryString(url, 'fileName') || other.getQueryString(url, 'originalFileName') || url.split('/').pop() || '未知文件'
}
// 下载文件
const handleDownload = (url: string) => {
if (!url) return
window.open(url, '_blank')
}
// 打开弹窗
const openDialog = async (id: string) => {
visible.value = true
detailData.value = null
loading.value = true
try {
const res = await getDetail(id)
if (res.data) {
detailData.value = res.data
}
} catch (err) {
console.error('获取详情失败', err)
} finally {
loading.value = false
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
}
} catch (err) {
console.error('获取学期字典失败', err)
}
}
// 初始化
onMounted(() => {
getSchoolTermDict()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped>
.article-container {
padding: 20px;
max-width: 100%;
}
.article-header {
border-bottom: 2px solid #e4e7ed;
padding-bottom: 20px;
margin-bottom: 30px;
}
.article-title {
font-size: 28px;
font-weight: bold;
color: #303133;
margin: 0 0 15px 0;
line-height: 1.5;
text-align: center;
}
.article-meta {
display: flex;
justify-content: space-between;
align-items: flex-start;
font-size: 14px;
color: #909399;
padding-top: 10px;
flex-wrap: wrap;
gap: 10px;
}
.meta-left,
.meta-right {
display: flex;
flex-direction: column;
gap: 8px;
}
.meta-item {
font-weight: 500;
}
.article-content {
font-size: 16px;
line-height: 1.8;
color: #606266;
text-align: justify;
word-wrap: break-word;
min-height: 200px;
}
.article-content :deep(p) {
margin: 0 0 15px 0;
}
.article-content :deep(img) {
max-width: 100%;
height: auto;
display: block;
margin: 20px auto;
}
.article-content :deep(h1),
.article-content :deep(h2),
.article-content :deep(h3),
.article-content :deep(h4),
.article-content :deep(h5),
.article-content :deep(h6) {
margin: 20px 0 15px 0;
font-weight: bold;
}
.article-content :deep(ul),
.article-content :deep(ol) {
margin: 15px 0;
padding-left: 30px;
}
.article-content :deep(blockquote) {
border-left: 4px solid #409eff;
padding-left: 15px;
margin: 15px 0;
color: #909399;
font-style: italic;
}
.article-attachment {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #e4e7ed;
}
.attachment-title {
font-size: 18px;
font-weight: bold;
color: #303133;
margin: 0 0 15px 0;
}
.attachment-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.attachment-item {
display: flex;
align-items: center;
padding: 12px 16px;
background-color: #f5f7fa;
border-radius: 4px;
transition: background-color 0.3s;
}
.attachment-item:hover {
background-color: #ecf5ff;
}
.attachment-icon {
font-size: 20px;
color: #409eff;
margin-right: 12px;
}
.attachment-name {
flex: 1;
color: #606266;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>

View File

@@ -0,0 +1,268 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
:width="800"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="班号" prop="classCode">
<el-select
v-model="form.classCode"
placeholder="请选择班号"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="类型" prop="type">
<el-select
v-model="form.type"
placeholder="请选择类型"
clearable
style="width: 100%">
<el-option
v-for="item in typeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="标题" prop="title">
<el-input
v-model="form.title"
placeholder="请输入标题"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="发表刊物" prop="journal">
<el-input
v-model="form.journal"
placeholder="请输入发表刊物"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="发表页码" prop="page">
<el-input-number
v-model="form.page"
:min="0"
placeholder="请输入发表页码"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="描述" prop="description">
<el-input
v-model="form.description"
type="textarea"
:rows="4"
placeholder="请输入描述"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="附件">
<upload-file
v-model="form.attachment"
:limit="5"
:fileSize="10"
type="simple" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ClassPaperFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/classpaper'
import { getClassListByRole } from '/@/api/basic/basicclass'
import UploadFile from '/@/components/Upload/index.vue'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add')
const classList = ref<any[]>([])
const typeList = ref<any[]>([
{ label: '德育论文', value: '0' },
{ label: '教案', value: '1' }
])
// 提交表单数据
const form = reactive({
id: '',
classCode: '',
type: '',
title: '',
journal: '',
page: 0,
description: '',
attachment: ''
})
// 定义校验规则
const dataRules = {
classCode: [
{ required: true, message: '请选择班号', trigger: 'change' }
],
type: [
{ required: true, message: '请选择类型', trigger: 'change' }
],
title: [
{ required: true, message: '请输入标题', trigger: 'blur' }
],
journal: [
{ required: true, message: '请输入发表刊物', trigger: 'blur' }
],
page: [
{ required: true, message: '请输入发表页码', trigger: 'blur' }
],
description: [
{ required: true, message: '请输入描述', trigger: 'blur' }
]
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.classCode = ''
form.type = ''
form.title = ''
form.journal = ''
form.page = 0
form.description = ''
form.attachment = ''
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.classCode = row.classCode || ''
form.type = row.type || ''
form.title = row.title || ''
form.journal = row.journal || ''
form.page = row.page || 0
form.description = row.description || ''
form.attachment = row.attachment || ''
// 如果需要获取详情
if (row.id && !row.description) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.description = res.data.description || ''
form.attachment = res.data.attachment || ''
}
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
const submitData = {
classCode: form.classCode,
type: form.type,
title: form.title,
journal: form.journal,
page: form.page,
description: form.description,
attachment: form.attachment || ''
}
if (operType.value === 'add') {
await addObj(submitData)
useMessage().success('新增成功')
} else {
await editObj({
id: form.id,
...submitData
})
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 获取班号列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
}
} catch (err) {
console.error('获取班号列表失败', err)
}
}
// 初始化
onMounted(() => {
getClassListData()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.mb20 {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,415 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="state.queryForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="state.queryForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="state.queryForm.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班号" prop="classNo">
<el-select
v-model="state.queryForm.classNo"
placeholder="请选择班号"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classNo">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="标题" prop="title">
<el-input
v-model="state.queryForm.title"
placeholder="请输入标题"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select
v-model="state.queryForm.type"
placeholder="请选择类型"
clearable
style="width: 200px">
<el-option
v-for="item in typeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="标题" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" />
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center" />
<el-table-column prop="teacherRealName" label="班主任" show-overflow-tooltip align="center" />
<el-table-column prop="type" label="类型" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatType(scope.row.type) }}</span>
</template>
</el-table-column>
<el-table-column prop="journal" label="发表刊物" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="page" label="发表页码" show-overflow-tooltip align="center" />
<el-table-column prop="isAddScore" label="加分" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ scope.row.isAddScore === '1' ? '是' : '否' }}</span>
</template>
</el-table-column>
<el-table-column prop="attachment" label="附件" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<el-button
v-if="scope.row.attachment"
icon="Document"
text
type="primary"
size="small"
@click="handleViewAttachment(scope.row)">
查看
</el-button>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="操作" width="250" align="center" fixed="right">
<template #default="scope">
<el-button
icon="View"
text
type="primary"
@click="handleView(scope.row)">
查看
</el-button>
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
v-if="scope.row.isAddScore !== '1'"
icon="Plus"
text
type="success"
@click="handleAddScore(scope.row)">
加分
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
<!-- 查看详情弹窗 -->
<detail-dialog ref="detailDialogRef" />
</div>
</template>
<script setup lang="ts" name="ClassPaper">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, addScore } from "/@/api/stuwork/classpaper";
import { getDeptList } from "/@/api/basic/basicclass";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import FormDialog from './form.vue'
import DetailDialog from './detail.vue'
// 定义变量内容
const formDialogRef = ref()
const detailDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const typeList = ref<any[]>([
{ label: '德育论文', value: '0' },
{ label: '教案', value: '1' }
])
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
schoolYear: '',
schoolTerm: '',
deptCode: '',
classNo: '',
title: '',
type: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化类型
const formatType = (value: string) => {
if (!value) return '-'
const item = typeList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.schoolYear = ''
state.queryForm.schoolTerm = ''
state.queryForm.deptCode = ''
state.queryForm.classNo = ''
state.queryForm.title = ''
state.queryForm.type = ''
getDataList()
}
// 查看
const handleView = (row: any) => {
detailDialogRef.value?.openDialog(row.id)
}
// 查看附件
const handleViewAttachment = (row: any) => {
if (row.attachment) {
const urls = typeof row.attachment === 'string' ? row.attachment.split(',') : [row.attachment]
urls.forEach((url: string) => {
if (url.trim()) {
window.open(url.trim(), '_blank')
}
})
}
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 加分
const handleAddScore = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要对该论文/案例进行加分吗?')
await addScore({
id: row.id,
...row
})
useMessage().success('加分成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '加分失败')
}
}
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该论文/案例吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptList()
if (res.data && Array.isArray(res.data)) {
deptList.value = res.data
} else {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
// 获取班号列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getDeptListData()
getClassListData()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,179 @@
<template>
<el-dialog
title="归档"
v-model="visible"
:width="500"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="标题">
<el-input v-model="form.title" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="作者">
<el-input v-model="form.author" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="班号">
<el-input v-model="form.classNo" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="归档级别" prop="belong">
<el-select
v-model="form.belong"
placeholder="请选择归档级别"
clearable
style="width: 100%">
<el-option
v-for="item in belongList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ClassPublicityBelongDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { editBelong, getDetail } from '/@/api/stuwork/classpublicity'
import { getDicts } from '/@/api/admin/dict'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const belongList = ref<any[]>([])
// 提交表单数据
const form = reactive({
id: '',
title: '',
author: '',
classNo: '',
belong: ''
})
// 定义校验规则
const dataRules = {
belong: [
{ required: true, message: '请选择归档级别', trigger: 'change' }
]
}
// 打开弹窗
const openDialog = async (row: any) => {
visible.value = true
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.title = ''
form.author = ''
form.classNo = ''
form.belong = ''
if (row) {
form.id = row.id
form.title = row.title || ''
form.author = row.author || ''
form.classNo = row.classNo || ''
form.belong = row.belong || ''
// 如果没有归档级别,尝试获取详情
if (row.id && !row.belong) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.belong = res.data.belong || ''
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
await editBelong({
id: form.id,
belong: form.belong
})
useMessage().success('归档成功')
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || '归档失败')
} finally {
loading.value = false
}
})
}
// 获取归档级别字典
const getBelongDict = async () => {
try {
const res = await getDicts('public_belong_type')
if (res.data && Array.isArray(res.data)) {
belongList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
}
} catch (err) {
console.error('获取归档级别字典失败', err)
}
}
// 初始化
onMounted(() => {
getBelongDict()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.mb20 {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,216 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
:width="800"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="班号" prop="classCode">
<el-select
v-model="form.classCode"
placeholder="请选择班号"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="作者" prop="author">
<el-input
v-model="form.author"
placeholder="请输入作者"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="标题" prop="title">
<el-input
v-model="form.title"
placeholder="请输入标题"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="网址" prop="website">
<el-input
v-model="form.website"
placeholder="请输入网址例如https://www.example.com"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ClassPublicityFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/classpublicity'
import { getClassListByRole } from '/@/api/basic/basicclass'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add')
const classList = ref<any[]>([])
// 提交表单数据
const form = reactive({
id: '',
classCode: '',
author: '',
title: '',
website: ''
})
// 定义校验规则
const dataRules = {
classCode: [
{ required: true, message: '请选择班号', trigger: 'change' }
],
author: [
{ required: true, message: '请输入作者', trigger: 'blur' }
],
title: [
{ required: true, message: '请输入标题', trigger: 'blur' }
],
website: [
{ required: true, message: '请输入网址', trigger: 'blur' },
{
pattern: /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/,
message: '请输入正确的网址格式',
trigger: 'blur'
}
]
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.classCode = ''
form.author = ''
form.title = ''
form.website = ''
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.classCode = row.classCode || ''
form.author = row.author || ''
form.title = row.title || ''
form.website = row.website || ''
// 如果需要获取详情
if (row.id && (!row.title || !row.website)) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.website = res.data.website || ''
}
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
const submitData = {
classCode: form.classCode,
author: form.author,
title: form.title,
website: form.website
}
if (operType.value === 'add') {
await addObj(submitData)
useMessage().success('新增成功')
} else {
await editObj({
id: form.id,
...submitData
})
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 获取班号列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
}
} catch (err) {
console.error('获取班号列表失败', err)
}
}
// 初始化
onMounted(() => {
getClassListData()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.mb20 {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,407 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="state.queryForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="state.queryForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="state.queryForm.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 200px"
@change="handleDeptChange">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班号" prop="classCode">
<el-select
v-model="state.queryForm.classCode"
placeholder="请选择班号"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" />
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center" />
<el-table-column prop="title" label="标题" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="author" label="作者" show-overflow-tooltip align="center" />
<el-table-column prop="updateTime" label="更新时间" show-overflow-tooltip align="center" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.updateTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column prop="belong" label="归档级别" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatBelong(scope.row.belong) }}</span>
</template>
</el-table-column>
<el-table-column prop="isAddScore" label="加分" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ scope.row.isAddScore === '1' ? '是' : '否' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="300" align="center" fixed="right">
<template #default="scope">
<el-button
icon="View"
text
type="primary"
@click="handleView(scope.row)">
查看
</el-button>
<el-button
v-if="scope.row.isAddScore !== '1'"
icon="Plus"
text
type="success"
@click="handleAddScore(scope.row)">
加分
</el-button>
<el-button
icon="Folder"
text
type="warning"
@click="handleBelong(scope.row)">
归档
</el-button>
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
<!-- 归档弹窗 -->
<belong-dialog ref="belongDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="ClassPublicity">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, addScore } from "/@/api/stuwork/classpublicity";
import { getDeptList } from "/@/api/basic/basicclass";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
import FormDialog from './form.vue'
import BelongDialog from './belong.vue'
// 定义变量内容
const formDialogRef = ref()
const belongDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const belongList = ref<any[]>([])
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
schoolYear: '',
schoolTerm: '',
deptCode: '',
classCode: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化归档级别
const formatBelong = (value: string) => {
if (!value) return '-'
const item = belongList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 学院变化
const handleDeptChange = () => {
// 可以根据学院筛选班级,这里暂时不实现
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.schoolYear = ''
state.queryForm.schoolTerm = ''
state.queryForm.deptCode = ''
state.queryForm.classCode = ''
getDataList()
}
// 查看(跳转网址)
const handleView = (row: any) => {
if (row.website) {
window.open(row.website, '_blank')
} else {
useMessage().warning('该记录没有网址')
}
}
// 加分
const handleAddScore = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要对该班级宣传进行加分吗?')
await addScore({
id: row.id,
...row
})
useMessage().success('加分成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '加分失败')
}
}
}
// 归档
const handleBelong = (row: any) => {
belongDialogRef.value?.openDialog(row)
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该班级宣传吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptList()
if (res.data && Array.isArray(res.data)) {
deptList.value = res.data
} else {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
// 获取班号列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}
// 获取归档级别字典
const getBelongDict = async () => {
try {
const res = await getDicts('public_belong_type')
if (res.data && Array.isArray(res.data)) {
belongList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
belongList.value = []
}
} catch (err) {
console.error('获取归档级别字典失败', err)
belongList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getDeptListData()
getClassListData()
getBelongDict()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,277 @@
<template>
<el-dialog
title="查看详情"
v-model="visible"
:close-on-click-modal="false"
draggable
width="1000px">
<div v-loading="loading" class="article-container" v-if="detailData">
<!-- 报纸文章样式 -->
<div class="article-header">
<h1 class="article-title">{{ detailData.themeName || '-' }}</h1>
<div class="article-meta">
<div class="meta-left">
<span class="meta-item">学年{{ detailData.schoolYear || '-' }}</span>
<span class="meta-item">学期{{ formatSchoolTerm(detailData.schoolTerm) }}</span>
<span class="meta-item">班号{{ detailData.classNo || '-' }}</span>
</div>
<div class="meta-right">
<span class="meta-item">主持人{{ detailData.author || '-' }}</span>
<span class="meta-item">活动地点{{ detailData.address || '-' }}</span>
<span class="meta-item">活动时间{{ detailData.recordDate || '-' }}</span>
<span class="meta-item">参加人数{{ detailData.attendNum || '-' }}</span>
</div>
</div>
</div>
<!-- 活动内容 -->
<div class="article-content" v-html="detailData.content || '-'"></div>
<!-- 活动图片1 -->
<div v-if="imageList1.length > 0" class="article-images">
<h3 class="images-title">活动照片</h3>
<div class="images-grid">
<el-image
v-for="(url, index) in imageList1"
:key="index"
:src="url"
:preview-src-list="imageList1"
:initial-index="index"
fit="cover"
class="article-image" />
</div>
</div>
<!-- 活动图片2 -->
<div v-if="imageList2.length > 0" class="article-images">
<h3 class="images-title">活动照片2</h3>
<div class="images-grid">
<el-image
v-for="(url, index) in imageList2"
:key="index"
:src="url"
:preview-src-list="imageList2"
:initial-index="index"
fit="cover"
class="article-image" />
</div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ClassSafeEduDetailDialog">
import { ref, computed, onMounted } from 'vue'
import { getDetail } from '/@/api/stuwork/classsafeedu'
import { getDicts } from '/@/api/admin/dict'
import { useMessage } from '/@/hooks/message'
// 定义变量内容
const visible = ref(false)
const loading = ref(false)
const detailData = ref<any>({})
const schoolTermList = ref<any[]>([])
// 图片列表
const imageList1 = computed(() => {
if (!detailData.value.attachment) return []
const urls = typeof detailData.value.attachment === 'string'
? detailData.value.attachment.split(',').filter((url: string) => url.trim())
: []
return urls
})
const imageList2 = computed(() => {
if (!detailData.value.attachment2) return []
const urls = typeof detailData.value.attachment2 === 'string'
? detailData.value.attachment2.split(',').filter((url: string) => url.trim())
: []
return urls
})
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 打开弹窗
const openDialog = async (row: any) => {
visible.value = true
detailData.value = {}
if (!row.id) {
useMessage().warning('数据异常')
return
}
loading.value = true
try {
const res = await getDetail(row.id)
if (res.data) {
detailData.value = res.data
} else {
useMessage().error('获取详情失败')
}
} catch (err: any) {
console.error('获取详情失败', err)
useMessage().error(err.msg || '获取详情失败')
} finally {
loading.value = false
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
}
} catch (err) {
console.error('获取学期字典失败', err)
}
}
// 初始化
onMounted(() => {
getSchoolTermDict()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped>
.article-container {
padding: 20px;
max-width: 100%;
}
.article-header {
border-bottom: 2px solid #e4e7ed;
padding-bottom: 20px;
margin-bottom: 30px;
}
.article-title {
font-size: 28px;
font-weight: bold;
color: #303133;
margin: 0 0 15px 0;
line-height: 1.5;
text-align: center;
}
.article-meta {
display: flex;
justify-content: space-between;
align-items: flex-start;
font-size: 14px;
color: #909399;
padding-top: 10px;
flex-wrap: wrap;
gap: 10px;
}
.meta-left,
.meta-right {
display: flex;
flex-direction: column;
gap: 8px;
}
.meta-item {
font-weight: 500;
}
.article-content {
font-size: 16px;
line-height: 1.8;
color: #606266;
text-align: justify;
word-wrap: break-word;
min-height: 200px;
margin-bottom: 30px;
}
.article-content :deep(p) {
margin: 0 0 15px 0;
}
.article-content :deep(img) {
max-width: 100%;
height: auto;
display: block;
margin: 20px auto;
}
.article-content :deep(h1),
.article-content :deep(h2),
.article-content :deep(h3),
.article-content :deep(h4),
.article-content :deep(h5),
.article-content :deep(h6) {
margin: 20px 0 15px 0;
font-weight: bold;
}
.article-content :deep(ul),
.article-content :deep(ol) {
margin: 15px 0;
padding-left: 30px;
}
.article-content :deep(blockquote) {
border-left: 4px solid #409eff;
padding-left: 15px;
margin: 15px 0;
color: #909399;
font-style: italic;
}
.article-images {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #e4e7ed;
}
.images-title {
font-size: 18px;
font-weight: bold;
color: #303133;
margin: 0 0 15px 0;
}
.images-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
}
.article-image {
width: 100%;
height: 200px;
border-radius: 4px;
cursor: pointer;
transition: transform 0.3s;
}
.article-image:hover {
transform: scale(1.05);
}
</style>

View File

@@ -0,0 +1,290 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
:width="900"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="班号" prop="classCode">
<el-select
v-model="form.classCode"
placeholder="请选择班号"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="活动主题" prop="themeName">
<el-input
v-model="form.themeName"
placeholder="请输入活动主题"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="主持人" prop="author">
<el-input
v-model="form.author"
placeholder="请输入主持人"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="活动地点" prop="address">
<el-input
v-model="form.address"
placeholder="请输入活动地点"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="活动时间" prop="recordDate">
<el-date-picker
v-model="form.recordDate"
type="date"
placeholder="选择活动时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="参加人数" prop="attendNum">
<el-input-number
v-model="form.attendNum"
:min="0"
placeholder="请输入参加人数"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="活动内容" prop="content">
<Editor
v-model:getHtml="form.content"
:height="300"
placeholder="请输入活动内容" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="活动图片" prop="attachment">
<upload-file
v-model="form.attachment"
:limit="5"
:fileSize="10"
type="simple" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="活动图片2" prop="attachment2">
<upload-file
v-model="form.attachment2"
:limit="5"
:fileSize="10"
type="simple" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ClassSafeEduFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/classsafeedu'
import { getClassListByRole } from '/@/api/basic/basicclass'
import Editor from '/@/components/Editor/index.vue'
import UploadFile from '/@/components/Upload/index.vue'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add')
const classList = ref<any[]>([])
// 提交表单数据
const form = reactive({
id: '',
classCode: '',
themeName: '',
author: '',
address: '',
recordDate: '',
attendNum: 0,
content: '',
attachment: '',
attachment2: ''
})
// 定义校验规则
const dataRules = {
classCode: [
{ required: true, message: '请选择班号', trigger: 'change' }
],
themeName: [
{ required: true, message: '请输入活动主题', trigger: 'blur' }
],
author: [
{ required: true, message: '请输入主持人', trigger: 'blur' }
],
address: [
{ required: true, message: '请输入活动地点', trigger: 'blur' }
],
recordDate: [
{ required: true, message: '请选择活动时间', trigger: 'change' }
],
attendNum: [
{ required: true, message: '请输入参加人数', trigger: 'blur' }
],
content: [
{ required: true, message: '请输入活动内容', trigger: 'blur' }
]
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.classCode = ''
form.themeName = ''
form.author = ''
form.address = ''
form.recordDate = ''
form.attendNum = 0
form.content = ''
form.attachment = ''
form.attachment2 = ''
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.classCode = row.classCode || ''
form.themeName = row.themeName || ''
form.author = row.author || ''
form.address = row.address || ''
form.recordDate = row.recordDate || ''
form.attendNum = row.attendNum || 0
form.content = row.content || ''
form.attachment = row.attachment || ''
form.attachment2 = row.attachment2 || ''
// 如果需要获取详情
if (row.id && !row.content) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.content = res.data.content || ''
form.attachment = res.data.attachment || ''
form.attachment2 = res.data.attachment2 || ''
}
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
const submitData = {
classCode: form.classCode,
themeName: form.themeName,
author: form.author,
address: form.address,
recordDate: form.recordDate,
attendNum: form.attendNum,
content: form.content,
attachment: form.attachment || '',
attachment2: form.attachment2 || ''
}
if (operType.value === 'add') {
await addObj(submitData)
useMessage().success('新增成功')
} else {
await editObj({
id: form.id,
...submitData
})
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
}
} catch (err) {
console.error('获取班级列表失败', err)
}
}
// 初始化
onMounted(() => {
getClassListData()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.mb20 {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,381 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="state.queryForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="state.queryForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班号" prop="classCode">
<el-select
v-model="state.queryForm.classCode"
placeholder="请选择班号"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="活动时间" prop="recordDate">
<el-date-picker
v-model="state.queryForm.recordDate"
type="date"
placeholder="选择活动时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<el-button
icon="Download"
type="primary"
class="ml10"
@click="handleExport">
导出
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center" />
<el-table-column prop="themeName" label="活动主题" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="author" label="主持人" show-overflow-tooltip align="center" />
<el-table-column prop="address" label="活动地点" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="recordDate" label="活动时间" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="attendNum" label="参加人数" show-overflow-tooltip align="center" />
<el-table-column prop="attachment" label="活动照片" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<el-button
v-if="scope.row.attachment"
icon="Picture"
text
type="primary"
size="small"
@click="handleViewImage(scope.row.attachment)">
查看
</el-button>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="attachment2" label="活动照片2" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<el-button
v-if="scope.row.attachment2"
icon="Picture"
text
type="primary"
size="small"
@click="handleViewImage(scope.row.attachment2)">
查看
</el-button>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="操作" width="250" align="center" fixed="right">
<template #default="scope">
<el-button
icon="View"
text
type="primary"
@click="handleView(scope.row)">
查看详情
</el-button>
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
<!-- 查看详情弹窗 -->
<detail-dialog ref="detailDialogRef" />
</div>
</template>
<script setup lang="ts" name="ClassSafeEdu">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, exportExcel } from "/@/api/stuwork/classsafeedu";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import FormDialog from './form.vue'
import DetailDialog from './detail.vue'
// 定义变量内容
const formDialogRef = ref()
const detailDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const classList = ref<any[]>([])
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
schoolYear: '',
schoolTerm: '',
classCode: '',
recordDate: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true // 页面加载时自动获取数据
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.schoolYear = ''
state.queryForm.schoolTerm = ''
state.queryForm.classCode = ''
state.queryForm.recordDate = ''
getDataList()
}
// 查看图片
const handleViewImage = (url: string) => {
if (url) {
const urls = typeof url === 'string' ? url.split(',') : [url]
urls.forEach((imgUrl: string) => {
if (imgUrl.trim()) {
window.open(imgUrl.trim(), '_blank')
}
})
}
}
// 导出
const handleExport = async () => {
try {
const res = await exportExcel(state.queryForm)
// 创建blob对象
const blob = new Blob([res], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
// 创建下载链接
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
// 设置文件名
const fileName = `安全教育_${new Date().getTime()}.xlsx`
link.setAttribute('download', fileName)
// 触发下载
document.body.appendChild(link)
link.click()
// 清理
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
useMessage().success('导出成功')
} catch (err: any) {
console.error('导出失败', err)
useMessage().error(err.msg || '导出失败')
}
}
// 查看详情
const handleView = (row: any) => {
detailDialogRef.value?.openDialog(row)
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该安全教育记录吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getClassListData()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,239 @@
<template>
<el-dialog
title="查看详情"
v-model="visible"
:close-on-click-modal="false"
draggable
width="1000px">
<div v-loading="loading" class="article-container" v-if="detailData">
<!-- 报纸文章样式 -->
<div class="article-header">
<h1 class="article-title">班级总结</h1>
<div class="article-meta">
<div class="meta-left">
<span class="meta-item">学年{{ detailData.schoolYear || '-' }}</span>
<span class="meta-item">学期{{ formatSchoolTerm(detailData.schoolTerm) }}</span>
<span class="meta-item">班号{{ detailData.classNo || '-' }}</span>
</div>
</div>
</div>
<!-- 统计信息 -->
<div class="summary-stats">
<el-descriptions :column="4" border>
<el-descriptions-item label="总人数">{{ detailData.totalCnt || 0 }}</el-descriptions-item>
<el-descriptions-item label="男生人数">{{ detailData.boyCnt || 0 }}</el-descriptions-item>
<el-descriptions-item label="女生人数">{{ detailData.girlCnt || 0 }}</el-descriptions-item>
<el-descriptions-item label="状态">
<span>{{ formatStatus(detailData.status) }}</span>
</el-descriptions-item>
<el-descriptions-item label="男(住宿)">{{ detailData.boyDormCnt || 0 }}</el-descriptions-item>
<el-descriptions-item label="女(住宿)">{{ detailData.girlDormCnt || 0 }}</el-descriptions-item>
<el-descriptions-item label="留校察看人数">{{ detailData.keepSchoolCnt || 0 }}</el-descriptions-item>
<el-descriptions-item label="记过人数">{{ detailData.gigCnt || 0 }}</el-descriptions-item>
<el-descriptions-item label="严重警告人数">{{ detailData.seriousWarningCnt || 0 }}</el-descriptions-item>
<el-descriptions-item label="警告人数">{{ detailData.warningCnt || 0 }}</el-descriptions-item>
<el-descriptions-item label="撤销处分人数">{{ detailData.revokeCnt || 0 }}</el-descriptions-item>
<el-descriptions-item label="退学人数">{{ detailData.dropCnt || 0 }}</el-descriptions-item>
</el-descriptions>
</div>
<!-- 总结报告 -->
<div class="article-content">
<h3 class="content-title">总结报告</h3>
<div v-html="detailData.summary || '-'"></div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ClassSummaryDetailDialog">
import { ref, onMounted } from 'vue'
import { getDetail } from '/@/api/stuwork/classsummary'
import { getDicts } from '/@/api/admin/dict'
// 定义变量内容
const visible = ref(false)
const loading = ref(false)
const detailData = ref<any>(null)
const schoolTermList = ref<any[]>([])
const statusList = ref<any[]>([])
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化状态
const formatStatus = (value: string) => {
if (!value) return '-'
const item = statusList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 打开弹窗
const openDialog = async (id: string) => {
visible.value = true
detailData.value = null
loading.value = true
try {
const res = await getDetail(id)
if (res.data) {
detailData.value = res.data
}
} catch (err) {
console.error('获取详情失败', err)
} finally {
loading.value = false
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
}
} catch (err) {
console.error('获取学期字典失败', err)
}
}
// 获取状态字典
const getStatusDict = async () => {
try {
const res = await getDicts('status_type')
if (res.data && Array.isArray(res.data)) {
statusList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
}
} catch (err) {
console.error('获取状态字典失败', err)
}
}
// 初始化
onMounted(() => {
getSchoolTermDict()
getStatusDict()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped>
.article-container {
padding: 20px;
max-width: 100%;
}
.article-header {
border-bottom: 2px solid #e4e7ed;
padding-bottom: 20px;
margin-bottom: 30px;
}
.article-title {
font-size: 28px;
font-weight: bold;
color: #303133;
margin: 0 0 15px 0;
line-height: 1.5;
text-align: center;
}
.article-meta {
display: flex;
justify-content: space-between;
align-items: flex-start;
font-size: 14px;
color: #909399;
padding-top: 10px;
flex-wrap: wrap;
gap: 10px;
}
.meta-left {
display: flex;
flex-direction: column;
gap: 8px;
}
.meta-item {
font-weight: 500;
}
.summary-stats {
margin-bottom: 30px;
}
.content-title {
font-size: 18px;
font-weight: bold;
color: #303133;
margin: 0 0 15px 0;
}
.article-content {
font-size: 16px;
line-height: 1.8;
color: #606266;
text-align: justify;
word-wrap: break-word;
min-height: 200px;
}
.article-content :deep(p) {
margin: 0 0 15px 0;
}
.article-content :deep(img) {
max-width: 100%;
height: auto;
display: block;
margin: 20px auto;
}
.article-content :deep(h1),
.article-content :deep(h2),
.article-content :deep(h3),
.article-content :deep(h4),
.article-content :deep(h5),
.article-content :deep(h6) {
margin: 20px 0 15px 0;
font-weight: bold;
}
.article-content :deep(ul),
.article-content :deep(ol) {
margin: 15px 0;
padding-left: 30px;
}
.article-content :deep(blockquote) {
border-left: 4px solid #409eff;
padding-left: 15px;
margin: 15px 0;
color: #909399;
font-style: italic;
}
</style>

View File

@@ -0,0 +1,355 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
:width="800"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="班号" prop="classCode">
<el-select
v-model="form.classCode"
placeholder="请选择班号"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="总人数" prop="totalCnt">
<el-input-number
v-model="form.totalCnt"
:min="0"
placeholder="请输入总人数"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="男生人数" prop="boyCnt">
<el-input-number
v-model="form.boyCnt"
:min="0"
placeholder="请输入男生人数"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="女生人数" prop="girlCnt">
<el-input-number
v-model="form.girlCnt"
:min="0"
placeholder="请输入女生人数"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="男(住宿)" prop="boyDormCnt">
<el-input-number
v-model="form.boyDormCnt"
:min="0"
placeholder="请输入男(住宿)人数"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="女(住宿)" prop="girlDormCnt">
<el-input-number
v-model="form.girlDormCnt"
:min="0"
placeholder="请输入女(住宿)人数"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="留校察看人数" prop="keepSchoolCnt">
<el-input-number
v-model="form.keepSchoolCnt"
:min="0"
placeholder="请输入留校察看人数"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="记过人数" prop="gigCnt">
<el-input-number
v-model="form.gigCnt"
:min="0"
placeholder="请输入记过人数"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="严重警告人数" prop="seriousWarningCnt">
<el-input-number
v-model="form.seriousWarningCnt"
:min="0"
placeholder="请输入严重警告人数"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="警告人数" prop="warningCnt">
<el-input-number
v-model="form.warningCnt"
:min="0"
placeholder="请输入警告人数"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="撤销处分人数" prop="revokeCnt">
<el-input-number
v-model="form.revokeCnt"
:min="0"
placeholder="请输入撤销处分人数"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="退学人数" prop="dropCnt">
<el-input-number
v-model="form.dropCnt"
:min="0"
placeholder="请输入退学人数"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="总结报告" prop="summary">
<Editor
v-model:getHtml="form.summary"
:height="'400'"
placeholder="请输入总结报告" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ClassSummaryFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/classsummary'
import { getClassListByRole } from '/@/api/basic/basicclass'
import Editor from '/@/components/Editor/index.vue'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add')
const classList = ref<any[]>([])
// 提交表单数据
const form = reactive({
id: '',
classCode: '',
totalCnt: 0,
boyCnt: 0,
girlCnt: 0,
boyDormCnt: 0,
girlDormCnt: 0,
keepSchoolCnt: 0,
gigCnt: 0,
seriousWarningCnt: 0,
warningCnt: 0,
revokeCnt: 0,
dropCnt: 0,
summary: ''
})
// 定义校验规则
const dataRules = {
classCode: [
{ required: true, message: '请选择班号', trigger: 'change' }
],
totalCnt: [
{ required: true, message: '请输入总人数', trigger: 'blur' }
],
boyCnt: [
{ required: true, message: '请输入男生人数', trigger: 'blur' }
],
girlCnt: [
{ required: true, message: '请输入女生人数', trigger: 'blur' }
],
boyDormCnt: [
{ required: true, message: '请输入男(住宿)人数', trigger: 'blur' }
],
girlDormCnt: [
{ required: true, message: '请输入女(住宿)人数', trigger: 'blur' }
],
keepSchoolCnt: [
{ required: true, message: '请输入留校察看人数', trigger: 'blur' }
],
gigCnt: [
{ required: true, message: '请输入记过人数', trigger: 'blur' }
],
seriousWarningCnt: [
{ required: true, message: '请输入严重警告人数', trigger: 'blur' }
],
warningCnt: [
{ required: true, message: '请输入警告人数', trigger: 'blur' }
],
revokeCnt: [
{ required: true, message: '请输入撤销处分人数', trigger: 'blur' }
],
dropCnt: [
{ required: true, message: '请输入退学人数', trigger: 'blur' }
],
summary: [
{ required: true, message: '请输入总结报告', trigger: 'blur' }
]
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.classCode = ''
form.totalCnt = 0
form.boyCnt = 0
form.girlCnt = 0
form.boyDormCnt = 0
form.girlDormCnt = 0
form.keepSchoolCnt = 0
form.gigCnt = 0
form.seriousWarningCnt = 0
form.warningCnt = 0
form.revokeCnt = 0
form.dropCnt = 0
form.summary = ''
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.classCode = row.classCode || ''
form.totalCnt = row.totalCnt || 0
form.boyCnt = row.boyCnt || 0
form.girlCnt = row.girlCnt || 0
form.boyDormCnt = row.boyDormCnt || 0
form.girlDormCnt = row.girlDormCnt || 0
form.keepSchoolCnt = row.keepSchoolCnt || 0
form.gigCnt = row.gigCnt || 0
form.seriousWarningCnt = row.seriousWarningCnt || 0
form.warningCnt = row.warningCnt || 0
form.revokeCnt = row.revokeCnt || 0
form.dropCnt = row.dropCnt || 0
form.summary = row.summary || ''
// 如果需要获取详情
if (row.id && !row.summary) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.summary = res.data.summary || ''
}
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
const submitData = {
classCode: form.classCode,
totalCnt: form.totalCnt,
boyCnt: form.boyCnt,
girlCnt: form.girlCnt,
boyDormCnt: form.boyDormCnt,
girlDormCnt: form.girlDormCnt,
keepSchoolCnt: form.keepSchoolCnt,
gigCnt: form.gigCnt,
seriousWarningCnt: form.seriousWarningCnt,
warningCnt: form.warningCnt,
revokeCnt: form.revokeCnt,
dropCnt: form.dropCnt,
summary: form.summary
}
if (operType.value === 'add') {
await addObj(submitData)
useMessage().success('新增成功')
} else {
await editObj({
id: form.id,
...submitData
})
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 获取班号列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
}
} catch (err) {
console.error('获取班号列表失败', err)
}
}
// 初始化
onMounted(() => {
getClassListData()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.mb20 {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,371 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="state.queryForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="state.queryForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="开课部门" prop="deptCode">
<el-select
v-model="state.queryForm.deptCode"
placeholder="请选择开课部门"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班号" prop="classNo">
<el-select
v-model="state.queryForm.classNo"
placeholder="请选择班号"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classNo">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="state.queryForm.status"
placeholder="请选择状态"
clearable
style="width: 200px">
<el-option
v-for="item in statusList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center" />
<el-table-column prop="totalCnt" label="总人数" show-overflow-tooltip align="center" />
<el-table-column prop="boyCnt" label="男生人数" show-overflow-tooltip align="center" />
<el-table-column prop="girlCnt" label="女生人数" show-overflow-tooltip align="center" />
<el-table-column prop="boyDormCnt" label="男(住宿)" show-overflow-tooltip align="center" />
<el-table-column prop="girlDormCnt" label="女(住宿)" show-overflow-tooltip align="center" />
<el-table-column prop="keepSchoolCnt" label="留校察看人数" show-overflow-tooltip align="center" />
<el-table-column prop="gigCnt" label="记过人数" show-overflow-tooltip align="center" />
<el-table-column prop="seriousWarningCnt" label="严重警告人数" show-overflow-tooltip align="center" />
<el-table-column prop="warningCnt" label="警告人数" show-overflow-tooltip align="center" />
<el-table-column prop="revokeCnt" label="撤销处分人数" show-overflow-tooltip align="center" />
<el-table-column prop="dropCnt" label="退学人数" show-overflow-tooltip align="center" />
<el-table-column prop="status" label="状态" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatStatus(scope.row.status) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center" fixed="right">
<template #default="scope">
<el-button
icon="View"
text
type="primary"
@click="handleView(scope.row)">
查看
</el-button>
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
<!-- 查看详情弹窗 -->
<detail-dialog ref="detailDialogRef" />
</div>
</template>
<script setup lang="ts" name="ClassSummary">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/classsummary";
import { getTeachDept } from "/@/api/basic/basicdept";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import FormDialog from './form.vue'
import DetailDialog from './detail.vue'
// 定义变量内容
const formDialogRef = ref()
const detailDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const statusList = ref<any[]>([])
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
schoolYear: '',
schoolTerm: '',
deptCode: '',
classNo: '',
status: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化状态
const formatStatus = (value: string) => {
if (!value) return '-'
const item = statusList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.schoolYear = ''
state.queryForm.schoolTerm = ''
state.queryForm.deptCode = ''
state.queryForm.classNo = ''
state.queryForm.status = ''
getDataList()
}
// 查看
const handleView = (row: any) => {
detailDialogRef.value?.openDialog(row.id)
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该班级总结吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取开课部门列表
const getDeptListData = async () => {
try {
const res = await getTeachDept()
if (res.data && Array.isArray(res.data)) {
deptList.value = res.data
} else {
deptList.value = []
}
} catch (err) {
console.error('获取开课部门列表失败', err)
deptList.value = []
}
}
// 获取班号列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}
// 获取状态字典
const getStatusDict = async () => {
try {
const res = await getDicts('status_type')
if (res.data && Array.isArray(res.data)) {
statusList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
statusList.value = []
}
} catch (err) {
console.error('获取状态字典失败', err)
statusList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getDeptListData()
getClassListData()
getStatusDict()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,344 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="state.queryForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="state.queryForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="是否达标" prop="isDb">
<el-select
v-model="state.queryForm.isDb"
placeholder="请选择是否达标"
clearable
style="width: 200px">
<el-option
v-for="item in yesNoList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="state.queryForm.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班级" prop="classNo">
<el-select
v-model="state.queryForm.classNo"
placeholder="请选择班级"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classNo">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="handleInit">
初始化
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" />
<el-table-column prop="classNo" label="班级" show-overflow-tooltip align="center" />
<el-table-column prop="count" label="上传次数" show-overflow-tooltip align="center" />
<el-table-column prop="remarks" label="备注" show-overflow-tooltip align="center" min-width="200" />
<el-table-column label="操作" width="200" align="center" fixed="right">
<template #default="scope">
<el-button
icon="View"
text
type="primary"
@click="handleViewRecord(scope.row)">
查看上传记录
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 初始化弹窗 -->
<init-dialog ref="initDialogRef" @refresh="getDataList" />
<!-- 查看上传记录弹窗 -->
<record-dialog ref="recordDialogRef" />
</div>
</template>
<script setup lang="ts" name="ClassTheme">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/classtheme";
import { getDeptList } from "/@/api/basic/basicclass";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import InitDialog from './init.vue'
import RecordDialog from './record.vue'
// 定义变量内容
const initDialogRef = ref()
const recordDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const yesNoList = ref<any[]>([])
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
schoolYear: '',
schoolTerm: '',
isDb: '',
deptCode: '',
classNo: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.schoolYear = ''
state.queryForm.schoolTerm = ''
state.queryForm.isDb = ''
state.queryForm.deptCode = ''
state.queryForm.classNo = ''
getDataList()
}
// 初始化
const handleInit = () => {
initDialogRef.value?.openDialog()
}
// 查看上传记录
const handleViewRecord = (row: any) => {
recordDialogRef.value?.openDialog(row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该主题班会课教案吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptList()
if (res.data && Array.isArray(res.data)) {
deptList.value = res.data
} else {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取是否字典
const getYesNoDict = async () => {
try {
const res = await getDicts('yes_no')
if (res.data && Array.isArray(res.data)) {
yesNoList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
yesNoList.value = []
}
} catch (err) {
console.error('获取是否字典失败', err)
yesNoList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getDeptListData()
getClassListData()
getYesNoDict()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,240 @@
<template>
<el-dialog
title="初始化"
v-model="visible"
:width="600"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="form.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="form.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 100%">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="form.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="班级" prop="classNo">
<el-select
v-model="form.classNo"
placeholder="请选择班级"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classNo">
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ClassThemeInitDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { initObj } from '/@/api/stuwork/classtheme'
import { queryAllSchoolYear } from '/@/api/basic/basicyear'
import { getClassListByRole } from '/@/api/basic/basicclass'
import { getDeptList } from '/@/api/basic/basicclass'
import { getDicts } from '/@/api/admin/dict'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
// 提交表单数据
const form = reactive({
schoolYear: '',
schoolTerm: '',
deptCode: '',
classNo: ''
})
// 定义校验规则
const dataRules = {
schoolYear: [
{ required: true, message: '请选择学年', trigger: 'change' }
],
schoolTerm: [
{ required: true, message: '请选择学期', trigger: 'change' }
],
deptCode: [
{ required: true, message: '请选择学院', trigger: 'change' }
],
classNo: [
{ required: true, message: '请选择班级', trigger: 'change' }
]
}
// 打开弹窗
const openDialog = () => {
visible.value = true
nextTick(() => {
dataFormRef.value?.resetFields()
form.schoolYear = ''
form.schoolTerm = ''
form.deptCode = ''
form.classNo = ''
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
await initObj(form)
useMessage().success('初始化成功')
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || '初始化失败')
} finally {
loading.value = false
}
})
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
}
} catch (err) {
console.error('获取学年列表失败', err)
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
}
} catch (err) {
console.error('获取学期字典失败', err)
}
}
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptList()
if (res.data && Array.isArray(res.data)) {
deptList.value = res.data
}
} catch (err) {
console.error('获取学院列表失败', err)
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
}
} catch (err) {
console.error('获取班级列表失败', err)
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getDeptListData()
getClassListData()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.mb20 {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,227 @@
<template>
<el-dialog
title="查看上传记录"
v-model="visible"
:close-on-click-modal="false"
draggable
width="1000px">
<!-- 搜索表单 -->
<el-form :model="queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getRecordList" style="margin-bottom: 20px;">
<el-form-item label="标题" prop="title">
<el-input
v-model="queryForm.title"
placeholder="请输入标题"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getRecordList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作按钮 -->
<div style="margin-bottom: 20px;">
<el-button
icon="Plus"
type="primary"
@click="handleAdd">
新增
</el-button>
</div>
<div v-loading="loading">
<el-table
:data="recordList"
border
style="width: 100%">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="活动主题" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="fileUrl" label="附件" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<el-button
v-if="scope.row.fileUrl"
icon="Document"
text
type="primary"
size="small"
@click="handleViewFile(scope.row.fileUrl)">
查看
</el-button>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="上传时间" show-overflow-tooltip align="center" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column prop="createBy" label="上传人" show-overflow-tooltip align="center" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-if="pagination.total > 0"
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="pagination" />
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
</span>
</template>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" :current-row="currentRow" @refresh="getRecordList" />
</el-dialog>
</template>
<script setup lang="ts" name="ClassThemeRecordDialog">
import { ref, reactive } from 'vue'
import { fetchRecordList, delRecord } from '/@/api/stuwork/classtheme'
import { useMessage, useMessageBox } from '/@/hooks/message'
import { parseTime } from '/@/utils/formatTime'
import FormDialog from './recordForm.vue'
// 定义变量内容
const visible = ref(false)
const loading = ref(false)
const formDialogRef = ref()
const searchFormRef = ref()
const recordList = ref<any[]>([])
const currentRow = ref<any>(null)
const queryForm = reactive({
title: ''
})
const pagination = reactive({
current: 1,
size: 10,
total: 0
})
// 查看文件
const handleViewFile = (url: string) => {
if (url) {
window.open(url, '_blank')
}
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
queryForm.title = ''
pagination.current = 1
getRecordList()
}
// 新增
const handleAdd = () => {
formDialogRef.value?.openDialog('add')
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该上传记录吗?')
await delRecord([row.id])
useMessage().success('删除成功')
getRecordList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 打开弹窗
const openDialog = async (row: any) => {
visible.value = true
currentRow.value = row
recordList.value = []
queryForm.title = ''
pagination.current = 1
pagination.total = 0
if (row) {
await getRecordList()
}
}
// 获取上传记录列表
const getRecordList = async () => {
if (!currentRow.value) return
loading.value = true
try {
const res = await fetchRecordList({
schoolYear: currentRow.value.schoolYear,
schoolTerm: currentRow.value.schoolTerm,
deptCode: currentRow.value.deptCode,
classNo: currentRow.value.classNo,
title: queryForm.title,
current: pagination.current,
size: pagination.size
})
if (res.data && res.data.records) {
recordList.value = res.data.records
pagination.total = res.data.total || 0
} else {
recordList.value = []
pagination.total = 0
}
} catch (err) {
console.error('获取上传记录失败', err)
recordList.value = []
pagination.total = 0
} finally {
loading.value = false
}
}
// 分页大小改变
const sizeChangeHandle = (size: number) => {
pagination.size = size
pagination.current = 1
getRecordList()
}
// 当前页改变
const currentChangeHandle = (current: number) => {
pagination.current = current
getRecordList()
}
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,199 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
:width="800"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="标题" prop="title">
<el-input
v-model="form.title"
placeholder="请输入标题"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="附件上传">
<upload-file
v-model="form.fileUrl"
:limit="5"
:fileSize="10"
type="simple" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="内容" prop="content">
<Editor
v-model:getHtml="form.content"
:height="'400'"
placeholder="请输入内容" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="备注">
<el-input
v-model="form.remarks"
type="textarea"
:rows="4"
placeholder="请输入备注"
:maxlength="250"
show-word-limit
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ClassThemeRecordFormDialog">
import { ref, reactive, nextTick, computed } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addRecord, editRecord, getRecordDetail } from '/@/api/stuwork/classtheme'
import Editor from '/@/components/Editor/index.vue'
import UploadFile from '/@/components/Upload/index.vue'
const props = defineProps({
currentRow: {
type: Object,
default: null
}
})
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add')
// 提交表单数据
const form = reactive({
id: '',
title: '',
fileUrl: '',
content: '',
remarks: ''
})
// 定义校验规则
const dataRules = {
title: [
{ required: true, message: '请输入标题', trigger: 'blur' }
],
content: [
{ required: true, message: '请输入内容', trigger: 'blur' }
]
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.title = ''
form.fileUrl = ''
form.content = ''
form.remarks = ''
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.title = row.title || ''
form.fileUrl = row.fileUrl || ''
form.content = row.content || ''
form.remarks = row.remarks || ''
// 如果需要获取详情
if (row.id && !row.content) {
loading.value = true
getRecordDetail(row.id).then((res: any) => {
if (res.data) {
form.content = res.data.content || ''
form.fileUrl = res.data.fileUrl || ''
form.remarks = res.data.remarks || ''
}
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
if (!props.currentRow) {
useMessage().error('缺少必要信息')
return
}
loading.value = true
try {
const submitData = {
title: form.title,
fileUrl: form.fileUrl || '',
content: form.content,
remarks: form.remarks || '',
schoolYear: props.currentRow.schoolYear,
schoolTerm: props.currentRow.schoolTerm,
deptCode: props.currentRow.deptCode,
classNo: props.currentRow.classNo
}
if (operType.value === 'add') {
await addRecord(submitData)
useMessage().success('新增成功')
} else {
await editRecord({
id: form.id,
...submitData
})
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.mb20 {
margin-bottom: 20px;
}
</style>

View File

@@ -10,7 +10,8 @@
placeholder="请选择学院"
clearable
filterable
style="width: 200px">
style="width: 200px"
@change="handleDeptChange">
<el-option
v-for="item in deptList"
:key="item.deptCode"
@@ -27,7 +28,7 @@
filterable
style="width: 200px">
<el-option
v-for="item in classList"
v-for="item in filteredClassList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
@@ -48,7 +49,7 @@
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
@@ -72,12 +73,12 @@
</el-table-column>
<el-table-column prop="createTime" label="创建时间" show-overflow-tooltip align="center" width="180">
<template #default="scope">
<span>{{ scope.row.createTime ? scope.row.createTime.split(' ')[0] + ' ' + scope.row.createTime.split(' ')[1] : '-' }}</span>
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" show-overflow-tooltip align="center" width="180">
<template #default="scope">
<span>{{ scope.row.updateTime ? scope.row.updateTime.split(' ')[0] + ' ' + scope.row.updateTime.split(' ')[1] : '-' }}</span>
<span>{{ parseTime(scope.row.updateTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
@@ -103,12 +104,13 @@
</template>
<script setup lang="ts" name="PendingWork">
import { reactive, ref, onMounted } from 'vue'
import { reactive, ref, onMounted, computed } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList } from "/@/api/stuwork/pendingwork";
import { getDeptList } from "/@/api/basic/basicclass";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { useMessage } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
// 定义变量内容
const searchFormRef = ref()
@@ -154,6 +156,11 @@ const handleReset = () => {
getDataList()
}
// 学院变化时,清空班级选择并过滤班级列表
const handleDeptChange = () => {
searchForm.classCode = ''
}
// 查看详情
const handleView = (row: any) => {
useMessage().warning('查看详情功能待实现')
@@ -179,6 +186,8 @@ const getClassListData = async () => {
const res = await getClassListByRole()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
} else {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
@@ -186,6 +195,14 @@ const getClassListData = async () => {
}
}
// 根据学院过滤班级列表
const filteredClassList = computed(() => {
if (!searchForm.deptCode) {
return classList.value
}
return classList.value.filter((item: any) => item.deptCode === searchForm.deptCode)
})
// 初始化
onMounted(() => {
getDeptListData()
@@ -193,3 +210,6 @@ onMounted(() => {
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,210 @@
<template>
<el-dialog
:title="form.id ? '编辑文明班级' : '新增文明班级'"
v-model="visible"
:width="600"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="100px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="班号" prop="classCode">
<el-select
v-model="form.classCode"
placeholder="请选择班号"
clearable
filterable
style="width: 100%"
@change="handleClassChange">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="奖项名称" prop="ruleName">
<el-input
v-model="form.ruleName"
placeholder="请输入奖项名称"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="备注" prop="remarks">
<el-input
v-model="form.remarks"
type="textarea"
:rows="4"
placeholder="请输入备注"
:maxlength="250"
show-word-limit
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="RewardClassFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/rewardclass'
import { getClassListByRole } from '/@/api/basic/basicclass'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add')
const classList = ref<any[]>([])
// 提交表单数据
const form = reactive({
id: '',
classCode: '',
ruleId: '',
ruleName: '',
remarks: ''
})
// 定义校验规则
const dataRules = {
classCode: [
{ required: true, message: '请选择班号', trigger: 'change' }
],
ruleName: [
{ required: true, message: '请输入奖项名称', trigger: 'blur' }
]
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.classCode = ''
form.ruleId = ''
form.ruleName = ''
form.remarks = ''
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.classCode = row.classCode || ''
form.ruleId = row.ruleId || ''
form.ruleName = row.ruleName || ''
form.remarks = row.remarks || ''
// 如果需要获取详情
if (row.id && (!row.ruleName || !row.remarks)) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.classCode = res.data.classCode || ''
form.ruleId = res.data.ruleId || ''
form.ruleName = res.data.ruleName || ''
form.remarks = res.data.remarks || ''
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})
}
}
})
}
// 班级变化
const handleClassChange = () => {
// 可以根据班级获取相关信息,这里暂时不实现
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
const submitData = {
classCode: form.classCode,
ruleId: form.ruleId || '', // 如果没有ruleId传空字符串
ruleName: form.ruleName,
remarks: form.remarks || ''
}
if (operType.value === 'add') {
await addObj(submitData)
useMessage().success('新增成功')
} else {
await editObj({
id: form.id,
...submitData
})
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 初始化
onMounted(() => {
getClassListData()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,424 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="state.queryForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="state.queryForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="state.queryForm.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 200px"
@change="handleDeptChange">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班级" prop="classCode">
<el-select
v-model="state.queryForm.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<el-button
icon="Upload"
type="primary"
class="ml10"
@click="handleImport">
导入
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" />
<el-table-column prop="classNo" label="班级" show-overflow-tooltip align="center" />
<el-table-column prop="classMasterName" label="班主任" show-overflow-tooltip align="center" />
<el-table-column prop="ruleName" label="奖项名称" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="remarks" label="备注" show-overflow-tooltip align="center" min-width="200" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
<!-- 导入弹窗 -->
<el-dialog
title="导入文明班级"
v-model="importDialogVisible"
:width="500"
:close-on-click-modal="false"
draggable>
<div style="margin-bottom: 15px;">
<el-button
icon="Download"
type="success"
@click="handleDownloadTemplate">
下载模板
</el-button>
</div>
<el-upload
ref="uploadRef"
:auto-upload="false"
:on-change="handleFileChange"
:limit="1"
accept=".xlsx,.xls"
drag>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
只能上传 xlsx/xls 文件
</div>
</template>
</el-upload>
<template #footer>
<span class="dialog-footer">
<el-button @click="importDialogVisible = false"> </el-button>
<el-button type="primary" @click="handleImportSubmit" :disabled="!importFile || importLoading"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="RewardClass">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, importExcel } from "/@/api/stuwork/rewardclass";
import { getDeptList } from "/@/api/basic/basicclass";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { UploadFilled } from '@element-plus/icons-vue'
import FormDialog from './form.vue'
// 定义变量内容
const formDialogRef = ref()
const uploadRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const importDialogVisible = ref(false)
const importFile = ref<File | null>(null)
const importLoading = ref(false)
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
schoolYear: '',
schoolTerm: '',
deptCode: '',
classCode: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true // 页面加载时自动获取数据
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 学院变化
const handleDeptChange = () => {
// 可以根据学院筛选班级,这里暂时不实现
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.schoolYear = ''
state.queryForm.schoolTerm = ''
state.queryForm.deptCode = ''
state.queryForm.classCode = ''
getDataList()
}
// 导入
const handleImport = () => {
importDialogVisible.value = true
importFile.value = null
uploadRef.value?.clearFiles()
}
// 下载模板
const handleDownloadTemplate = async () => {
try {
const fileName = '文明班级导入模板.xlsx'
// 使用动态导入获取文件URL从 views/stuwork/rewardclass 到 assets/file 的相对路径
const fileUrl = new URL(`../../../assets/file/${fileName}`, import.meta.url).href
const response = await fetch(fileUrl)
if (!response.ok) {
throw new Error('文件下载失败')
}
const blob = await response.blob()
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = fileName
document.body.appendChild(link)
link.click()
window.URL.revokeObjectURL(url)
document.body.removeChild(link)
useMessage().success('模板下载成功')
} catch (error) {
console.error('模板下载失败', error)
useMessage().error('模板下载失败,请检查模板文件是否存在')
}
}
// 文件变化
const handleFileChange = (file: any) => {
importFile.value = file.raw
}
// 提交导入
const handleImportSubmit = async () => {
if (!importFile.value) {
useMessage().warning('请选择要导入的文件')
return
}
importLoading.value = true
try {
await importExcel(importFile.value)
useMessage().success('导入成功')
importDialogVisible.value = false
importFile.value = null
uploadRef.value?.clearFiles()
getDataList()
} catch (err: any) {
useMessage().error(err.msg || '导入失败')
} finally {
importLoading.value = false
}
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该文明班级记录吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptList()
if (res.data && Array.isArray(res.data)) {
deptList.value = res.data
} else {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getDeptListData()
getClassListData()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,204 @@
<template>
<el-dialog
:title="form.id ? '编辑文明宿舍' : '新增文明宿舍'"
v-model="visible"
:width="600"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="100px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="宿舍号" prop="roomNo">
<el-select
v-model="form.roomNo"
placeholder="请选择宿舍号"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in roomList"
:key="item.roomNo"
:label="item.roomNo"
:value="item.roomNo">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="奖项名称" prop="ruleName">
<el-input
v-model="form.ruleName"
placeholder="请输入奖项名称"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="备注" prop="remarks">
<el-input
v-model="form.remarks"
type="textarea"
:rows="4"
placeholder="请输入备注"
:maxlength="250"
show-word-limit
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="RewardDormFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/rewarddorm'
import { dormRoomList } from '/@/api/stuwork/dormroom'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add')
const roomList = ref<any[]>([])
// 提交表单数据
const form = reactive({
id: '',
roomNo: '',
ruleId: '',
ruleName: '',
remarks: ''
})
// 定义校验规则
const dataRules = {
roomNo: [
{ required: true, message: '请选择宿舍号', trigger: 'change' }
],
ruleName: [
{ required: true, message: '请输入奖项名称', trigger: 'blur' }
]
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.roomNo = ''
form.ruleId = ''
form.ruleName = ''
form.remarks = ''
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.roomNo = row.roomNo || ''
form.ruleId = row.ruleId || ''
form.ruleName = row.ruleName || ''
form.remarks = row.remarks || ''
// 如果需要获取详情
if (row.id && (!row.ruleName || !row.remarks)) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.roomNo = res.data.roomNo || ''
form.ruleId = res.data.ruleId || ''
form.ruleName = res.data.ruleName || ''
form.remarks = res.data.remarks || ''
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
const submitData = {
roomNo: form.roomNo,
ruleId: form.ruleId || '', // 如果没有ruleId传空字符串
ruleName: form.ruleName,
remarks: form.remarks || ''
}
if (operType.value === 'add') {
await addObj(submitData)
useMessage().success('新增成功')
} else {
await editObj({
id: form.id,
...submitData
})
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 获取宿舍列表
const getRoomListData = async () => {
try {
const res = await dormRoomList()
if (res.data && Array.isArray(res.data)) {
roomList.value = res.data
} else {
roomList.value = []
}
} catch (err) {
console.error('获取宿舍列表失败', err)
roomList.value = []
}
}
// 初始化
onMounted(() => {
getRoomListData()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,364 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="state.queryForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="state.queryForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="宿舍号" prop="roomNo">
<el-input
v-model="state.queryForm.roomNo"
placeholder="请输入宿舍号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="奖项名称" prop="ruleName">
<el-input
v-model="state.queryForm.ruleName"
placeholder="请输入奖项名称"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<el-button
icon="Upload"
type="primary"
class="ml10"
@click="handleImport">
导入
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="roomNo" label="宿舍号" show-overflow-tooltip align="center" />
<el-table-column prop="ruleName" label="奖项名称" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="remarks" label="备注" show-overflow-tooltip align="center" min-width="200" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
<!-- 导入弹窗 -->
<el-dialog
title="导入文明宿舍"
v-model="importDialogVisible"
:width="500"
:close-on-click-modal="false"
draggable>
<div style="margin-bottom: 15px;">
<el-button
icon="Download"
type="success"
@click="handleDownloadTemplate">
下载模板
</el-button>
</div>
<el-upload
ref="uploadRef"
:auto-upload="false"
:on-change="handleFileChange"
:limit="1"
accept=".xlsx,.xls"
drag>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
只能上传 xlsx/xls 文件
</div>
</template>
</el-upload>
<template #footer>
<span class="dialog-footer">
<el-button @click="importDialogVisible = false"> </el-button>
<el-button type="primary" @click="handleImportSubmit" :disabled="!importFile || importLoading"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="RewardDorm">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, importExcel } from "/@/api/stuwork/rewarddorm";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { UploadFilled } from '@element-plus/icons-vue'
import FormDialog from './form.vue'
// 定义变量内容
const formDialogRef = ref()
const uploadRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const importDialogVisible = ref(false)
const importFile = ref<File | null>(null)
const importLoading = ref(false)
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
schoolYear: '',
schoolTerm: '',
roomNo: '',
ruleName: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true // 页面加载时自动获取数据
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.schoolYear = ''
state.queryForm.schoolTerm = ''
state.queryForm.roomNo = ''
state.queryForm.ruleName = ''
getDataList()
}
// 导入
const handleImport = () => {
importDialogVisible.value = true
importFile.value = null
uploadRef.value?.clearFiles()
}
// 下载模板
const handleDownloadTemplate = async () => {
try {
const fileName = '文明宿舍导入模板.xlsx'
// 使用动态导入获取文件URL从 views/stuwork/rewarddorm 到 assets/file 的相对路径
const fileUrl = new URL(`../../../assets/file/${fileName}`, import.meta.url).href
const response = await fetch(fileUrl)
if (!response.ok) {
throw new Error('文件下载失败')
}
const blob = await response.blob()
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = fileName
document.body.appendChild(link)
link.click()
window.URL.revokeObjectURL(url)
document.body.removeChild(link)
useMessage().success('模板下载成功')
} catch (error) {
console.error('模板下载失败', error)
useMessage().error('模板下载失败,请检查模板文件是否存在')
}
}
// 文件变化
const handleFileChange = (file: any) => {
importFile.value = file.raw
}
// 提交导入
const handleImportSubmit = async () => {
if (!importFile.value) {
useMessage().warning('请选择要导入的文件')
return
}
importLoading.value = true
try {
await importExcel(importFile.value)
useMessage().success('导入成功')
importDialogVisible.value = false
importFile.value = null
uploadRef.value?.clearFiles()
getDataList()
} catch (err: any) {
useMessage().error(err.msg || '导入失败')
} finally {
importLoading.value = false
}
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该文明宿舍记录吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,201 @@
<template>
<el-dialog
:title="form.id ? '编辑评优评先奖项' : '新增评优评先奖项'"
v-model="visible"
:width="600"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="100px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="奖项名称" prop="ruleName">
<el-input
v-model="form.ruleName"
placeholder="请输入奖项名称"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="奖项类型" prop="ruleType">
<el-select
v-model="form.ruleType"
placeholder="请选择奖项类型"
clearable
style="width: 100%">
<el-option
v-for="item in ruleTypeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="备注" prop="remarks">
<el-input
v-model="form.remarks"
type="textarea"
:rows="4"
placeholder="请输入备注"
:maxlength="250"
show-word-limit
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="RewardRuleFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/rewardrule'
import { getDicts } from '/@/api/admin/dict'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add')
const ruleTypeList = ref<any[]>([])
// 提交表单数据
const form = reactive({
id: '',
ruleName: '',
ruleType: '',
remarks: ''
})
// 定义校验规则
const dataRules = {
ruleName: [
{ required: true, message: '请输入奖项名称', trigger: 'blur' }
],
ruleType: [
{ required: true, message: '请选择奖项类型', trigger: 'change' }
]
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.ruleName = ''
form.ruleType = ''
form.remarks = ''
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.ruleName = row.ruleName || ''
form.ruleType = row.ruleType || ''
form.remarks = row.remarks || ''
// 如果需要获取详情
if (row.id && (!row.ruleName || !row.ruleType)) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.ruleName = res.data.ruleName || ''
form.ruleType = res.data.ruleType || ''
form.remarks = res.data.remarks || ''
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
const submitData = {
ruleName: form.ruleName,
ruleType: form.ruleType,
remarks: form.remarks || ''
}
if (operType.value === 'add') {
await addObj(submitData)
useMessage().success('新增成功')
} else {
await editObj({
id: form.id,
...submitData
})
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 获取奖项类型字典
const getRuleTypeDict = async () => {
try {
const res = await getDicts('rule_type')
if (res.data && Array.isArray(res.data)) {
ruleTypeList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
ruleTypeList.value = []
}
} catch (err) {
console.error('获取奖项类型字典失败', err)
ruleTypeList.value = []
}
}
// 初始化
onMounted(() => {
getRuleTypeDict()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,197 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="奖项类型" prop="ruleType">
<el-select
v-model="state.queryForm.ruleType"
placeholder="请选择奖项类型"
clearable
style="width: 200px">
<el-option
v-for="item in ruleTypeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="ruleName" label="奖项名称" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="ruleType" label="奖项类型" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatRuleType(scope.row.ruleType) }}</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="备注" show-overflow-tooltip align="center" min-width="200" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="RewardRule">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/rewardrule";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import FormDialog from './form.vue'
// 定义变量内容
const formDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const ruleTypeList = ref<any[]>([])
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
ruleType: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true // 页面加载时自动获取数据
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 格式化奖项类型
const formatRuleType = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = ruleTypeList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.ruleType = ''
getDataList()
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该评优评先奖项吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取奖项类型字典
const getRuleTypeDict = async () => {
try {
const res = await getDicts('rule_type')
if (res.data && Array.isArray(res.data)) {
ruleTypeList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
ruleTypeList.value = []
}
} catch (err) {
console.error('获取奖项类型字典失败', err)
ruleTypeList.value = []
}
}
// 初始化
onMounted(() => {
getRuleTypeDict()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,317 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="queryForm.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班级" prop="classCode">
<el-select
v-model="queryForm.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="queryForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="queryForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Download"
type="primary"
class="ml10"
@click="handleExport">
导出
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="dataList"
v-loading="loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="departName" label="学院" show-overflow-tooltip align="center" />
<el-table-column prop="classNo" label="班级" show-overflow-tooltip align="center" />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip align="center" />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" />
<el-table-column prop="averageConduct" label="操行平均分" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ scope.row.averageConduct !== null && scope.row.averageConduct !== undefined ? scope.row.averageConduct.toFixed(2) : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="averageScore" label="总评成绩平均分" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ scope.row.averageScore !== null && scope.row.averageScore !== undefined ? scope.row.averageScore.toFixed(2) : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="violation" label="违规情况" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="ruleName" label="个人奖项" show-overflow-tooltip align="center" min-width="200">
<template #default="scope">
<span v-if="scope.row.ruleName && Array.isArray(scope.row.ruleName) && scope.row.ruleName.length > 0">
{{ scope.row.ruleName.join('') }}
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="upDateTime" label="保存时间" show-overflow-tooltip align="center" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.upDateTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script setup lang="ts" name="RewardStudent">
import { reactive, ref, onMounted } from 'vue'
import { fetchList, exportExcel } from "/@/api/stuwork/rewardstudent";
import { getDeptList } from "/@/api/basic/basicclass";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
// 定义变量内容
const searchFormRef = ref()
const showSearch = ref(true)
const loading = ref(false)
const dataList = ref<any[]>([])
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
// 查询表单
const queryForm = reactive({
deptCode: '',
classCode: '',
schoolYear: '',
schoolTerm: ''
})
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 获取数据列表
const getDataList = async () => {
loading.value = true
try {
const res = await fetchList(queryForm)
if (res.data && Array.isArray(res.data)) {
dataList.value = res.data
} else {
dataList.value = []
}
} catch (err: any) {
console.error('获取数据列表失败', err)
useMessage().error(err.msg || '获取数据列表失败')
dataList.value = []
} finally {
loading.value = false
}
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
queryForm.deptCode = ''
queryForm.classCode = ''
queryForm.schoolYear = ''
queryForm.schoolTerm = ''
getDataList()
}
// 导出
const handleExport = async () => {
try {
loading.value = true
const res = await exportExcel(queryForm)
// 创建blob对象
const blob = new Blob([res], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
// 创建下载链接
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
// 设置文件名
const fileName = `学生评优评先_${new Date().getTime()}.xlsx`
link.setAttribute('download', fileName)
// 触发下载
document.body.appendChild(link)
link.click()
// 清理
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
useMessage().success('导出成功')
} catch (err: any) {
console.error('导出失败', err)
useMessage().error(err.msg || '导出失败')
} finally {
loading.value = false
}
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptList()
if (res.data && Array.isArray(res.data)) {
deptList.value = res.data
} else {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getDeptListData()
getClassListData()
getDataList()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,322 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="state.queryForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="state.queryForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="handleAdd">
新增
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="批次名称" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="type" label="季度" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<span>{{ formatQuarter(scope.row.type) }}</span>
</template>
</el-table-column>
<el-table-column prop="classify" label="类别" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<span>{{ formatClassify(scope.row.classify) }}</span>
</template>
</el-table-column>
<el-table-column prop="moneyValue" label="补助金额" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ scope.row.moneyValue !== undefined && scope.row.moneyValue !== null ? scope.row.moneyValue : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="startTime" label="开始日期" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column prop="endTime" label="截止日期" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="备注" show-overflow-tooltip align="center" min-width="150" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="StipendTermBatch">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/stipendtermbatch";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
import FormDialog from './form.vue'
// 定义变量内容
const searchFormRef = ref()
const formDialogRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const quarterList = ref<any[]>([])
const classifyList = ref<any[]>([])
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
schoolYear: '',
schoolTerm: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化季度
const formatQuarter = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = quarterList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化类别
const formatClassify = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = classifyList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.schoolYear = ''
state.queryForm.schoolTerm = ''
getDataList()
}
// 新增
const handleAdd = () => {
formDialogRef.value?.openDialog('add')
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row.id)
}
// 删除
const handleDelete = async (row: any) => {
try {
const { confirm } = useMessageBox()
await confirm(`确定要删除批次 "${row.title}" 吗?`)
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取季度字典
const getQuarterDict = async () => {
try {
const res = await getDicts('stipend_term_batch_type')
if (res.data && Array.isArray(res.data)) {
quarterList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
quarterList.value = []
}
} catch (err) {
console.error('获取季度字典失败', err)
quarterList.value = []
}
}
// 获取类别字典
const getClassifyDict = async () => {
try {
const res = await getDicts('classify')
if (res.data && Array.isArray(res.data)) {
classifyList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
classifyList.value = []
}
} catch (err) {
console.error('获取类别字典失败', err)
classifyList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getQuarterDict()
getClassifyDict()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,378 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
:width="800"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="批次名称" prop="title">
<el-input
v-model="form.title"
placeholder="请输入批次名称"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="form.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="form.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 100%">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="季度" prop="type">
<el-select
v-model="form.type"
placeholder="请选择季度"
clearable
style="width: 100%">
<el-option
v-for="item in quarterList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="类别" prop="classify">
<el-select
v-model="form.classify"
placeholder="请选择类别"
clearable
style="width: 100%">
<el-option
v-for="item in classifyList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="补助金额" prop="money">
<el-select
v-model="form.money"
placeholder="请选择补助金额"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in moneyList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="开始日期" prop="startTime">
<el-date-picker
v-model="form.startTime"
type="date"
placeholder="请输入开始日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="截止日期" prop="endTime">
<el-date-picker
v-model="form.endTime"
type="date"
placeholder="请输入截止日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="备注" prop="remarks">
<el-input
v-model="form.remarks"
type="textarea"
:rows="3"
placeholder="请输入备注"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="StipendTermBatchForm">
import { ref, reactive, onMounted, nextTick } from 'vue'
import { addObj, editObj, getDetail } from "/@/api/stuwork/stipendtermbatch";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { useMessage } from "/@/hooks/message";
// 定义变量内容
const emit = defineEmits(['refresh'])
const visible = ref(false)
const loading = ref(false)
const dataFormRef = ref()
const operType = ref<'add' | 'edit'>('add')
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const quarterList = ref<any[]>([])
const classifyList = ref<any[]>([])
const moneyList = ref<any[]>([])
// 表单数据
const form = reactive({
id: '',
title: '',
schoolYear: '',
schoolTerm: '',
type: '',
classify: '',
money: '',
startTime: '',
endTime: '',
remarks: ''
})
// 表单验证规则
const dataRules = reactive({
title: [
{ required: true, message: '请输入批次名称', trigger: 'blur' }
],
schoolYear: [
{ required: true, message: '请选择学年', trigger: 'change' }
],
schoolTerm: [
{ required: true, message: '请选择学期', trigger: 'change' }
],
type: [
{ required: true, message: '请选择季度', trigger: 'change' }
],
classify: [
{ required: true, message: '请选择类别', trigger: 'change' }
],
money: [
{ required: true, message: '请选择补助金额', trigger: 'change' }
],
startTime: [
{ required: true, message: '请输入开始日期', trigger: 'change' }
],
endTime: [
{ required: true, message: '请输入截止日期', trigger: 'change' }
]
})
// 打开弹窗
const openDialog = async (type: 'add' | 'edit', id?: string) => {
visible.value = true
operType.value = type
// 重置表单
Object.assign(form, {
id: '',
title: '',
schoolYear: '',
schoolTerm: '',
type: '',
classify: '',
money: '',
startTime: '',
endTime: '',
remarks: ''
})
// 编辑时获取详情
if (type === 'edit' && id) {
loading.value = true
try {
const res = await getDetail(id)
if (res.data) {
Object.assign(form, {
id: res.data.id,
title: res.data.title || '',
schoolYear: res.data.schoolYear || '',
schoolTerm: res.data.schoolTerm || '',
type: res.data.type || '',
classify: res.data.classify || '',
money: res.data.money || '',
startTime: res.data.startTime || '',
endTime: res.data.endTime || '',
remarks: res.data.remarks || ''
})
}
} catch (err: any) {
useMessage().error(err.msg || '获取详情失败')
visible.value = false
} finally {
loading.value = false
}
}
// 清除验证
nextTick(() => {
dataFormRef.value?.clearValidate()
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
if (operType.value === 'add') {
await addObj(form)
useMessage().success('新增成功')
} else {
await editObj(form)
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
}
} catch (err) {
console.error('获取学年列表失败', err)
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
}
} catch (err) {
console.error('获取学期字典失败', err)
}
}
// 获取季度字典
const getQuarterDict = async () => {
try {
const res = await getDicts('stipend_term_batch_type')
if (res.data && Array.isArray(res.data)) {
quarterList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
}
} catch (err) {
console.error('获取季度字典失败', err)
}
}
// 获取类别字典
const getClassifyDict = async () => {
try {
const res = await getDicts('classify')
if (res.data && Array.isArray(res.data)) {
classifyList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
}
} catch (err) {
console.error('获取类别字典失败', err)
}
}
// 获取补助金额字典
const getMoneyDict = async () => {
try {
const res = await getDicts('stipendMoney')
if (res.data && Array.isArray(res.data)) {
moneyList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
}
} catch (err) {
console.error('获取补助金额字典失败', err)
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getQuarterDict()
getClassifyDict()
getMoneyDict()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,378 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
:width="800"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="批次名称" prop="title">
<el-input
v-model="form.title"
placeholder="请输入批次名称"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="form.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="form.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 100%">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="季度" prop="type">
<el-select
v-model="form.type"
placeholder="请选择季度"
clearable
style="width: 100%">
<el-option
v-for="item in quarterList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="类别" prop="classify">
<el-select
v-model="form.classify"
placeholder="请选择类别"
clearable
style="width: 100%">
<el-option
v-for="item in classifyList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="补助金额" prop="money">
<el-select
v-model="form.money"
placeholder="请选择补助金额"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in moneyList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="开始日期" prop="startTime">
<el-date-picker
v-model="form.startTime"
type="date"
placeholder="请输入开始日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="截止日期" prop="endTime">
<el-date-picker
v-model="form.endTime"
type="date"
placeholder="请输入截止日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="备注" prop="remarks">
<el-input
v-model="form.remarks"
type="textarea"
:rows="3"
placeholder="请输入备注"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="StipendTermBatchForm">
import { ref, reactive, onMounted, nextTick } from 'vue'
import { addObj, editObj, getDetail } from "/@/api/stuwork/stipendtermbatch";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { useMessage } from "/@/hooks/message";
// 定义变量内容
const emit = defineEmits(['refresh'])
const visible = ref(false)
const loading = ref(false)
const dataFormRef = ref()
const operType = ref<'add' | 'edit'>('add')
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const quarterList = ref<any[]>([])
const classifyList = ref<any[]>([])
const moneyList = ref<any[]>([])
// 表单数据
const form = reactive({
id: '',
title: '',
schoolYear: '',
schoolTerm: '',
type: '',
classify: '',
money: '',
startTime: '',
endTime: '',
remarks: ''
})
// 表单验证规则
const dataRules = reactive({
title: [
{ required: true, message: '请输入批次名称', trigger: 'blur' }
],
schoolYear: [
{ required: true, message: '请选择学年', trigger: 'change' }
],
schoolTerm: [
{ required: true, message: '请选择学期', trigger: 'change' }
],
type: [
{ required: true, message: '请选择季度', trigger: 'change' }
],
classify: [
{ required: true, message: '请选择类别', trigger: 'change' }
],
money: [
{ required: true, message: '请选择补助金额', trigger: 'change' }
],
startTime: [
{ required: true, message: '请输入开始日期', trigger: 'change' }
],
endTime: [
{ required: true, message: '请输入截止日期', trigger: 'change' }
]
})
// 打开弹窗
const openDialog = async (type: 'add' | 'edit', id?: string) => {
visible.value = true
operType.value = type
// 重置表单
Object.assign(form, {
id: '',
title: '',
schoolYear: '',
schoolTerm: '',
type: '',
classify: '',
money: '',
startTime: '',
endTime: '',
remarks: ''
})
// 编辑时获取详情
if (type === 'edit' && id) {
loading.value = true
try {
const res = await getDetail(id)
if (res.data) {
Object.assign(form, {
id: res.data.id,
title: res.data.title || '',
schoolYear: res.data.schoolYear || '',
schoolTerm: res.data.schoolTerm || '',
type: res.data.type || '',
classify: res.data.classify || '',
money: res.data.money || '',
startTime: res.data.startTime || '',
endTime: res.data.endTime || '',
remarks: res.data.remarks || ''
})
}
} catch (err: any) {
useMessage().error(err.msg || '获取详情失败')
visible.value = false
} finally {
loading.value = false
}
}
// 清除验证
nextTick(() => {
dataFormRef.value?.clearValidate()
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
if (operType.value === 'add') {
await addObj(form)
useMessage().success('新增成功')
} else {
await editObj(form)
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
}
} catch (err) {
console.error('获取学年列表失败', err)
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
}
} catch (err) {
console.error('获取学期字典失败', err)
}
}
// 获取季度字典
const getQuarterDict = async () => {
try {
const res = await getDicts('stipend_term_batch_type')
if (res.data && Array.isArray(res.data)) {
quarterList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
}
} catch (err) {
console.error('获取季度字典失败', err)
}
}
// 获取类别字典
const getClassifyDict = async () => {
try {
const res = await getDicts('classify')
if (res.data && Array.isArray(res.data)) {
classifyList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
}
} catch (err) {
console.error('获取类别字典失败', err)
}
}
// 获取补助金额字典
const getMoneyDict = async () => {
try {
const res = await getDicts('stipendMoney')
if (res.data && Array.isArray(res.data)) {
moneyList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
}
} catch (err) {
console.error('获取补助金额字典失败', err)
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getQuarterDict()
getClassifyDict()
getMoneyDict()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,322 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="state.queryForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="state.queryForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="handleAdd">
新增
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="批次名称" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="type" label="季度" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<span>{{ formatQuarter(scope.row.type) }}</span>
</template>
</el-table-column>
<el-table-column prop="classify" label="类别" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<span>{{ formatClassify(scope.row.classify) }}</span>
</template>
</el-table-column>
<el-table-column prop="moneyValue" label="补助金额" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ scope.row.moneyValue !== undefined && scope.row.moneyValue !== null ? scope.row.moneyValue : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="startTime" label="开始日期" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column prop="endTime" label="截止日期" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column prop="remarks" label="备注" show-overflow-tooltip align="center" min-width="150" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="StipendTermBatch">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/stipendtermbatch";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
import FormDialog from './form.vue'
// 定义变量内容
const searchFormRef = ref()
const formDialogRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const quarterList = ref<any[]>([])
const classifyList = ref<any[]>([])
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
schoolYear: '',
schoolTerm: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化季度
const formatQuarter = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = quarterList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化类别
const formatClassify = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = classifyList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.schoolYear = ''
state.queryForm.schoolTerm = ''
getDataList()
}
// 新增
const handleAdd = () => {
formDialogRef.value?.openDialog('add')
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row.id)
}
// 删除
const handleDelete = async (row: any) => {
try {
const { confirm } = useMessageBox()
await confirm(`确定要删除批次 "${row.title}" 吗?`)
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取季度字典
const getQuarterDict = async () => {
try {
const res = await getDicts('stipend_term_batch_type')
if (res.data && Array.isArray(res.data)) {
quarterList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
quarterList.value = []
}
} catch (err) {
console.error('获取季度字典失败', err)
quarterList.value = []
}
}
// 获取类别字典
const getClassifyDict = async () => {
try {
const res = await getDicts('classify')
if (res.data && Array.isArray(res.data)) {
classifyList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
classifyList.value = []
}
} catch (err) {
console.error('获取类别字典失败', err)
classifyList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getQuarterDict()
getClassifyDict()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,362 @@
<template>
<el-dialog
:title="form.id ? '编辑社团' : '新增社团'"
v-model="visible"
:width="800"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="社团名称" prop="associationName">
<el-input
v-model="form.associationName"
placeholder="请输入社团名称"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="form.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="指导老师" prop="teacherNo">
<el-select
v-model="form.teacherNo"
placeholder="请选择指导老师"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in teacherList"
:key="item.teacherNo"
:label="item.realName"
:value="item.teacherNo">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="负责人" prop="maintainer">
<el-input
v-model="form.maintainer"
placeholder="请输入负责人姓名"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="成立时间" prop="openTime">
<el-date-picker
v-model="form.openTime"
type="date"
placeholder="请选择成立时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="联系电话" prop="tel">
<el-input
v-model="form.tel"
placeholder="请输入联系电话"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="所属类别" prop="type">
<el-select
v-model="form.type"
placeholder="请选择所属类别"
clearable
style="width: 100%">
<el-option
v-for="item in typeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="成立申请" prop="applyNote">
<el-input
v-model="form.applyNote"
type="textarea"
:rows="4"
placeholder="请输入成立申请"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="社团章程" prop="ruleNote">
<el-input
v-model="form.ruleNote"
type="textarea"
:rows="4"
placeholder="请输入社团章程"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="StuAssociationFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/stuassociation'
import { getDeptList } from '/@/api/basic/basicclass'
import { getDicts } from '/@/api/admin/dict'
import request from '/@/utils/request'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add')
const deptList = ref<any[]>([])
const teacherList = ref<any[]>([])
const typeList = ref<any[]>([])
// 提交表单数据
const form = reactive({
id: '',
associationName: '',
deptCode: '',
teacherNo: '',
maintainer: '',
openTime: '',
tel: '',
type: '',
applyNote: '',
ruleNote: ''
})
// 定义校验规则
const dataRules = {
associationName: [
{ required: true, message: '请输入社团名称', trigger: 'blur' }
],
deptCode: [
{ required: true, message: '请选择学院', trigger: 'change' }
],
teacherNo: [
{ required: true, message: '请选择指导老师', trigger: 'change' }
],
maintainer: [
{ required: true, message: '请输入负责人姓名', trigger: 'blur' }
],
openTime: [
{ required: true, message: '请选择成立时间', trigger: 'change' }
],
tel: [
{ required: true, message: '请输入联系电话', trigger: 'blur' }
],
type: [
{ required: true, message: '请选择所属类别', trigger: 'change' }
],
applyNote: [
{ required: true, message: '请输入成立申请', trigger: 'blur' }
],
ruleNote: [
{ required: true, message: '请输入社团章程', trigger: 'blur' }
]
}
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptList()
if (res.data && Array.isArray(res.data)) {
deptList.value = res.data
} else {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
// 获取教师列表
const getTeacherList = async () => {
try {
const res = await request({
url: '/professional/teacherbase/TeacherBaseList',
method: 'get'
})
if (res.data && Array.isArray(res.data)) {
teacherList.value = res.data
} else {
teacherList.value = []
}
} catch (err) {
console.error('获取教师列表失败', err)
teacherList.value = []
}
}
// 获取类别字典
const getTypeDict = async () => {
try {
const res = await getDicts('association_type')
if (res.data && Array.isArray(res.data)) {
typeList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
typeList.value = []
}
} catch (err) {
console.error('获取类别字典失败', err)
typeList.value = []
}
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
operType.value = type
visible.value = true
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.associationName = ''
form.deptCode = ''
form.teacherNo = ''
form.maintainer = ''
form.openTime = ''
form.tel = ''
form.type = ''
form.applyNote = ''
form.ruleNote = ''
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.associationName = row.associationName || ''
form.deptCode = row.deptCode || ''
form.teacherNo = row.teacherNo || ''
form.maintainer = row.maintainer || ''
form.openTime = row.openTime || ''
form.tel = row.tel || ''
form.type = row.type || ''
form.applyNote = row.applyNote || ''
form.ruleNote = row.ruleNote || ''
// 如果需要获取详情
if (row.id && (!row.associationName || !row.deptCode)) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.associationName = res.data.associationName || ''
form.deptCode = res.data.deptCode || ''
form.teacherNo = res.data.teacherNo || ''
form.maintainer = res.data.maintainer || ''
form.openTime = res.data.openTime || ''
form.tel = res.data.tel || ''
form.type = res.data.type || ''
form.applyNote = res.data.applyNote || ''
form.ruleNote = res.data.ruleNote || ''
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (valid) {
loading.value = true
try {
const submitData = {
associationName: form.associationName,
deptCode: form.deptCode,
teacherNo: form.teacherNo,
maintainer: form.maintainer,
openTime: form.openTime,
tel: form.tel,
type: form.type,
applyNote: form.applyNote,
ruleNote: form.ruleNote
}
if (operType.value === 'edit' && form.id) {
await editObj({ ...submitData, id: form.id })
useMessage().success('编辑成功')
} else {
await addObj(submitData)
useMessage().success('新增成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'edit' ? '编辑失败' : '新增失败'))
} finally {
loading.value = false
}
}
})
}
// 初始化
onMounted(() => {
getDeptListData()
getTeacherList()
getTypeDict()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,274 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="state.queryForm.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="指导老师姓名" prop="teacherRealName">
<el-input
v-model="state.queryForm.teacherRealName"
placeholder="请输入指导老师姓名"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="所属类别" prop="type">
<el-select
v-model="state.queryForm.type"
placeholder="请选择所属类别"
clearable
style="width: 200px">
<el-option
v-for="item in typeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="associationName" label="社团名称" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="teacherRealName" label="指导老师姓名" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="maintainer" label="负责人" show-overflow-tooltip align="center" width="100" />
<el-table-column prop="num" label="人数" show-overflow-tooltip align="center" width="80">
<template #default="scope">
<span>{{ scope.row.num !== undefined && scope.row.num !== null ? scope.row.num : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="openTime" label="成立时间" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ parseTime(scope.row.openTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column prop="tel" label="联系电话" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="type" label="所属类别" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ formatType(scope.row.type) }}</span>
</template>
</el-table-column>
<el-table-column prop="applyNote" label="成立申请" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="ruleNote" label="社团章程" show-overflow-tooltip align="center" min-width="200" />
<el-table-column label="操作" width="200" align="center" fixed="right">
<template #default="scope">
<el-button
icon="User"
text
type="primary"
@click="handleViewMember(scope.row)">
查看成员
</el-button>
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
<!-- 查看成员弹窗 -->
<member-dialog ref="memberDialogRef" />
</div>
</template>
<script setup lang="ts" name="StuAssociation">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/stuassociation";
import { getDeptList } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
import FormDialog from './form.vue'
import MemberDialog from './member.vue'
// 定义变量内容
const formDialogRef = ref()
const memberDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const deptList = ref<any[]>([])
const typeList = ref<any[]>([])
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
deptCode: '',
teacherRealName: '',
type: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true // 页面加载时自动获取数据
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 格式化类型
const formatType = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = typeList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.deptCode = ''
state.queryForm.teacherRealName = ''
state.queryForm.type = ''
getDataList()
}
// 查看成员
const handleViewMember = (row: any) => {
memberDialogRef.value?.openDialog(row.id, row.associationName)
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该社团吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptList()
if (res.data && Array.isArray(res.data)) {
deptList.value = res.data
} else {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
// 获取类别字典
const getTypeDict = async () => {
try {
const res = await getDicts('association_type')
if (res.data && Array.isArray(res.data)) {
typeList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
typeList.value = []
}
} catch (err) {
console.error('获取类别字典失败', err)
typeList.value = []
}
}
// 初始化
onMounted(() => {
getDeptListData()
getTypeDict()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,134 @@
<template>
<el-dialog
title="查看成员"
v-model="visible"
:close-on-click-modal="false"
draggable
width="1000px">
<div v-loading="loading">
<div style="margin-bottom: 15px;">
<el-text type="primary" size="large">社团名称{{ associationName || '-' }}</el-text>
</div>
<el-table
:data="memberList"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" width="100" />
<el-table-column prop="phone" label="联系电话" show-overflow-tooltip align="center" width="120" />
</el-table>
<!-- 分页 -->
<pagination
v-if="pagination.total > 0"
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="pagination" />
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="StuAssociationMemberDialog">
import { ref, reactive } from 'vue'
import { fetchList } from '/@/api/stuwork/stuassociationmember'
import { BasicTableProps, useTable } from "/@/hooks/table";
// 定义变量内容
const visible = ref(false)
const loading = ref(false)
const associationName = ref('')
const associationId = ref('')
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 成员列表
const memberList = ref<any[]>([])
// 分页配置
const pagination = reactive({
current: 1,
size: 10,
total: 0
})
// 打开弹窗
const openDialog = async (id: string, name: string) => {
visible.value = true
associationId.value = id
associationName.value = name || ''
memberList.value = []
pagination.current = 1
pagination.total = 0
await getMemberList()
}
// 获取成员列表
const getMemberList = async () => {
if (!associationId.value) return
loading.value = true
try {
const res = await fetchList({
associationId: associationId.value,
current: pagination.current,
size: pagination.size
})
if (res.data) {
if (res.data.records && Array.isArray(res.data.records)) {
memberList.value = res.data.records
} else if (Array.isArray(res.data)) {
memberList.value = res.data
} else {
memberList.value = []
}
pagination.total = res.data.total || res.data.totalCount || 0
} else {
memberList.value = []
pagination.total = 0
}
} catch (err) {
console.error('获取成员列表失败', err)
memberList.value = []
pagination.total = 0
} finally {
loading.value = false
}
}
// 分页变化
const sizeChangeHandle = (size: number) => {
pagination.size = size
pagination.current = 1
getMemberList()
}
const currentChangeHandle = (current: number) => {
pagination.current = current
getMemberList()
}
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,302 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
:width="800"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="班级" prop="classCode">
<el-select
v-model="form.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 100%"
@change="handleClassChange">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学号" prop="stuNo">
<el-select
v-model="form.stuNo"
:placeholder="form.classCode ? (studentLoading ? '加载中...' : '请选择学号') : '请先选择班级'"
clearable
filterable
style="width: 100%"
:disabled="!form.classCode || studentLoading"
:loading="studentLoading"
@change="handleStudentChange">
<el-option
v-for="item in studentList"
:key="item.stuNo"
:label="`${item.realName} (${item.stuNo})`"
:value="item.stuNo">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="需关爱类型" prop="careType">
<el-select
v-model="form.careType"
placeholder="请选择需关爱类型"
clearable
style="width: 100%">
<el-option
v-for="item in careTypeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="危机表现" prop="present">
<el-input
v-model="form.present"
type="textarea"
:rows="4"
placeholder="请输入危机表现"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="记录时间" prop="recordDate">
<el-date-picker
v-model="form.recordDate"
type="date"
placeholder="选择记录时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="StuCareFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/stucare'
import { getClassListByRole } from '/@/api/basic/basicclass'
import { queryAllStudentByClassCode } from '/@/api/basic/basicstudent'
import { getDicts } from '/@/api/admin/dict'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const studentLoading = ref(false)
const operType = ref('add')
const classList = ref<any[]>([])
const studentList = ref<any[]>([])
const careTypeList = ref<any[]>([])
// 提交表单数据
const form = reactive({
id: '',
classCode: '',
stuNo: '',
realName: '',
careType: '',
present: '',
recordDate: ''
})
// 定义校验规则
const dataRules = {
classCode: [
{ required: true, message: '请选择班级', trigger: 'change' }
],
stuNo: [
{ required: true, message: '请选择学号', trigger: 'change' }
],
careType: [
{ required: true, message: '请选择需关爱类型', trigger: 'change' }
],
present: [
{ required: true, message: '请输入危机表现', trigger: 'blur' }
],
recordDate: [
{ required: true, message: '请选择记录时间', trigger: 'change' }
]
}
// 班级变化
const handleClassChange = async () => {
form.stuNo = ''
form.realName = ''
studentList.value = []
if (form.classCode) {
studentLoading.value = true
try {
const res = await queryAllStudentByClassCode(form.classCode)
if (res.data && Array.isArray(res.data)) {
studentList.value = res.data
} else {
studentList.value = []
}
} catch (err) {
console.error('获取学生列表失败', err)
useMessage().error('获取学生列表失败')
studentList.value = []
} finally {
studentLoading.value = false
}
}
}
// 学生变化
const handleStudentChange = () => {
const student = studentList.value.find(item => item.stuNo === form.stuNo)
if (student) {
form.realName = student.realName || ''
} else {
form.realName = ''
}
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.classCode = ''
form.stuNo = ''
form.realName = ''
form.careType = ''
form.present = ''
form.recordDate = ''
studentList.value = []
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.classCode = row.classCode || ''
form.stuNo = row.stuNo || ''
form.realName = row.realName || ''
form.careType = row.careType || ''
form.present = row.present || ''
form.recordDate = row.recordDate || ''
// 加载学生列表
if (form.classCode) {
handleClassChange()
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
const submitData = {
classCode: form.classCode,
stuNo: form.stuNo,
careType: form.careType,
present: form.present,
recordDate: form.recordDate
}
if (operType.value === 'add') {
await addObj(submitData)
useMessage().success('新增成功')
} else {
await editObj({
id: form.id,
...submitData
})
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
}
} catch (err) {
console.error('获取班级列表失败', err)
}
}
// 获取需关爱类型字典
const getCareTypeDict = async () => {
try {
const res = await getDicts('stu_care_type')
if (res.data && Array.isArray(res.data)) {
careTypeList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
}
} catch (err) {
console.error('获取需关爱类型字典失败', err)
}
}
// 初始化
onMounted(() => {
getClassListData()
getCareTypeDict()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.mb20 {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,378 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="state.queryForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="state.queryForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="state.queryForm.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班级" prop="classCode">
<el-select
v-model="state.queryForm.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="state.queryForm.realName"
placeholder="请输入姓名"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="学号" prop="stuNo">
<el-input
v-model="state.queryForm.stuNo"
placeholder="请输入学号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" />
<el-table-column prop="classNo" label="班级" show-overflow-tooltip align="center" />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" />
<el-table-column prop="teacherRealName" label="班主任姓名" show-overflow-tooltip align="center" />
<el-table-column prop="teacherTel" label="班主任电话" show-overflow-tooltip align="center" />
<el-table-column prop="careType" label="需关爱类型" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatCareType(scope.row.careType) }}</span>
</template>
</el-table-column>
<el-table-column prop="present" label="危机表现" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="recordDate" label="记录时间" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ scope.row.recordDate || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="result" label="干预结果" show-overflow-tooltip align="center" min-width="150">
<template #default="scope">
<span>{{ scope.row.result || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="250" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Document"
text
type="success"
@click="handleResult(scope.row)">
干预结果
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
<!-- 干预结果弹窗 -->
<result-dialog ref="resultDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="StuCare">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/stucare";
import { getDeptList } from "/@/api/basic/basicclass";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import FormDialog from './form.vue'
import ResultDialog from './result.vue'
// 定义变量内容
const formDialogRef = ref()
const resultDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const careTypeList = ref<any[]>([])
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
schoolYear: '',
schoolTerm: '',
deptCode: '',
classCode: '',
realName: '',
stuNo: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化需关爱类型
const formatCareType = (value: string) => {
if (!value) return '-'
const item = careTypeList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.schoolYear = ''
state.queryForm.schoolTerm = ''
state.queryForm.deptCode = ''
state.queryForm.classCode = ''
state.queryForm.realName = ''
state.queryForm.stuNo = ''
getDataList()
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 干预结果
const handleResult = (row: any) => {
resultDialogRef.value?.openDialog(row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该心理健康记录吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptList()
if (res.data && Array.isArray(res.data)) {
deptList.value = res.data
} else {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取需关爱类型字典
const getCareTypeDict = async () => {
try {
const res = await getDicts('stu_care_type')
if (res.data && Array.isArray(res.data)) {
careTypeList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
careTypeList.value = []
}
} catch (err) {
console.error('获取需关爱类型字典失败', err)
careTypeList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getDeptListData()
getClassListData()
getCareTypeDict()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,151 @@
<template>
<el-dialog
title="干预结果"
v-model="visible"
:width="600"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="学生姓名">
<el-input v-model="form.realName" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="学号">
<el-input v-model="form.stuNo" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="班级">
<el-input v-model="form.classNo" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="干预结果" prop="result">
<el-input
v-model="form.result"
type="textarea"
:rows="6"
placeholder="请输入干预结果"
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="StuCareResultDialog">
import { ref, reactive, nextTick } from 'vue'
import { useMessage } from '/@/hooks/message'
import { updateResult, getDetail } from '/@/api/stuwork/stucare'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
// 提交表单数据
const form = reactive({
id: '',
realName: '',
stuNo: '',
classNo: '',
result: ''
})
// 定义校验规则
const dataRules = {
result: [
{ required: true, message: '请输入干预结果', trigger: 'blur' }
]
}
// 打开弹窗
const openDialog = async (row: any) => {
visible.value = true
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.realName = ''
form.stuNo = ''
form.classNo = ''
form.result = ''
if (row) {
form.id = row.id
form.realName = row.realName || ''
form.stuNo = row.stuNo || ''
form.classNo = row.classNo || ''
form.result = row.result || ''
// 如果没有干预结果,尝试获取详情
if (row.id && !row.result) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.result = res.data.result || ''
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
await updateResult({
id: form.id,
result: form.result
})
useMessage().success('保存成功')
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || '保存失败')
} finally {
loading.value = false
}
})
}
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.mb20 {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,406 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
:width="800"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="form.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 100%"
:disabled="!!form.id">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="form.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 100%"
:disabled="!!form.id">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="班级" prop="classCode">
<el-select
v-model="form.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 100%"
@change="handleClassChange">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学生" prop="stuNo">
<el-select
v-model="form.stuNo"
:placeholder="form.classCode ? '请选择学生' : '请先选择班级'"
clearable
filterable
style="width: 100%"
:disabled="!form.classCode || studentLoading"
:loading="studentLoading"
@change="handleStudentChange">
<el-option
v-for="item in studentList"
:key="item.stuNo"
:label="`${item.realName} (${item.stuNo})`"
:value="item.stuNo">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="分数" prop="score">
<el-input-number
v-model="form.score"
:min="0"
:max="100"
placeholder="请输入分数"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="类型" prop="conductType">
<el-select
v-model="form.conductType"
placeholder="请选择类型"
clearable
style="width: 100%">
<el-option
v-for="item in typeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="考核时间" prop="recordDate">
<el-date-picker
v-model="form.recordDate"
type="date"
placeholder="选择考核时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="情况记录" prop="description">
<el-input
v-model="form.description"
type="textarea"
:rows="4"
placeholder="请输入情况记录"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="附件" prop="attachment">
<upload-file
v-model="form.attachment"
:limit="5"
:fileSize="10"
type="simple" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="StuConductFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/stuconduct'
import { getClassListByRole } from '/@/api/basic/basicclass'
import { queryAllSchoolYear } from '/@/api/basic/basicyear'
import { queryAllStudentByClassCode } from '/@/api/basic/basicstudent'
import { getDicts } from '/@/api/admin/dict'
import UploadFile from '/@/components/Upload/index.vue'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const studentLoading = ref(false)
const operType = ref('add')
const classList = ref<any[]>([])
const studentList = ref<any[]>([])
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const typeList = ref<any[]>([])
// 提交表单数据
const form = reactive({
id: '',
schoolYear: '',
schoolTerm: '',
classCode: '',
stuNo: '',
score: 0,
conductType: '',
recordDate: '',
description: '',
attachment: ''
})
// 定义校验规则
const dataRules = {
schoolYear: [
{ required: true, message: '请选择学年', trigger: 'change' }
],
schoolTerm: [
{ required: true, message: '请选择学期', trigger: 'change' }
],
classCode: [
{ required: true, message: '请选择班级', trigger: 'change' }
],
stuNo: [
{ required: true, message: '请选择学生', trigger: 'change' }
],
score: [
{ required: true, message: '请输入分数', trigger: 'blur' }
],
conductType: [
{ required: true, message: '请选择类型', trigger: 'change' }
],
recordDate: [
{ required: true, message: '请选择考核时间', trigger: 'change' }
],
description: [
{ required: true, message: '请输入情况记录', trigger: 'blur' }
]
// attachment 非必填,不需要验证规则
}
// 班级变化
const handleClassChange = async () => {
form.stuNo = ''
studentList.value = []
if (form.classCode) {
studentLoading.value = true
try {
const res = await queryAllStudentByClassCode(form.classCode)
if (res.data && Array.isArray(res.data)) {
studentList.value = res.data
} else {
studentList.value = []
}
} catch (err) {
console.error('获取学生列表失败', err)
useMessage().error('获取学生列表失败')
studentList.value = []
} finally {
studentLoading.value = false
}
}
}
// 学生变化
const handleStudentChange = () => {
// 学生选择后可以做一些处理,这里暂时不需要
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
visible.value = true
operType.value = type
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.schoolYear = ''
form.schoolTerm = ''
form.classCode = ''
form.stuNo = ''
form.score = 0
form.conductType = ''
form.recordDate = ''
form.description = ''
form.attachment = ''
studentList.value = []
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.schoolYear = row.schoolYear || ''
form.schoolTerm = row.schoolTerm || ''
form.classCode = row.classCode || ''
form.stuNo = row.stuNo || ''
form.score = row.score || 0
form.conductType = row.conductType || ''
form.recordDate = row.recordDate || ''
form.description = row.description || ''
form.attachment = row.attachment || ''
// 加载学生列表
if (form.classCode) {
handleClassChange()
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
const submitData = {
schoolYear: form.schoolYear,
schoolTerm: form.schoolTerm,
classCode: form.classCode,
stuNo: form.stuNo,
score: form.score,
conductType: form.conductType,
recordDate: form.recordDate,
description: form.description,
attachment: form.attachment || ''
}
if (operType.value === 'add') {
await addObj(submitData)
useMessage().success('新增成功')
} else {
await editObj({
id: form.id,
...submitData
})
useMessage().success('编辑成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'))
} finally {
loading.value = false
}
})
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
}
} catch (err) {
console.error('获取学年列表失败', err)
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
}
} catch (err) {
console.error('获取学期字典失败', err)
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
}
} catch (err) {
console.error('获取班级列表失败', err)
}
}
// 获取类型字典
const getTypeDict = async () => {
try {
const res = await getDicts('conduct_type')
if (res.data && Array.isArray(res.data)) {
typeList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
}
} catch (err) {
console.error('获取类型字典失败', err)
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getClassListData()
getTypeDict()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.mb20 {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,472 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="state.queryForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="state.queryForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="state.queryForm.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 200px"
@change="handleDeptChange">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班级" prop="classCode">
<el-select
v-model="state.queryForm.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="state.queryForm.realName"
placeholder="请输入姓名"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="类型" prop="conductType">
<el-select
v-model="state.queryForm.conductType"
placeholder="请选择类型"
clearable
style="width: 200px">
<el-option
v-for="item in typeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<el-button
icon="Upload"
type="primary"
class="ml10"
@click="handleImport">
导入
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip align="center" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" />
<el-table-column prop="classNo" label="班级" show-overflow-tooltip align="center" />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" />
<el-table-column prop="conductType" label="类型" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ formatType(scope.row.conductType) }}</span>
</template>
</el-table-column>
<el-table-column prop="recordDate" label="考核时间" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ scope.row.recordDate || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="description" label="情况记录" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="attachment" label="附件" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<el-button
v-if="scope.row.attachment"
icon="Document"
text
type="primary"
size="small"
@click="handleViewAttachment(scope.row)">
查看
</el-button>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
<!-- 导入弹窗 -->
<el-dialog
title="导入操行考核"
v-model="importDialogVisible"
:width="500"
:close-on-click-modal="false"
draggable>
<el-upload
ref="uploadRef"
:auto-upload="false"
:on-change="handleFileChange"
:limit="1"
accept=".xlsx,.xls"
drag>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
只能上传 xlsx/xls 文件
</div>
</template>
</el-upload>
<template #footer>
<span class="dialog-footer">
<el-button @click="importDialogVisible = false"> </el-button>
<el-button type="primary" @click="handleImportSubmit" :disabled="!importFile || importLoading"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="StuConduct">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, importExcel } from "/@/api/stuwork/stuconduct";
import { getDeptList } from "/@/api/basic/basicclass";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { UploadFilled } from '@element-plus/icons-vue'
import FormDialog from './form.vue'
// 定义变量内容
const formDialogRef = ref()
const uploadRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const typeList = ref<any[]>([])
const importDialogVisible = ref(false)
const importFile = ref<File | null>(null)
const importLoading = ref(false)
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
schoolYear: '',
schoolTerm: '',
deptCode: '',
classCode: '',
realName: '',
conductType: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化类型
const formatType = (value: string) => {
if (!value) return '-'
const item = typeList.value.find((item: any) => item.value === value)
return item ? item.label : value
}
// 学院变化
const handleDeptChange = () => {
// 可以根据学院筛选班级,这里暂时不实现
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.schoolYear = ''
state.queryForm.schoolTerm = ''
state.queryForm.deptCode = ''
state.queryForm.classCode = ''
state.queryForm.realName = ''
state.queryForm.conductType = ''
getDataList()
}
// 查看附件
const handleViewAttachment = (row: any) => {
if (row.attachment) {
const urls = typeof row.attachment === 'string' ? row.attachment.split(',') : [row.attachment]
urls.forEach((url: string) => {
if (url.trim()) {
window.open(url.trim(), '_blank')
}
})
}
}
// 导入
const handleImport = () => {
importDialogVisible.value = true
importFile.value = null
uploadRef.value?.clearFiles()
}
// 文件变化
const handleFileChange = (file: any) => {
importFile.value = file.raw
}
// 提交导入
const handleImportSubmit = async () => {
if (!importFile.value) {
useMessage().warning('请选择要导入的文件')
return
}
importLoading.value = true
try {
await importExcel(importFile.value)
useMessage().success('导入成功')
importDialogVisible.value = false
importFile.value = null
uploadRef.value?.clearFiles()
getDataList()
} catch (err: any) {
useMessage().error(err.msg || '导入失败')
} finally {
importLoading.value = false
}
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该操行考核记录吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptList()
if (res.data && Array.isArray(res.data)) {
deptList.value = res.data
} else {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取类型字典
const getTypeDict = async () => {
try {
const res = await getDicts('conduct_type')
if (res.data && Array.isArray(res.data)) {
typeList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
typeList.value = []
}
} catch (err) {
console.error('获取类型字典失败', err)
typeList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getDeptListData()
getClassListData()
getTypeDict()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,372 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="queryForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="queryForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班级" prop="classCode">
<el-select
v-model="queryForm.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 统计表格 -->
<el-row style="margin-bottom: 20px;">
<el-table
:data="statisticsData"
v-loading="loading"
border
style="width: 100%"
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column prop="label" label="" width="120" align="center" fixed="left" />
<el-table-column prop="classNo" label="班级" min-width="150" align="center" />
<el-table-column prop="excellent" label="优秀" min-width="100" align="center" />
<el-table-column prop="good" label="良好" min-width="100" align="center" />
<el-table-column prop="pass" label="及格" min-width="100" align="center" />
<el-table-column prop="fail" label="不及格" min-width="100" align="center" />
</el-table>
</el-row>
<!-- 学生列表表格 -->
<el-row>
<el-table
:data="studentList"
v-loading="loading"
border
:max-height="600"
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip align="center" />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" />
<el-table-column prop="score" label="学期总评" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ scope.row.score !== null && scope.row.score !== undefined ? scope.row.score.toFixed(2) : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="View"
text
type="primary"
@click="handleView(scope.row)">
查看
</el-button>
</template>
</el-table-column>
</el-table>
</el-row>
</div>
</div>
</template>
<script setup lang="ts" name="StuConductTerm">
import { reactive, ref, onMounted, computed } from 'vue'
import { getStuConductTerm } from "/@/api/stuwork/stuconduct";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { useMessage } from "/@/hooks/message";
// 表格样式 - 在组件内部定义,不从外部导入
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 定义变量内容
const searchFormRef = ref()
const showSearch = ref(true)
const loading = ref(false)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const classList = ref<any[]>([])
const studentList = ref<any[]>([])
// 查询表单
const queryForm = reactive({
schoolYear: '',
schoolTerm: '',
classCode: ''
})
// 统计表格数据
const statisticsData = computed(() => {
if (studentList.value.length === 0) {
return []
}
// 计算各等级人数
// 优秀:>=90良好80-89及格60-79不及格<60
let excellent = 0 // 优秀
let good = 0 // 良好
let pass = 0 // 及格
let fail = 0 // 不及格
const total = studentList.value.length
studentList.value.forEach((student: any) => {
const score = student.score
if (score !== null && score !== undefined) {
if (score >= 90) {
excellent++
} else if (score >= 80) {
good++
} else if (score >= 60) {
pass++
} else {
fail++
}
}
})
// 计算比率
const excellentRate = total > 0 ? ((excellent / total) * 100).toFixed(2) + '%' : '0%'
const goodRate = total > 0 ? ((good / total) * 100).toFixed(2) + '%' : '0%'
const passRate = total > 0 ? ((pass / total) * 100).toFixed(2) + '%' : '0%'
const failRate = total > 0 ? ((fail / total) * 100).toFixed(2) + '%' : '0%'
// 优良率 = (优秀 + 良好) / 总人数
const excellentGoodCount = excellent + good
const excellentGoodRate = total > 0 ? ((excellentGoodCount / total) * 100).toFixed(2) + '%' : '0%'
// 获取班级名称
const classNo = studentList.value.length > 0 ? (studentList.value[0].classNo || '-') : '-'
return [
{
label: '人数',
classNo: classNo,
excellent: excellent,
good: good,
pass: pass,
fail: fail
},
{
label: '比率',
classNo: classNo,
excellent: excellentRate,
good: goodRate,
pass: passRate,
fail: failRate
},
{
label: '优良率',
classNo: classNo,
excellent: excellentGoodRate,
good: '-',
pass: '-',
fail: '-'
},
{
label: '备注',
classNo: classNo,
excellent: '-',
good: '-',
pass: '-',
fail: '-'
}
]
})
// 获取数据列表
const getDataList = async () => {
if (!queryForm.schoolYear || !queryForm.schoolTerm || !queryForm.classCode) {
useMessage().warning('请选择学年、学期和班级')
return
}
loading.value = true
try {
const res = await getStuConductTerm({
schoolYear: queryForm.schoolYear,
schoolTerm: queryForm.schoolTerm,
classCode: queryForm.classCode
})
if (res.data && Array.isArray(res.data)) {
// 处理返回的数据,提取学生列表
// 根据API文档返回的是StuConductTermVO数组
const tempList: any[] = []
res.data.forEach((item: any) => {
// 如果返回的数据结构中有basicStudentVOList需要展开
if (item.basicStudentVOList && Array.isArray(item.basicStudentVOList) && item.basicStudentVOList.length > 0) {
item.basicStudentVOList.forEach((student: any) => {
tempList.push({
stuNo: student.stuNo || item.stuNo,
realName: student.realName || item.realName,
score: item.score, // 学期总评分数
classNo: item.classNo,
classCode: item.classCode
})
})
} else {
// 直接使用item作为学生信息
tempList.push({
stuNo: item.stuNo,
realName: item.realName,
score: item.score,
classNo: item.classNo,
classCode: item.classCode
})
}
})
studentList.value = tempList
} else {
studentList.value = []
}
} catch (err: any) {
console.error('获取数据列表失败', err)
useMessage().error(err.msg || '获取数据列表失败')
studentList.value = []
} finally {
loading.value = false
}
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
queryForm.schoolYear = ''
queryForm.schoolTerm = ''
queryForm.classCode = ''
studentList.value = []
}
// 查看详情
const handleView = (row: any) => {
// 可以跳转到详情页面或打开详情弹窗
useMessage().info('查看详情功能待实现')
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getClassListData()
})
</script>
<style scoped lang="scss">
.layout-padding {
.layout-padding-auto {
.layout-padding-view {
.el-row {
margin-bottom: 20px;
}
}
}
}
// 确保页面可以滚动
.layout-padding {
height: 100%;
overflow-y: auto;
.layout-padding-auto {
height: 100%;
.layout-padding-view {
height: 100%;
overflow-y: auto;
}
}
}
</style>

View File

@@ -0,0 +1,334 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="queryForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班级" prop="classCode">
<el-select
v-model="queryForm.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 统计表格 -->
<el-row style="margin-bottom: 20px;">
<el-table
:data="statisticsData"
v-loading="loading"
border
style="width: 100%"
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column prop="label" label="" width="120" align="center" fixed="left" />
<el-table-column prop="classNo" label="班级" min-width="150" align="center" />
<el-table-column prop="excellent" label="优秀" min-width="100" align="center" />
<el-table-column prop="good" label="良好" min-width="100" align="center" />
<el-table-column prop="pass" label="及格" min-width="100" align="center" />
<el-table-column prop="fail" label="不及格" min-width="100" align="center" />
</el-table>
</el-row>
<!-- 学生列表表格 -->
<el-row>
<el-table
:data="studentList"
v-loading="loading"
border
:max-height="600"
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip align="center" />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" />
<el-table-column prop="score" label="学年总评" show-overflow-tooltip align="center">
<template #default="scope">
<span>{{ scope.row.score !== null && scope.row.score !== undefined ? scope.row.score.toFixed(2) : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="View"
text
type="primary"
@click="handleView(scope.row)">
查看
</el-button>
</template>
</el-table-column>
</el-table>
</el-row>
</div>
</div>
</template>
<script setup lang="ts" name="StuConductYear">
import { reactive, ref, onMounted, computed } from 'vue'
import { getStuConductYear } from "/@/api/stuwork/stuconduct";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { useMessage } from "/@/hooks/message";
// 表格样式 - 在组件内部定义,不从外部导入
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 定义变量内容
const searchFormRef = ref()
const showSearch = ref(true)
const loading = ref(false)
const schoolYearList = ref<any[]>([])
const classList = ref<any[]>([])
const studentList = ref<any[]>([])
// 查询表单
const queryForm = reactive({
schoolYear: '',
classCode: ''
})
// 统计表格数据
const statisticsData = computed(() => {
if (studentList.value.length === 0) {
return []
}
// 计算各等级人数
// 优秀:>=90良好80-89及格60-79不及格<60
let excellent = 0 // 优秀
let good = 0 // 良好
let pass = 0 // 及格
let fail = 0 // 不及格
const total = studentList.value.length
studentList.value.forEach((student: any) => {
const score = student.score
if (score !== null && score !== undefined) {
if (score >= 90) {
excellent++
} else if (score >= 80) {
good++
} else if (score >= 60) {
pass++
} else {
fail++
}
}
})
// 计算比率
const excellentRate = total > 0 ? ((excellent / total) * 100).toFixed(2) + '%' : '0%'
const goodRate = total > 0 ? ((good / total) * 100).toFixed(2) + '%' : '0%'
const passRate = total > 0 ? ((pass / total) * 100).toFixed(2) + '%' : '0%'
const failRate = total > 0 ? ((fail / total) * 100).toFixed(2) + '%' : '0%'
// 优良率 = (优秀 + 良好) / 总人数
const excellentGoodCount = excellent + good
const excellentGoodRate = total > 0 ? ((excellentGoodCount / total) * 100).toFixed(2) + '%' : '0%'
// 获取班级名称
const classNo = studentList.value.length > 0 ? (studentList.value[0].classNo || '-') : '-'
return [
{
label: '人数',
classNo: classNo,
excellent: excellent,
good: good,
pass: pass,
fail: fail
},
{
label: '比率',
classNo: classNo,
excellent: excellentRate,
good: goodRate,
pass: passRate,
fail: failRate
},
{
label: '优良率',
classNo: classNo,
excellent: excellentGoodRate,
good: '-',
pass: '-',
fail: '-'
},
{
label: '备注',
classNo: classNo,
excellent: '-',
good: '-',
pass: '-',
fail: '-'
}
]
})
// 获取数据列表
const getDataList = async () => {
if (!queryForm.schoolYear || !queryForm.classCode) {
useMessage().warning('请选择学年和班级')
return
}
loading.value = true
try {
const res = await getStuConductYear({
schoolYear: queryForm.schoolYear,
classCode: queryForm.classCode
})
if (res.data && Array.isArray(res.data)) {
// 处理返回的数据,提取学生列表
// 根据API文档返回的是StuConductYearVO数组
const tempList: any[] = []
res.data.forEach((item: any) => {
// 如果返回的数据结构中有basicStudentVOList需要展开
if (item.basicStudentVOList && Array.isArray(item.basicStudentVOList) && item.basicStudentVOList.length > 0) {
item.basicStudentVOList.forEach((student: any) => {
tempList.push({
stuNo: student.stuNo || item.stuNo,
realName: student.realName || item.realName,
score: item.score, // 学年总评分数
classNo: item.classNo,
classCode: item.classCode
})
})
} else {
// 直接使用item作为学生信息
tempList.push({
stuNo: item.stuNo,
realName: item.realName,
score: item.score,
classNo: item.classNo,
classCode: item.classCode
})
}
})
studentList.value = tempList
} else {
studentList.value = []
}
} catch (err: any) {
console.error('获取数据列表失败', err)
useMessage().error(err.msg || '获取数据列表失败')
studentList.value = []
} finally {
loading.value = false
}
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
queryForm.schoolYear = ''
queryForm.classCode = ''
studentList.value = []
}
// 查看详情
const handleView = (row: any) => {
// 可以跳转到详情页面或打开详情弹窗
useMessage().info('查看详情功能待实现')
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getClassListData()
})
</script>
<style scoped lang="scss">
.layout-padding {
.layout-padding-auto {
.layout-padding-view {
.el-row {
margin-bottom: 20px;
}
}
}
}
// 确保页面可以滚动
.layout-padding {
height: 100%;
overflow-y: auto;
.layout-padding-auto {
height: 100%;
.layout-padding-view {
height: 100%;
overflow-y: auto;
}
}
}
</style>

View File

@@ -0,0 +1,216 @@
<template>
<el-dialog
title="编辑学生会"
v-model="visible"
:width="700"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="学生会名称" prop="unionName">
<el-input
v-model="form.unionName"
placeholder="学生会名称"
disabled
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="类型" prop="unionType">
<el-select
v-model="form.unionType"
placeholder="类型"
disabled
style="width: 100%">
<el-option
v-for="item in unionTypeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="联系地址" prop="address">
<el-input
v-model="form.address"
placeholder="请输入联系地址"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="负责人" prop="maintainer">
<el-input
v-model="form.maintainer"
placeholder="请输入负责人"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="form.sort"
:min="0"
placeholder="请输入排序"
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="StuUnionFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { editObj, getDetail } from '/@/api/stuwork/stuunion'
import { getDicts } from '/@/api/admin/dict'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const unionTypeList = ref<any[]>([])
// 提交表单数据
const form = reactive({
id: '',
unionName: '',
unionType: '',
address: '',
maintainer: '',
sort: undefined as number | undefined
})
// 定义校验规则
const dataRules = {
address: [
{ required: true, message: '请输入联系地址', trigger: 'blur' }
],
maintainer: [
{ required: true, message: '请输入负责人', trigger: 'blur' }
]
}
// 获取类型字典
const getUnionTypeDict = async () => {
try {
const res = await getDicts('union_type')
if (res.data && Array.isArray(res.data)) {
unionTypeList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
unionTypeList.value = []
}
} catch (err) {
console.error('获取类型字典失败', err)
unionTypeList.value = []
}
}
// 打开弹窗
const openDialog = async (type: string = 'edit', row?: any) => {
visible.value = true
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.unionName = ''
form.unionType = ''
form.address = ''
form.maintainer = ''
form.sort = undefined
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.unionName = row.unionName || ''
form.unionType = row.unionType || ''
form.address = row.address || ''
form.maintainer = row.maintainer || ''
form.sort = row.sort !== undefined && row.sort !== null ? row.sort : undefined
// 如果需要获取详情
if (row.id && (!row.address || !row.maintainer)) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.unionName = res.data.unionName || ''
form.unionType = res.data.unionType || ''
form.address = res.data.address || ''
form.maintainer = res.data.maintainer || ''
form.sort = res.data.sort !== undefined && res.data.sort !== null ? res.data.sort : undefined
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (valid) {
loading.value = true
try {
const submitData = {
id: form.id,
address: form.address,
maintainer: form.maintainer,
sort: form.sort !== undefined && form.sort !== null ? form.sort : undefined
}
await editObj(submitData)
useMessage().success('编辑成功')
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || '编辑失败')
} finally {
loading.value = false
}
}
})
}
// 初始化
onMounted(() => {
getUnionTypeDict()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,140 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="unionName" label="学生会名称" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="unionType" label="类型" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ formatUnionType(scope.row.unionType) }}</span>
</template>
</el-table-column>
<el-table-column prop="address" label="联系地址" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="maintainer" label="负责人" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="sort" label="排序" show-overflow-tooltip align="center" width="80">
<template #default="scope">
<span>{{ scope.row.sort !== undefined && scope.row.sort !== null ? scope.row.sort : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="100" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="StuUnion">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList } from "/@/api/stuwork/stuunion";
import { useMessage } from "/@/hooks/message";
import { getDicts } from "/@/api/admin/dict";
import FormDialog from './form.vue'
// 定义变量内容
const formDialogRef = ref()
const showSearch = ref(false)
const unionTypeList = ref<any[]>([])
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true // 页面加载时自动获取数据
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 格式化类型
const formatUnionType = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = unionTypeList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 获取类型字典
const getUnionTypeDict = async () => {
try {
const res = await getDicts('union_type')
if (res.data && Array.isArray(res.data)) {
unionTypeList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
unionTypeList.value = []
}
} catch (err) {
console.error('获取类型字典失败', err)
unionTypeList.value = []
}
}
// 初始化
onMounted(() => {
getUnionTypeDict()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,183 @@
<template>
<el-dialog
:title="form.id ? '编辑团员' : '新增团员'"
v-model="visible"
:width="800"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="学号" prop="stuNo">
<el-input
v-model="form.stuNo"
placeholder="请输入学号"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="入团时间" prop="enterTime">
<el-date-picker
v-model="form.enterTime"
type="date"
placeholder="请输入入团时间"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="团员编号" prop="serNo">
<el-input
v-model="form.serNo"
placeholder="请输入团员编号"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="团内职务" prop="position">
<el-input
v-model="form.position"
placeholder="请输入团内职务"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="StuUnionLeagueFormDialog">
import { ref, reactive, nextTick } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/stuunionleague'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add')
// 提交表单数据
const form = reactive({
id: '',
stuNo: '',
enterTime: '',
serNo: '',
position: ''
})
// 定义校验规则
const dataRules = {
stuNo: [
{ required: true, message: '请输入学号', trigger: 'blur' }
],
enterTime: [
{ required: true, message: '请输入入团时间', trigger: 'change' }
],
serNo: [
{ required: true, message: '请输入团员编号', trigger: 'blur' }
]
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
operType.value = type
visible.value = true
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.stuNo = ''
form.enterTime = ''
form.serNo = ''
form.position = ''
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.stuNo = row.stuNo || ''
form.enterTime = row.enterTime || ''
form.serNo = row.serNo || ''
form.position = row.position || ''
// 如果需要获取详情
if (row.id && (!row.stuNo || !row.enterTime)) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.stuNo = res.data.stuNo || ''
form.enterTime = res.data.enterTime || ''
form.serNo = res.data.serNo || ''
form.position = res.data.position || ''
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (valid) {
loading.value = true
try {
const submitData = {
stuNo: form.stuNo,
enterTime: form.enterTime,
serNo: form.serNo,
position: form.position
}
if (operType.value === 'edit' && form.id) {
await editObj({ ...submitData, id: form.id })
useMessage().success('编辑成功')
} else {
await addObj(submitData)
useMessage().success('新增成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'edit' ? '编辑失败' : '新增失败'))
} finally {
loading.value = false
}
}
})
}
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,368 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="班级" prop="classCode">
<el-select
v-model="state.queryForm.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班号" prop="classNo">
<el-input
v-model="state.queryForm.classNo"
placeholder="请输入班号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="state.queryForm.realName"
placeholder="请输入姓名"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="团员编号" prop="serNo">
<el-input
v-model="state.queryForm.serNo"
placeholder="请输入团员编号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="入学年份" prop="grade">
<el-select
v-model="state.queryForm.grade"
placeholder="请选择入学年份"
clearable
style="width: 200px">
<el-option
v-for="item in gradeList"
:key="item.grade"
:label="item.grade"
:value="item.grade">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<el-button
icon="Upload"
type="primary"
class="ml10"
@click="handleImport">
导入
</el-button>
<el-button
icon="Download"
type="primary"
class="ml10"
@click="handleExport">
导出
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="classNo" label="班级" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" width="100" />
<el-table-column prop="phone" label="联系电话" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="enterTime" label="入团时间" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ parseTime(scope.row.enterTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column prop="serNo" label="团员编号" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="position" label="团内职务" show-overflow-tooltip align="center" width="120" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
<!-- 导入弹窗 -->
<el-dialog
title="导入团员"
v-model="importDialogVisible"
:close-on-click-modal="false"
width="500px">
<el-upload
ref="uploadRef"
:auto-upload="false"
:limit="1"
:on-exceed="handleExceed"
:on-change="handleFileChange"
:file-list="fileList"
accept=".xlsx,.xls"
drag>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
只能上传 xlsx/xls 文件
</div>
</template>
</el-upload>
<template #footer>
<span class="dialog-footer">
<el-button @click="importDialogVisible = false"> </el-button>
<el-button type="primary" @click="handleImportSubmit" :loading="importLoading"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="StuUnionLeague">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, importExcel, exportExcel } from "/@/api/stuwork/stuunionleague";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { getGradeList } from "/@/api/basic/basicclass";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
import { UploadFilled } from '@element-plus/icons-vue';
import type { UploadFile, UploadFiles } from 'element-plus';
import FormDialog from './form.vue'
// 定义变量内容
const formDialogRef = ref()
const searchFormRef = ref()
const uploadRef = ref()
const showSearch = ref(true)
const classList = ref<any[]>([])
const gradeList = ref<any[]>([])
const importDialogVisible = ref(false)
const importLoading = ref(false)
const fileList = ref<UploadFile[]>([])
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
classCode: '',
classNo: '',
realName: '',
serNo: '',
grade: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.classCode = ''
state.queryForm.classNo = ''
state.queryForm.realName = ''
state.queryForm.serNo = ''
state.queryForm.grade = ''
getDataList()
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该团员吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 导入
const handleImport = () => {
importDialogVisible.value = true
fileList.value = []
}
// 文件变化
const handleFileChange = (file: UploadFile, files: UploadFiles) => {
fileList.value = [file]
}
// 文件超出限制
const handleExceed = () => {
useMessage().warning('只能上传一个文件')
}
// 提交导入
const handleImportSubmit = async () => {
if (fileList.value.length === 0) {
useMessage().warning('请选择要上传的文件')
return
}
const file = fileList.value[0].raw
if (!file) {
useMessage().warning('文件不存在')
return
}
importLoading.value = true
try {
await importExcel(file as File)
useMessage().success('导入成功')
importDialogVisible.value = false
fileList.value = []
getDataList()
} catch (err: any) {
useMessage().error(err.msg || '导入失败')
} finally {
importLoading.value = false
}
}
// 导出
const handleExport = async () => {
try {
const res = await exportExcel(state.queryForm)
const blob = new Blob([res.data as BlobPart])
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `团员列表_${parseTime(new Date(), '{y}{m}{d}{h}{i}{s}')}.xlsx`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
useMessage().success('导出成功')
} catch (err: any) {
useMessage().error(err.msg || '导出失败')
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取入学年份列表
const getGradeListData = async () => {
try {
const res = await getGradeList()
if (res.data && Array.isArray(res.data)) {
gradeList.value = res.data
} else {
gradeList.value = []
}
} catch (err) {
console.error('获取入学年份列表失败', err)
gradeList.value = []
}
}
// 初始化
onMounted(() => {
getClassListData()
getGradeListData()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,371 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="班级" prop="classCode">
<el-select
v-model="state.queryForm.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Document"
type="primary"
class="ml10"
:disabled="selectedRows.length === 0"
@click="handleApply">
申请免学费
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="classNo" label="班级" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="majorName" label="专业" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" width="100" />
<el-table-column prop="gender" label="性别" show-overflow-tooltip align="center" width="80">
<template #default="scope">
<span>{{ formatGender(scope.row.gender) }}</span>
</template>
</el-table-column>
<el-table-column prop="idCard" label="身份证号" show-overflow-tooltip align="center" min-width="180" />
<el-table-column prop="bankCard" label="中职卡号" show-overflow-tooltip align="center" width="150" />
<el-table-column prop="phone" label="联系方式" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="education" label="生源" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<span>{{ formatEducation(scope.row.education) }}</span>
</template>
</el-table-column>
<el-table-column prop="majorLevel" label="层次" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<span>{{ formatMajorLevel(scope.row.majorLevel) }}</span>
</template>
</el-table-column>
<el-table-column prop="grade" label="入学年份" show-overflow-tooltip align="center" width="100" />
<el-table-column prop="gradeCurr" label="当前年纪" show-overflow-tooltip align="center" width="100" />
<el-table-column prop="temporaryyeYear" label="借读学年" show-overflow-tooltip align="center" width="120" />
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 申请免学费弹窗 -->
<el-dialog
title="申请免学费"
v-model="applyDialogVisible"
:width="500"
:close-on-click-modal="false"
draggable>
<el-form
ref="applyFormRef"
:model="applyForm"
:rules="applyRules"
label-width="120px">
<el-form-item label="批次" prop="termId">
<el-select
v-model="applyForm.termId"
placeholder="请选择批次"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in termList"
:key="item.id"
:label="item.title"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="applyDialogVisible = false"> </el-button>
<el-button type="primary" @click="handleSubmit" :disabled="applyLoading"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="TuitionFreeStuApply">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, applyObj } from "/@/api/stuwork/tuitionfreestuapply";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import request from '/@/utils/request'
// 定义变量内容
const searchFormRef = ref()
const applyFormRef = ref()
const showSearch = ref(true)
const classList = ref<any[]>([])
const termList = ref<any[]>([])
const genderList = ref<any[]>([])
const educationList = ref<any[]>([])
const majorLevelList = ref<any[]>([])
const selectedRows = ref<any[]>([])
const applyDialogVisible = ref(false)
const applyLoading = ref(false)
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 申请表单
const applyForm = reactive({
termId: ''
})
// 申请表单验证规则
const applyRules = {
termId: [
{ required: true, message: '请选择批次', trigger: 'change' }
]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
classCode: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 格式化性别
const formatGender = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = genderList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化生源
const formatEducation = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = educationList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化层次
const formatMajorLevel = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = majorLevelList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 表格选择变化
const handleSelectionChange = (selection: any[]) => {
selectedRows.value = selection
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.classCode = ''
getDataList()
}
// 申请免学费
const handleApply = () => {
if (selectedRows.value.length === 0) {
useMessage().warning('请至少选择一名学生')
return
}
applyDialogVisible.value = true
applyForm.termId = ''
}
// 提交申请
const handleSubmit = async () => {
if (!applyFormRef.value) return
await applyFormRef.value.validate(async (valid: boolean) => {
if (valid) {
applyLoading.value = true
try {
const { confirm } = useMessageBox()
await confirm(`确定要为选中的 ${selectedRows.value.length} 名学生申请免学费吗?`)
const studentIds = selectedRows.value.map((row: any) => row.id || row.stuNo)
await applyObj({
termId: applyForm.termId,
studentIds: studentIds
})
useMessage().success('申请成功')
applyDialogVisible.value = false
selectedRows.value = []
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '申请失败')
}
} finally {
applyLoading.value = false
}
}
})
}
// 获取批次列表
const getTermList = async () => {
try {
const res = await request({
url: '/stuwork/tuitionfreeterm/list',
method: 'get'
})
if (res.data && Array.isArray(res.data)) {
termList.value = res.data
} else {
termList.value = []
}
} catch (err) {
console.error('获取批次列表失败', err)
termList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取性别字典
const getGenderDict = async () => {
try {
const res = await getDicts('sexy')
if (res.data && Array.isArray(res.data)) {
genderList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
genderList.value = []
}
} catch (err) {
console.error('获取性别字典失败', err)
genderList.value = []
}
}
// 获取生源字典
const getEducationDict = async () => {
try {
const res = await getDicts('pre_school_education')
if (res.data && Array.isArray(res.data)) {
educationList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
educationList.value = []
}
} catch (err) {
console.error('获取生源字典失败', err)
educationList.value = []
}
}
// 获取层次字典
const getMajorLevelDict = async () => {
try {
const res = await getDicts('basic_major_level')
if (res.data && Array.isArray(res.data)) {
majorLevelList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
majorLevelList.value = []
}
} catch (err) {
console.error('获取层次字典失败', err)
majorLevelList.value = []
}
}
// 初始化
onMounted(() => {
getTermList()
getClassListData()
getGenderDict()
getEducationDict()
getMajorLevelDict()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,306 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="批次" prop="termId">
<el-select
v-model="state.queryForm.termId"
placeholder="请选择批次"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in termList"
:key="item.id"
:label="item.title"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="state.queryForm.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班级" prop="classCode">
<el-select
v-model="state.queryForm.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- Tab切换 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
<el-tab-pane label="免学费审批" name="pending"></el-tab-pane>
<el-tab-pane label="已审批" name="approved"></el-tab-pane>
</el-tabs>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 免学费审批表格 -->
<el-table
v-if="activeTab === 'pending'"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="termTitle" label="免学费批次" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="majorName" label="专业名称" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="classNo" label="班号" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="teacherName" label="班主任" show-overflow-tooltip align="center" width="100" />
<el-table-column prop="num" label="人数" show-overflow-tooltip align="center" width="80">
<template #default="scope">
<span>{{ scope.row.num !== undefined && scope.row.num !== null ? scope.row.num : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="120" align="center" fixed="right">
<template #default="scope">
<el-button
icon="View"
text
type="primary"
@click="handleViewDetail(scope.row)">
查看详情
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 已审批表格带合计 -->
<el-table
v-if="activeTab === 'approved'"
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
show-summary
:summary-method="getSummaries">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="termTitle" label="免学费批次" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="classNum" label="申报班级数" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ scope.row.classNum !== undefined && scope.row.classNum !== null ? scope.row.classNum : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="stuNum" label="申报人数" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ scope.row.stuNum !== undefined && scope.row.stuNum !== null ? scope.row.stuNum : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="120" align="center" fixed="right">
<template #default="scope">
<el-button
icon="View"
text
type="primary"
@click="handleViewDetail(scope.row)">
查看详情
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 查看详情弹窗 -->
<detail-dialog ref="detailDialogRef" />
</div>
</template>
<script setup lang="ts" name="TuitionFreeStuApprove">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { getApproveData, getApprovedData } from "/@/api/stuwork/tuitionfreestuapprove";
import { getDeptList } from "/@/api/basic/basicclass";
import { getClassListByRole } from "/@/api/basic/basicclass";
import request from '/@/utils/request'
import DetailDialog from './detail.vue'
// 定义变量内容
const searchFormRef = ref()
const detailDialogRef = ref()
const showSearch = ref(true)
const activeTab = ref('pending')
const termList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
termId: '',
deptCode: '',
classCode: ''
},
pageList: getApproveData,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// Tab切换
const handleTabChange = (tabName: string) => {
if (tabName === 'pending') {
state.pageList = getApproveData
} else {
state.pageList = getApprovedData
}
getDataList()
}
// 合计方法(仅用于已审批表格)
const getSummaries = (param: any) => {
const { columns, data } = param
const sums: any[] = []
columns.forEach((column: any, index: number) => {
if (index === 0) {
sums[index] = '合计'
return
}
if (column.property === 'classNum' || column.property === 'stuNum') {
const values = data.map((item: any) => Number(item[column.property] || 0))
const sum = values.reduce((prev: number, curr: number) => {
return prev + curr
}, 0)
sums[index] = sum
} else {
sums[index] = ''
}
})
return sums
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.termId = ''
state.queryForm.deptCode = ''
state.queryForm.classCode = ''
getDataList()
}
// 查看详情
const handleViewDetail = (row: any) => {
detailDialogRef.value?.openDialog(row)
}
// 获取批次列表
const getTermList = async () => {
try {
const res = await request({
url: '/stuwork/tuitionfreeterm/list',
method: 'get'
})
if (res.data && Array.isArray(res.data)) {
termList.value = res.data
} else {
termList.value = []
}
} catch (err) {
console.error('获取批次列表失败', err)
termList.value = []
}
}
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptList()
if (res.data && Array.isArray(res.data)) {
deptList.value = res.data
} else {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 初始化
onMounted(() => {
getTermList()
getDeptListData()
getClassListData()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,70 @@
<template>
<el-dialog
title="查看详情"
v-model="visible"
:close-on-click-modal="false"
draggable
width="800px">
<div v-loading="loading" class="detail-container" v-if="detailData">
<el-descriptions :column="2" border>
<el-descriptions-item label="免学费批次" :span="2">
{{ detailData.termTitle || '-' }}
</el-descriptions-item>
<el-descriptions-item label="学院">
{{ detailData.deptName || '-' }}
</el-descriptions-item>
<el-descriptions-item label="专业名称">
{{ detailData.majorName || '-' }}
</el-descriptions-item>
<el-descriptions-item label="班号">
{{ detailData.classNo || '-' }}
</el-descriptions-item>
<el-descriptions-item label="班主任">
{{ detailData.teacherName || '-' }}
</el-descriptions-item>
<el-descriptions-item label="人数" v-if="detailData.num !== undefined">
{{ detailData.num !== null ? detailData.num : '-' }}
</el-descriptions-item>
<el-descriptions-item label="申报班级数" v-if="detailData.classNum !== undefined">
{{ detailData.classNum !== null ? detailData.classNum : '-' }}
</el-descriptions-item>
<el-descriptions-item label="申报人数" v-if="detailData.stuNum !== undefined">
{{ detailData.stuNum !== null ? detailData.stuNum : '-' }}
</el-descriptions-item>
</el-descriptions>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="TuitionFreeStuApproveDetailDialog">
import { ref } from 'vue'
// 定义变量内容
const visible = ref(false)
const loading = ref(false)
const detailData = ref<any>({})
// 打开弹窗
const openDialog = async (row: any) => {
visible.value = true
loading.value = false
detailData.value = row || {}
}
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.detail-container {
padding: 20px 0;
}
</style>

View File

@@ -0,0 +1,492 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="批次" prop="termId">
<el-select
v-model="state.queryForm.termId"
placeholder="请选择批次"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in termList"
:key="item.id"
:label="item.title"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="state.queryForm.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="入学年份" prop="grade">
<el-select
v-model="state.queryForm.grade"
placeholder="请选择入学年份"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in gradeList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="年纪" prop="gradeCurr">
<el-input-number
v-model="state.queryForm.gradeCurr"
placeholder="请输入年纪"
:min="1"
:max="10"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="班级" prop="classCode">
<el-select
v-model="state.queryForm.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="审核状态" prop="checkStatus">
<el-select
v-model="state.queryForm.checkStatus"
placeholder="请选择审核状态"
clearable
style="width: 200px">
<el-option
v-for="item in checkStatusList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="handleAdd">
新增
</el-button>
<el-button
icon="Download"
type="primary"
plain
class="ml10"
:loading="exportLoading"
@click="handleExport">
导出
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="deptName" label="学院" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="majorName" label="专业" show-overflow-tooltip align="center" min-width="150" />
<el-table-column prop="teacherName" label="班主任" show-overflow-tooltip align="center" width="100" />
<el-table-column prop="grade" label="入学年份" show-overflow-tooltip align="center" width="100" />
<el-table-column prop="gradeCurr" label="年级" show-overflow-tooltip align="center" width="80">
<template #default="scope">
<span>{{ scope.row.gradeCurr !== undefined && scope.row.gradeCurr !== null ? scope.row.gradeCurr : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="classNo" label="班级" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip align="center" width="100" />
<el-table-column prop="gender" label="性别" show-overflow-tooltip align="center" width="80">
<template #default="scope">
<span>{{ formatGender(scope.row.gender) }}</span>
</template>
</el-table-column>
<el-table-column prop="education" label="生源" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<span>{{ formatEducation(scope.row.education) }}</span>
</template>
</el-table-column>
<el-table-column prop="majorLevel" label="层次" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<span>{{ formatMajorLevel(scope.row.majorLevel) }}</span>
</template>
</el-table-column>
<el-table-column prop="phone" label="联系电话" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="money" label="金额" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<span>{{ scope.row.money !== undefined && scope.row.money !== null ? scope.row.money : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="checkStatus" label="审核状态" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<span>{{ formatCheckStatus(scope.row.checkStatus) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<!-- <form-dialog ref="formDialogRef" @refresh="getDataList" /> -->
</div>
</template>
<script setup lang="ts" name="TuitionFreeStu">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, exportExcel, delObj, getDetail } from "/@/api/stuwork/tuitionfreestu";
import { getDeptList } from "/@/api/basic/basicclass";
import { getClassListByRole } from "/@/api/basic/basicclass";
import { getDicts } from "/@/api/admin/dict";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { useMessage, useMessageBox } from "/@/hooks/message";
import request from '/@/utils/request'
import axios from 'axios'
// 定义变量内容
const searchFormRef = ref()
const formDialogRef = ref()
const showSearch = ref(true)
const exportLoading = ref(false)
const termList = ref<any[]>([])
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const gradeList = ref<any[]>([])
const genderList = ref<any[]>([])
const educationList = ref<any[]>([])
const majorLevelList = ref<any[]>([])
const checkStatusList = ref<any[]>([
{ label: '待审核', value: '0' },
{ label: '审核通过', value: '1' }
])
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
termId: '',
deptCode: '',
grade: '',
gradeCurr: undefined,
classCode: '',
checkStatus: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 格式化性别
const formatGender = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = genderList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化生源
const formatEducation = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = educationList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化层次
const formatMajorLevel = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = majorLevelList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化审核状态
const formatCheckStatus = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = checkStatusList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.termId = ''
state.queryForm.deptCode = ''
state.queryForm.grade = ''
state.queryForm.gradeCurr = undefined
state.queryForm.classCode = ''
state.queryForm.checkStatus = ''
getDataList()
}
// 新增
const handleAdd = () => {
useMessage().info('新增功能待开发')
// formDialogRef.value?.openDialog('add')
}
// 编辑
const handleEdit = (row: any) => {
useMessage().info('编辑功能待开发')
// formDialogRef.value?.openDialog('edit', row.id)
}
// 删除
const handleDelete = async (row: any) => {
try {
const { confirm } = useMessageBox()
await confirm(`确定要删除学号为 ${row.stuNo} 的免学费记录吗?`)
await delObj(row.id)
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 导出
const handleExport = async () => {
try {
exportLoading.value = true
const res = await exportExcel(state.queryForm)
const blob = new Blob([res.data as BlobPart])
const fileName = `免学费查询_${new Date().getTime()}.xls`
const elink = document.createElement('a')
elink.download = fileName
elink.style.display = 'none'
elink.href = URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href)
document.body.removeChild(elink)
useMessage().success('导出成功')
} catch (err: any) {
useMessage().error(err.msg || '导出失败')
} finally {
exportLoading.value = false
}
}
// 获取批次列表
const getTermList = async () => {
try {
const res = await request({
url: '/stuwork/tuitionfreeterm/list',
method: 'get'
})
if (res.data && Array.isArray(res.data)) {
termList.value = res.data
} else {
termList.value = []
}
} catch (err) {
console.error('获取批次列表失败', err)
termList.value = []
}
}
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptList()
if (res.data && Array.isArray(res.data)) {
deptList.value = res.data
} else {
deptList.value = []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data && Array.isArray(res.data)) {
classList.value = res.data
} else {
classList.value = []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
// 获取入学年份列表
const getGradeList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
gradeList.value = res.data
} else {
gradeList.value = []
}
} catch (err) {
console.error('获取入学年份列表失败', err)
gradeList.value = []
}
}
// 获取性别字典
const getGenderDict = async () => {
try {
const res = await getDicts('sexy')
if (res.data && Array.isArray(res.data)) {
genderList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
genderList.value = []
}
} catch (err) {
console.error('获取性别字典失败', err)
genderList.value = []
}
}
// 获取生源字典
const getEducationDict = async () => {
try {
const res = await getDicts('pre_school_education')
if (res.data && Array.isArray(res.data)) {
educationList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
educationList.value = []
}
} catch (err) {
console.error('获取生源字典失败', err)
educationList.value = []
}
}
// 获取层次字典
const getMajorLevelDict = async () => {
try {
const res = await getDicts('basic_major_level')
if (res.data && Array.isArray(res.data)) {
majorLevelList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
majorLevelList.value = []
}
} catch (err) {
console.error('获取层次字典失败', err)
majorLevelList.value = []
}
}
// 初始化
onMounted(() => {
getTermList()
getDeptListData()
getClassListData()
getGradeList()
getGenderDict()
getEducationDict()
getMajorLevelDict()
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,328 @@
<template>
<el-dialog
:title="form.id ? '编辑批次' : '新增批次'"
v-model="visible"
:width="800"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="批次名称" prop="title">
<el-input
v-model="form.title"
placeholder="请输入批次名称"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="form.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 100%"
:disabled="!!form.id">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="form.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 100%"
:disabled="!!form.id">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="上报开始日期" prop="startTime">
<el-date-picker
v-model="form.startTime"
type="date"
placeholder="请输入上报开始日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="上报截止日期" prop="endTime">
<el-date-picker
v-model="form.endTime"
type="date"
placeholder="请输入上报截止日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="类别" prop="type">
<el-select
v-model="form.type"
placeholder="请选择类别"
clearable
style="width: 100%">
<el-option
v-for="item in typeList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="excel表头" prop="excelTitle">
<el-input
v-model="form.excelTitle"
type="textarea"
:rows="4"
placeholder="请输入excel表头"
clearable
style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="TuitionFreeTermFormDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from '/@/hooks/message'
import { addObj, editObj, getDetail } from '/@/api/stuwork/tuitionfreeterm'
import { queryAllSchoolYear } from '/@/api/basic/basicyear'
import { getDicts } from '/@/api/admin/dict'
const emit = defineEmits(['refresh'])
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const operType = ref('add')
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const typeList = ref<any[]>([])
// 提交表单数据
const form = reactive({
id: '',
title: '',
schoolYear: '',
schoolTerm: '',
startTime: '',
endTime: '',
type: '',
excelTitle: ''
})
// 定义校验规则
const dataRules = {
title: [
{ required: true, message: '请输入批次名称', trigger: 'blur' }
],
schoolYear: [
{ required: true, message: '请选择学年', trigger: 'change' }
],
schoolTerm: [
{ required: true, message: '请选择学期', trigger: 'change' }
],
startTime: [
{ required: true, message: '请输入上报开始日期', trigger: 'change' }
],
endTime: [
{ required: true, message: '请输入上报截止日期', trigger: 'change' }
],
type: [
{ required: true, message: '请选择类别', trigger: 'change' }
],
excelTitle: [
{ required: true, message: '请输入excel表头', trigger: 'blur' }
]
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取类别字典
const getTypeDict = async () => {
try {
const res = await getDicts('tuition_free_term_type')
if (res.data && Array.isArray(res.data)) {
typeList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
typeList.value = []
}
} catch (err) {
console.error('获取类别字典失败', err)
typeList.value = []
}
}
// 打开弹窗
const openDialog = async (type: string = 'add', row?: any) => {
operType.value = type
visible.value = true
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.id = ''
form.title = ''
form.schoolYear = ''
form.schoolTerm = ''
form.startTime = ''
form.endTime = ''
form.type = ''
form.excelTitle = ''
// 编辑时填充数据
if (type === 'edit' && row) {
form.id = row.id
form.title = row.title || ''
form.schoolYear = row.schoolYear || ''
form.schoolTerm = row.schoolTerm || ''
form.startTime = row.startTime || ''
form.endTime = row.endTime || ''
form.type = row.type || ''
form.excelTitle = row.excelTitle || ''
// 如果需要获取详情
if (row.id && (!row.title || !row.schoolYear)) {
loading.value = true
getDetail(row.id).then((res: any) => {
if (res.data) {
form.title = res.data.title || ''
form.schoolYear = res.data.schoolYear || ''
form.schoolTerm = res.data.schoolTerm || ''
form.startTime = res.data.startTime || ''
form.endTime = res.data.endTime || ''
form.type = res.data.type || ''
form.excelTitle = res.data.excelTitle || ''
}
}).catch((err) => {
console.error('获取详情失败', err)
}).finally(() => {
loading.value = false
})
}
}
})
}
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (valid) {
loading.value = true
try {
const submitData = {
title: form.title,
schoolYear: form.schoolYear,
schoolTerm: form.schoolTerm,
startTime: form.startTime,
endTime: form.endTime,
type: form.type,
excelTitle: form.excelTitle
}
if (operType.value === 'edit' && form.id) {
await editObj({ ...submitData, id: form.id })
useMessage().success('编辑成功')
} else {
await addObj(submitData)
useMessage().success('新增成功')
}
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'edit' ? '编辑失败' : '新增失败'))
} finally {
loading.value = false
}
}
})
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getTypeDict()
})
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,277 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="学年" prop="schoolYear">
<el-select
v-model="state.queryForm.schoolYear"
placeholder="请选择学年"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in schoolYearList"
:key="item.year"
:label="item.year"
:value="item.year">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="学期" prop="schoolTerm">
<el-select
v-model="state.queryForm.schoolTerm"
placeholder="请选择学期"
clearable
style="width: 200px">
<el-option
v-for="item in schoolTermList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button
icon="Plus"
type="primary"
class="ml10"
@click="formDialogRef.openDialog()">
新增
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10 mr20"
style="float: right;"
@queryTable="getDataList">
</right-toolbar>
</div>
</el-row>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="批次名称" show-overflow-tooltip align="center" min-width="200" />
<el-table-column prop="schoolYear" label="学年" show-overflow-tooltip align="center" width="120" />
<el-table-column prop="schoolTerm" label="学期" show-overflow-tooltip align="center" width="100">
<template #default="scope">
<span>{{ formatSchoolTerm(scope.row.schoolTerm) }}</span>
</template>
</el-table-column>
<el-table-column prop="startTime" label="上报开始日期" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column prop="endTime" label="上报截止日期" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column prop="type" label="类别" show-overflow-tooltip align="center" width="120">
<template #default="scope">
<span>{{ formatType(scope.row.type) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
text
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
text
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
<!-- 新增/编辑表单弹窗 -->
<form-dialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="TuitionFreeTerm">
import { reactive, ref, onMounted } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj } from "/@/api/stuwork/tuitionfreeterm";
import { queryAllSchoolYear } from "/@/api/basic/basicyear";
import { getDicts } from "/@/api/admin/dict";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { parseTime } from "/@/utils/formatTime";
import FormDialog from './form.vue'
// 定义变量内容
const formDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const schoolYearList = ref<any[]>([])
const schoolTermList = ref<any[]>([])
const typeList = ref<any[]>([])
// 表格样式
const tableStyle = {
cellStyle: { padding: '8px 0' },
headerCellStyle: { background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
schoolYear: '',
schoolTerm: ''
},
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle: _tableStyle
} = useTable(state)
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 格式化类别
const formatType = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = typeList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
state.queryForm.schoolYear = ''
state.queryForm.schoolTerm = ''
getDataList()
}
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog('edit', row)
}
// 删除
const handleDelete = async (row: any) => {
const { confirm } = useMessageBox()
try {
await confirm('确定要删除该批次吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 获取学年列表
const getSchoolYearList = async () => {
try {
const res = await queryAllSchoolYear()
if (res.data && Array.isArray(res.data)) {
schoolYearList.value = res.data
} else {
schoolYearList.value = []
}
} catch (err) {
console.error('获取学年列表失败', err)
schoolYearList.value = []
}
}
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
// 获取类别字典
const getTypeDict = async () => {
try {
const res = await getDicts('tuition_free_term_type')
if (res.data && Array.isArray(res.data)) {
typeList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
typeList.value = []
}
} catch (err) {
console.error('获取类别字典失败', err)
typeList.value = []
}
}
// 初始化
onMounted(() => {
getSchoolYearList()
getSchoolTermDict()
getTypeDict()
})
</script>
<style scoped lang="scss">
</style>