提交代码

This commit is contained in:
zlwu committed 2025-08-20 00:17:33 +08:00
1 parent 8f8257ec2e
commit 603858b53b
35 files changed
+3576 -26

No files matched your search

@@ -46,6 +46,11 @@ export interface DisciplineRevokeVO {
*/
approvalBy: string | number;
/**
* 审核人姓名
*/
approvalByName: string;
/**
* 审核时间
*/
@@ -0,0 +1,92 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { XskhRewardRecordVo, XskhRewardRecordBo, XskhRewardRecordQuery, XskhRewardRecordForm } from './types';
/**
* 查询学生奖励记录列表
* @param query
* @returns
*/
export function listRewardRecord(query: XskhRewardRecordQuery): AxiosPromise<TableDataInfo<XskhRewardRecordVo>> {
return request({
url: '/rewardRecord/rewardRecord/list',
method: 'get',
params: query
});
}
/**
* 查询学生奖励记录详细
* @param id
* @returns
*/
export function getRewardRecord(id: string | number): AxiosPromise<R<XskhRewardRecordVo>> {
return request({
url: '/rewardRecord/rewardRecord/' + id,
method: 'get'
});
}
/**
* 新增学生奖励记录
* @param data
* @returns
*/
export function addRewardRecord(data: XskhRewardRecordForm): AxiosPromise<R<any>> {
return request({
url: '/rewardRecord/rewardRecord',
method: 'post',
data: data
});
}
/**
* 修改学生奖励记录
* @param data
* @returns
*/
export function updateRewardRecord(data: XskhRewardRecordForm): AxiosPromise<R<any>> {
return request({
url: '/rewardRecord/rewardRecord',
method: 'put',
data: data
});
}
/**
* 删除学生奖励记录
* @param ids
* @returns
*/
export function delRewardRecord(ids: string | string[] | number | number[]): AxiosPromise<R<any>> {
return request({
url: '/rewardRecord/rewardRecord/' + ids,
method: 'delete'
});
}
/**
* 导出学生奖励记录
* @param query
* @returns
*/
export function exportRewardRecord(query: XskhRewardRecordQuery): AxiosPromise<any> {
return request({
url: '/rewardRecord/rewardRecord/export',
method: 'post',
data: query
});
}
/**
* 获取学生选项列表(用于下拉框)
* @param keyword
* @returns
*/
export function getStudentOptions(keyword?: string): AxiosPromise<R<XskhStudentOptionVO[]>> {
return request({
url: '/rewardRecord/rewardRecord/studentOptions',
method: 'get',
params: { keyword }
});
}
+211
View File
@@ -0,0 +1,211 @@
import { BaseEntity } from '@/types/axios';
/**
* 学生奖励记录对象
*/
export interface XskhRewardRecordVo extends BaseEntity {
/** 主键ID */
id?: string | number;
/** 学生ID */
studentId?: string | number;
/** 学号 */
studentNo?: string;
/** 学生姓名 */
studentName?: string;
/** 学院ID */
collegeId?: string | number;
/** 学院名称 */
collegeName?: string;
/** 专业ID */
majorId?: string | number;
/** 专业名称 */
majorName?: string;
/** 班级ID */
classId?: string | number;
/** 班级名称 */
className?: string;
/** 奖励类型ID */
rewardTypeId?: string | number;
/** 奖励类型名称 */
rewardTypeName?: string;
/** 奖励日期 */
rewardDate?: string;
/** 奖励内容 */
rewardContent?: string;
/** 加分分值 */
scoreAddition?: number;
/** 附件OSS文件ID,多个用逗号分隔 */
attachmentOssIds?: string;
/** 状态(1-生效 2-撤销) */
status?: string;
/** 创建者 */
createBy?: string | number;
/** 创建时间 */
createTime?: string;
/** 更新者 */
updateBy?: string | number;
/** 更新时间 */
updateTime?: string;
}
/**
* 学生奖励记录业务对象
*/
export interface XskhRewardRecordBo extends BaseEntity {
/** 主键ID */
id?: string | number;
/** 学生ID */
studentId?: string | number;
/** 学号 */
studentNo?: string;
/** 学生姓名 */
studentName?: string;
/** 学院ID */
collegeId?: string | number;
/** 学院名称 */
collegeName?: string;
/** 专业ID */
majorId?: string | number;
/** 专业名称 */
majorName?: string;
/** 班级ID */
classId?: string | number;
/** 班级名称 */
className?: string;
/** 奖励类型ID */
rewardTypeId?: string | number;
/** 奖励类型名称 */
rewardTypeName?: string;
/** 奖励日期 */
rewardDate?: string;
/** 奖励内容 */
rewardContent?: string;
/** 加分分值 */
scoreAddition?: number;
/** 附件OSS文件ID,多个用逗号分隔 */
attachmentOssIds?: string;
/** 状态(1-生效 2-撤销) */
status?: string;
/** 创建者 */
createBy?: string | number;
/** 创建时间 */
createTime?: string;
/** 更新者 */
updateBy?: string | number;
/** 更新时间 */
updateTime?: string;
}
/**
* 学生奖励记录查询对象
*/
export interface XskhRewardRecordQuery extends BaseEntity {
/** 当前页码 */
pageNum?: number;
/** 每页数量 */
pageSize?: number;
/** 学生ID */
studentId?: string | number;
/** 学号 */
studentNo?: string;
/** 学生姓名 */
studentName?: string;
/** 学院ID */
collegeId?: string | number;
/** 学院名称 */
collegeName?: string;
/** 专业ID */
majorId?: string | number;
/** 专业名称 */
majorName?: string;
/** 班级ID */
classId?: string | number;
/** 班级名称 */
className?: string;
/** 奖励类型ID */
rewardTypeId?: string | number;
/** 奖励类型名称 */
rewardTypeName?: string;
/** 奖励日期 */
rewardDate?: string;
/** 奖励内容 */
rewardContent?: string;
/** 加分分值 */
scoreAddition?: number;
/** 状态(1-生效 2-撤销) */
status?: string;
/** 创建者 */
createBy?: string | number;
/** 创建时间 */
createTime?: string;
/** 更新者 */
updateBy?: string | number;
/** 更新时间 */
updateTime?: string;
/** 请求参数 */
params?: any;
}
/**
* 学生奖励记录表单对象
*/
export interface XskhRewardRecordForm extends BaseEntity {
/** 主键ID */
id?: string | number;
/** 学生ID */
studentId?: string | number;
/** 学号 */
studentNo?: string;
/** 学生姓名 */
studentName?: string;
/** 学院ID */
collegeId?: string | number;
/** 学院名称 */
collegeName?: string;
/** 专业ID */
majorId?: string | number;
/** 专业名称 */
majorName?: string;
/** 班级ID */
classId?: string | number;
/** 班级名称 */
className?: string;
/** 奖励类型ID */
rewardTypeId?: string | number;
/** 奖励类型名称 */
rewardTypeName?: string;
/** 奖励日期 */
rewardDate?: string;
/** 奖励内容 */
rewardContent?: string;
/** 加分分值 */
scoreAddition?: number;
/** 附件OSS文件ID,多个用逗号分隔 */
attachmentOssIds?: string;
/** 状态(1-生效 2-撤销) */
status?: string;
}
/**
* 学生选项VO
*/
export interface XskhStudentOptionVO {
/** 学生ID */
id: string | number;
/** 学号 */
studentNo: string;
/** 学生姓名 */
name: string;
/** 学院ID */
collegeId: string | number;
/** 学院名称 */
collegeName: string;
/** 专业ID */
majorId: string | number;
/** 专业名称 */
majorName: string;
/** 班级ID */
classId: string | number;
/** 班级名称 */
className: string;
}
+79
View File
@@ -0,0 +1,79 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { XskhRewardTypeVo, XskhRewardTypeBo, XskhRewardTypeQuery } from './types';
/**
* 查询奖励类型列表
* @param query
* @returns
*/
export function listRewardType(query: XskhRewardTypeQuery): AxiosPromise<TableDataInfo<XskhRewardTypeVo>> {
return request({
url: '/rewardType/rewardType/list',
method: 'get',
params: query
});
}
/**
* 查询奖励类型详细
* @param id
* @returns
*/
export function getRewardType(id: string | number): AxiosPromise<R<XskhRewardTypeVo>> {
return request({
url: '/rewardType/rewardType/' + id,
method: 'get'
});
}
/**
* 新增奖励类型
* @param data
* @returns
*/
export function addRewardType(data: XskhRewardTypeBo): AxiosPromise<R<any>> {
return request({
url: '/rewardType/rewardType',
method: 'post',
data: data
});
}
/**
* 修改奖励类型
* @param data
* @returns
*/
export function updateRewardType(data: XskhRewardTypeBo): AxiosPromise<R<any>> {
return request({
url: '/rewardType/rewardType',
method: 'put',
data: data
});
}
/**
* 删除奖励类型
* @param ids
* @returns
*/
export function delRewardType(ids: string | string[] | number | number[]): AxiosPromise<R<any>> {
return request({
url: '/rewardType/rewardType/' + ids,
method: 'delete'
});
}
/**
* 导出奖励类型
* @param query
* @returns
*/
export function exportRewardType(query: XskhRewardTypeQuery): AxiosPromise<any> {
return request({
url: '/rewardType/rewardType/export',
method: 'post',
data: query
});
}
+79
View File
@@ -0,0 +1,79 @@
import { BaseEntity } from '@/types/axios';
/**
* 奖励类型对象
*/
export interface XskhRewardTypeVo extends BaseEntity {
/** 主键ID */
id?: string | number;
/** 类型编码 */
typeCode?: string;
/** 类型名称 */
typeName?: string;
/** 类型说明 */
description?: string;
/** 状态(0-正常 1-停用) */
status?: string;
/** 排序号 */
sortOrder?: number;
/** 创建者 */
createBy?: string | number;
/** 创建时间 */
createTime?: string;
/** 更新者 */
updateBy?: string | number;
/** 更新时间 */
updateTime?: string;
}
/**
* 奖励类型业务对象
*/
export interface XskhRewardTypeBo extends BaseEntity {
/** 主键ID */
id?: string | number;
/** 类型编码 */
typeCode?: string;
/** 类型名称 */
typeName?: string;
/** 类型说明 */
description?: string;
/** 状态(0-正常 1-停用) */
status?: string;
/** 排序号 */
sortOrder?: number;
/** 创建者 */
createBy?: string | number;
/** 创建时间 */
createTime?: string;
/** 更新者 */
updateBy?: string | number;
/** 更新时间 */
updateTime?: string;
}
/**
* 奖励类型查询对象
*/
export interface XskhRewardTypeQuery extends BaseEntity {
/** 当前页码 */
pageNum?: number;
/** 每页数量 */
pageSize?: number;
/** 类型编码 */
typeCode?: string;
/** 类型名称 */
typeName?: string;
/** 状态(0-正常 1-停用) */
status?: string;
/** 创建者 */
createBy?: string | number;
/** 创建时间 */
createTime?: string;
/** 更新者 */
updateBy?: string | number;
/** 更新时间 */
updateTime?: string;
/** 请求参数 */
params?: any;
}
@@ -0,0 +1,195 @@
<template>
<div class="class-discipline-record">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams">
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="学号" prop="studentNo">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="学生姓名" prop="studentName">
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24" class="search-btn-container">
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<span class="text-lg font-medium">班级违纪记录列表</span>
</el-col>
<right-toolbar :showSearch="showSearch" @update:showSearch="showSearch = $event" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="disciplineRecordList" class="full-width-table">
<el-table-column label="学号" align="center" prop="studentNo" min-width="120" />
<el-table-column label="学生姓名" align="center" prop="studentName" min-width="100" />
<el-table-column label="学院名称" align="center" prop="collegeName" min-width="140" show-overflow-tooltip />
<el-table-column label="专业名称" align="center" prop="majorName" min-width="140" show-overflow-tooltip />
<el-table-column label="班级名称" align="center" prop="className" min-width="120" show-overflow-tooltip />
<el-table-column label="违纪处分类型" align="center" prop="disciplineTypeName" min-width="140" show-overflow-tooltip />
<el-table-column label="违纪处分日期" align="center" prop="disciplineDate" min-width="130" />
<el-table-column label="扣分分值" align="center" prop="scoreDeduction" min-width="100">
<template #default="scope">
<span>{{ scope.row.scoreDeduction ? parseFloat(scope.row.scoreDeduction).toFixed(2) : '0.00' }}</span>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" min-width="80">
<template #default="scope">
<dict-tag :options="student_discipline_status" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" min-width="160" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="100">
<template #default="scope">
<el-tooltip content="查看" placement="top">
<el-button
link
type="primary"
icon="View"
@click="handleView(scope.row)"
v-hasPermi="['disciplineRecord:classTeacher:query']"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page="queryParams.pageNum" :limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 查看违纪处分记录对话框 -->
<DisciplineRecordForm ref="disciplineRecordFormRef" @success="handleSuccess" />
</div>
</template>
<script setup name="ClassDisciplineRecord" lang="ts">
import { ref, reactive, toRefs, onMounted, getCurrentInstance, ComponentInternalInstance } from 'vue';
import { listDisciplineRecord } from '@/api/xskh/disciplineRecord/disciplineRecord';
import type { DisciplineRecordVO, DisciplineRecordQuery } from '@/api/xskh/disciplineRecord/disciplineRecord/types';
import DisciplineRecordForm from '../xsDisciplineRecord/DisciplineRecordForm.vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { student_discipline_status } = toRefs<any>(proxy?.useDict('student_discipline_status'));
const disciplineRecordList = ref<DisciplineRecordVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const disciplineRecordFormRef = ref<ComponentRef<typeof DisciplineRecordForm>>();
interface QueryParams {
pageNum: number;
pageSize: number;
studentNo?: string;
studentName?: string;
params: Record<string, any>;
}
const data = reactive<{
form: Record<string, any>;
queryParams: QueryParams;
}>({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
studentNo: undefined,
studentName: undefined,
params: {}
}
});
const { queryParams } = toRefs(data);
/** 查询班级违纪处分记录列表 */
const getList = async () => {
loading.value = true;
try {
// 后续需要调用班主任专用的API
const res = await listDisciplineRecord(queryParams.value);
disciplineRecordList.value = res.rows;
total.value = res.total;
} catch (error) {
console.error('获取班级违纪记录失败:', error);
disciplineRecordList.value = [];
total.value = 0;
} finally {
loading.value = false;
}
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 查看按钮操作 */
const handleView = (row: DisciplineRecordVO) => {
disciplineRecordFormRef.value?.openDialog(row.id, '查看');
};
/** 提交成功回调 */
const handleSuccess = () => {
getList();
};
onMounted(() => {
getList();
});
</script>
<style scoped>
/* 搜索按钮容器样式 */
.search-btn-container {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
.search-btn-container .el-form-item {
margin-bottom: 0 !important;
}
/* 表格全宽样式 */
.full-width-table {
width: 100%;
}
/* 确保表格容器充满宽度 */
:deep(.el-table) {
width: 100%;
}
/* 表格内容区域充满宽度 */
:deep(.el-table__body-wrapper) {
width: 100%;
}
</style>
@@ -0,0 +1,315 @@
<template>
<div class="revoke-application-approval">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams">
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="学号" prop="studentNo">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="学生姓名" prop="studentName">
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24" class="search-btn-container">
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<span class="text-lg font-medium">违纪撤销申请审核列表</span>
</el-col>
<right-toolbar :showSearch="showSearch" @update:showSearch="showSearch = $event" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="revokeApplicationList" class="full-width-table">
<el-table-column label="学号" align="center" prop="studentNo" min-width="120" />
<el-table-column label="学生姓名" align="center" prop="studentName" min-width="100" />
<el-table-column label="违纪类型" align="center" prop="disciplineTypeName" min-width="140" show-overflow-tooltip />
<el-table-column label="违纪日期" align="center" prop="disciplineDate" min-width="130" />
<el-table-column label="撤销原因" align="center" prop="revokeReason" min-width="200" show-overflow-tooltip />
<el-table-column label="申请时间" align="center" prop="applyTime" min-width="160" />
<el-table-column label="状态" align="center" prop="status" min-width="100">
<template #default="scope">
<dict-tag :options="student_revoke_status" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="200">
<template #default="scope">
<el-tooltip content="查看" placement="top">
<el-button
link
type="primary"
icon="View"
@click="handleView(scope.row)"
v-hasPermi="['disciplineRevoke:classTeacher:query']"
></el-button>
</el-tooltip>
<el-tooltip content="审核通过" placement="top" v-if="scope.row.status === '1'">
<el-button
link
type="success"
icon="CircleCheck"
@click="handleApprove(scope.row, '2')"
v-hasPermi="['disciplineRevoke:classTeacher:approve']"
></el-button>
</el-tooltip>
<el-tooltip content="审核驳回" placement="top" v-if="scope.row.status === '1'">
<el-button
link
type="danger"
icon="CircleClose"
@click="handleApprove(scope.row, '3')"
v-hasPermi="['disciplineRevoke:classTeacher:approve']"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page="queryParams.pageNum" :limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 查看撤销申请对话框 -->
<RevokeApplicationView ref="revokeApplicationViewRef" />
<!-- 审核对话框 -->
<el-dialog :title="approvalDialog.title" v-model="approvalDialog.visible" width="600px" append-to-body destroy-on-close>
<el-form ref="approvalFormRef" :model="approvalForm" :rules="approvalRules" label-width="120px">
<el-form-item label="学生姓名">
<el-input v-model="approvalForm.studentName" disabled />
</el-form-item>
<el-form-item label="审核结果">
<el-input v-model="approvalForm.statusText" disabled />
</el-form-item>
<el-form-item label="审核时间">
<el-input v-model="approvalForm.approvalTime" disabled />
</el-form-item>
<el-form-item label="撤销申请原因">
<el-input v-model="approvalForm.revokeReason" type="textarea" :rows="3" disabled />
</el-form-item>
<el-form-item label="审核备注" prop="approvalRemark">
<el-input v-model="approvalForm.approvalRemark" type="textarea" placeholder="请输入审核备注" :rows="4" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitApproval">确 定</el-button>
<el-button @click="cancelApproval">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="RevokeApplicationApproval" lang="ts">
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue';
import request from '@/utils/request';
import RevokeApplicationView from '../xsDisciplineRecord/RevokeApplicationView.vue';
const { proxy } = getCurrentInstance();
const { student_revoke_status } = toRefs<any>(proxy?.useDict('student_revoke_status'));
const revokeApplicationList = ref<any[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const buttonLoading = ref(false);
const queryFormRef = ref();
const revokeApplicationViewRef = ref();
const approvalFormRef = ref();
interface QueryParams {
pageNum: number;
pageSize: number;
studentNo?: string;
studentName?: string;
params: Record<string, any>;
}
const data = reactive<{
form: Record<string, any>;
queryParams: QueryParams;
}>({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
studentNo: undefined,
studentName: undefined,
params: {}
}
});
const { queryParams } = toRefs(data);
// 审核对话框
const approvalDialog = reactive({
visible: false,
title: ''
});
const approvalForm = ref({
id: undefined,
disciplineRecordId: undefined, // 添加违纪记录ID字段
studentName: '',
status: '',
statusText: '',
approvalTime: '',
revokeReason: '', // 添加撤销申请原因字段
approvalRemark: ''
});
const approvalRules = reactive({
approvalRemark: [{ required: true, message: '审核备注不能为空', trigger: 'blur' }]
});
/** 查询班级违纪撤销申请列表 */
const getList = async () => {
loading.value = true;
try {
// 后续需要调用班主任专用的API
const res = await request({
url: '/disciplineRevoke/disciplineRevoke/classTeacherList',
method: 'get',
params: queryParams.value
});
revokeApplicationList.value = res.rows || [];
total.value = res.total || 0;
} catch (error) {
console.error('获取违纪撤销申请列表失败:', error);
revokeApplicationList.value = [];
total.value = 0;
} finally {
loading.value = false;
}
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 查看按钮操作 */
const handleView = (row: any) => {
revokeApplicationViewRef.value?.openDialog(row.disciplineRecordId, '查看');
};
/** 审核按钮操作 */
const handleApprove = (row: any, status: string) => {
approvalForm.value = {
id: row.id,
disciplineRecordId: row.disciplineRecordId, // 添加违纪记录ID
studentName: row.studentName,
status: status,
statusText: status === '2' ? '审核通过' : '审核驳回',
approvalTime: new Date().toLocaleString(),
revokeReason: row.revokeReason || '', // 添加撤销申请原因
approvalRemark: ''
};
approvalDialog.title = status === '2' ? '审核通过' : '审核驳回';
approvalDialog.visible = true;
};
/** 提交审核 */
const submitApproval = () => {
approvalFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
try {
await request({
url: '/disciplineRevoke/disciplineRevoke/classTeacherApprove',
method: 'put',
data: {
id: approvalForm.value.id,
disciplineRecordId: approvalForm.value.disciplineRecordId, // 添加违纪记录ID
status: approvalForm.value.status,
revokeReason: approvalForm.value.revokeReason, // 添加撤销申请原因
approvalRemark: approvalForm.value.approvalRemark
}
});
proxy?.$modal.msgSuccess('审核成功');
approvalDialog.visible = false;
getList();
} catch (error) {
console.error('审核失败:', error);
proxy?.$modal.msgError('审核失败');
} finally {
buttonLoading.value = false;
}
}
});
};
/** 取消审核 */
const cancelApproval = () => {
approvalDialog.visible = false;
approvalForm.value = {
id: undefined,
disciplineRecordId: undefined, // 添加违纪记录ID字段
studentName: '',
status: '',
statusText: '',
approvalTime: '',
revokeReason: '', // 添加撤销申请原因字段
approvalRemark: ''
};
};
onMounted(() => {
getList();
});
</script>
<style scoped>
/* 搜索按钮容器样式 */
.search-btn-container {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
.search-btn-container .el-form-item {
margin-bottom: 0 !important;
}
/* 表格全宽样式 */
.full-width-table {
width: 100%;
}
/* 确保表格容器充满宽度 */
:deep(.el-table) {
width: 100%;
}
/* 表格内容区域充满宽度 */
:deep(.el-table__body-wrapper) {
width: 100%;
}
</style>
@@ -0,0 +1,43 @@
<template>
<div class="p-2 page-container">
<el-card shadow="never">
<el-tabs v-model="activeTab" type="border-card">
<el-tab-pane label="班级违纪记录" name="disciplineRecord">
<ClassDisciplineRecord />
</el-tab-pane>
<el-tab-pane label="违纪撤销申请审核" name="revokeApplication">
<RevokeApplicationApproval />
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</template>
<script setup name="ClassTeacherDiscipline" lang="ts">
import { ref } from 'vue';
import ClassDisciplineRecord from './ClassDisciplineRecord.vue';
import RevokeApplicationApproval from './RevokeApplicationApproval.vue';
const activeTab = ref('disciplineRecord');
</script>
<style scoped>
/* 页面容器样式 */
.page-container {
padding: 8px;
}
/* Tab样式 */
:deep(.el-tabs--border-card) {
border: 1px solid #e4e7ed;
border-radius: 4px;
}
:deep(.el-tabs__content) {
padding: 20px;
}
:deep(.el-tab-pane) {
min-height: 600px;
}
</style>
@@ -467,7 +467,7 @@ const handleStudentChange = (studentId: string | number) => {
/** 加载违纪处分类型 */
const loadDisciplineTypes = async () => {
try {
const res = await listDisciplineType({ pageNum: 1, pageSize: 1000 });
const res = await listDisciplineType({ pageNum: 1, pageSize: 1000, status: '0' }); // 只查询生效的数据
disciplineTypeOptions.value = res.data?.rows || res.rows || [];
} catch (error) {
console.error('加载违纪处分类型失败:', error);
@@ -0,0 +1,980 @@
<template>
<el-dialog :title="dialog.title" v-model="dialog.visible" width="1000px" append-to-body destroy-on-close>
<el-form ref="rewardRecordFormRef" :model="form" :rules="rules" label-width="120px">
<el-row :gutter="20">
<!-- 左列:学生选择和奖励信息 -->
<el-col :span="12">
<el-form-item label="学生选择" prop="studentId" v-if="dialog.title !== '查看'">
<el-select
v-model="form.studentId"
placeholder="请输入学号或姓名搜索学生"
filterable
remote
:remote-method="remoteSearchStudents"
:loading="studentLoading"
clearable
@change="handleStudentChange"
style="width: 100%"
>
<el-option v-for="student in studentOptions" :key="student.id" :label="`${student.name} (${student.studentNo})`" :value="student.id">
<div class="student-option">
<div class="student-main">
<span class="student-name">{{ student.name }}</span>
<span class="student-no">{{ student.studentNo }}</span>
</div>
<div class="student-info" v-if="student.collegeName || student.majorName || student.className">
<span v-if="student.collegeName" class="info-item">{{ student.collegeName }}</span>
<span v-if="student.majorName" class="info-item">{{ student.majorName }}</span>
<span v-if="student.className" class="info-item">{{ student.className }}</span>
</div>
</div>
</el-option>
</el-select>
</el-form-item>
<!-- 查看模式下直接显示学生姓名 -->
<el-form-item label="学生选择" v-else>
<div class="student-display">
<span class="student-name-text">{{ form.studentName }}</span>
<span class="student-no-text" v-if="form.studentNo">({{ form.studentNo }})</span>
</div>
</el-form-item>
<el-form-item label="奖励类型" prop="rewardTypeId">
<el-select
v-model="form.rewardTypeId"
placeholder="请选择奖励类型"
filterable
clearable
@change="handleRewardTypeChange"
style="width: 100%"
:disabled="dialog.title === '查看'"
>
<el-option v-for="type in rewardTypeOptions" :key="type.id" :label="type.typeName" :value="type.id" />
</el-select>
</el-form-item>
<el-form-item label="奖励日期" prop="rewardDate">
<el-date-picker
v-model="form.rewardDate"
type="date"
placeholder="请选择奖励日期"
style="width: 100%"
value-format="YYYY-MM-DD"
:disabled="dialog.title === '查看'"
/>
</el-form-item>
<el-form-item label="加分分值" prop="scoreAddition">
<el-input-number
v-model="form.scoreAddition"
:min="0"
:max="999"
:precision="2"
:step="1"
placeholder="请输入加分分值"
style="width: 100%"
:disabled="dialog.title === '查看'"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status" :disabled="dialog.title === '查看'">
<el-radio v-for="dict in student_reward_status" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<!-- 右列:学生基本信息 -->
<el-col :span="12">
<el-form-item label="学号" prop="studentNo">
<el-input v-model="form.studentNo" placeholder="选择学生后自动填充" disabled />
</el-form-item>
<el-form-item label="学生姓名" prop="studentName">
<el-input v-model="form.studentName" placeholder="选择学生后自动填充" disabled />
</el-form-item>
<el-form-item label="学院名称" prop="collegeName">
<el-input v-model="form.collegeName" placeholder="选择学生后自动填充" disabled />
</el-form-item>
<el-form-item label="专业名称" prop="majorName">
<el-input v-model="form.majorName" placeholder="选择学生后自动填充" disabled />
</el-form-item>
<el-form-item label="班级名称" prop="className">
<el-input v-model="form.className" placeholder="选择学生后自动填充" disabled />
</el-form-item>
</el-col>
</el-row>
<!-- 完整宽度的字段 -->
<el-row>
<el-col :span="24">
<el-form-item label="奖励内容" prop="rewardContent">
<el-input v-model="form.rewardContent" type="textarea" placeholder="请输入奖励内容" :rows="3" :disabled="dialog.title === '查看'" />
</el-form-item>
</el-col>
</el-row>
<!-- 附件上传区域 -->
<el-row>
<el-col :span="24">
<el-form-item label="上传附件" v-if="dialog.title !== '查看'">
<div class="attachment-upload-container">
<el-upload
:file-list="attachmentList"
class="upload-demo"
drag
list-type="picture-card"
:on-preview="handleFilePreview"
:action="uploadAction"
:headers="uploadHeaders"
:data="uploadData"
:auto-upload="true"
:before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:on-remove="handleRemoveAttachmentConfirm"
:on-change="handleFileChange"
multiple
:limit="15"
:show-file-list="true"
accept="image/*,video/*,.pdf,.doc,.docx,.txt"
>
<template #tip>
<div class="el-upload__tip">
支持图片、视频、PDF、Word文档等格式,单个文件大小不超过10MB,最多上传15个文件。
<span class="el-upload__tip" v-if="!form.id" style="color: #ff0000">(新增时文件会立即上传,保存奖励记录后自动关联)</span>
<span class="el-upload__tip" v-else style="color: #ff0000">(编辑时文件会立即上传并关联到当前奖励记录)</span>
</div>
</template>
<!-- 调整拖拽上传图标与文字在同一行、居中对齐 -->
<div class="el-upload-dragger1">
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
</div>
<!-- 自定义文件卡片缩略图(含文件名) -->
<template #file="{ file }">
<div class="upload-file-card">
<!-- 缩略图区域 -->
<div class="upload-file-thumb">
<image-preview v-if="isImageFile(file) && file.url" :src="getFileOssId(file)" class="upload-thumb-image" :isOssId="true" />
<div v-else class="upload-file-placeholder">
<el-icon><Document /></el-icon>
<span class="file-ext">{{ getFileExt(file.name) }}</span>
</div>
<!-- loading 遮罩只在上传中显示 -->
<div v-if="file.status === 'uploading'" class="upload-file-loading">
<el-icon class="is-loading"><Loading /></el-icon>
<span class="loading-text">上传中...</span>
</div>
</div>
<!-- 文件名称 -->
<div class="upload-file-name" :title="file.name">
{{ getShortFileName(file.name) }}
</div>
<!-- 操作按钮区域 - 固定在文件名下方 -->
<div class="upload-file-card-actions" v-if="file.status && file.status !== 'ready'">
<el-button-group size="small">
<el-button type="success" @click.stop="handleDownloadAttachment(file)" title="下载" :disabled="file.status !== 'success'">
<el-icon><Download /></el-icon>
</el-button>
<el-button type="danger" @click.stop="handleRemoveAttachmentConfirm(file)" title="删除" :disabled="dialog.title === '查看'">
<el-icon><Delete /></el-icon>
</el-button>
</el-button-group>
</div>
</div>
</template>
</el-upload>
</div>
</el-form-item>
<!-- 查看模式下的附件展示 -->
<el-form-item label="附件" v-else-if="dialog.title === '查看' && attachmentList.length > 0">
<div class="attachment-upload-container">
<div class="upload-demo">
<div class="attachment-view-grid">
<div v-for="file in attachmentList" :key="file.uid" class="upload-file-card">
<!-- 缩略图区域 -->
<div class="upload-file-thumb">
<image-preview v-if="isImageFile(file) && file.url" :src="getFileOssId(file)" class="upload-thumb-image" :isOssId="true" />
<div v-else class="upload-file-placeholder">
<el-icon><Document /></el-icon>
<span class="file-ext">{{ getFileExt(file.name) }}</span>
</div>
</div>
<!-- 文件名称 -->
<div class="upload-file-name" :title="file.name">
{{ getShortFileName(file.name) }}
</div>
<!-- 操作按钮区域 - 查看模式只显示下载按钮 -->
<div class="upload-file-card-actions">
<el-button type="success" size="small" @click.stop="handleDownloadAttachment(file)" title="下载">
<el-icon><Download /></el-icon>
</el-button>
</div>
</div>
</div>
</div>
</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer v-if="dialog.title !== '查看'">
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup name="RewardRecordForm" lang="ts">
import { ref, reactive, computed, toRefs, getCurrentInstance } from 'vue';
import { UploadFilled, Document, Loading, Download, Delete } from '@element-plus/icons-vue';
import { ElMessage, ElMessageBox, ElLoading, FormInstance } from 'element-plus';
import { getToken } from '@/utils/auth';
import { globalHeaders } from '@/utils/request';
import ImagePreview from '@/components/ImagePreview/index.vue';
import request from '@/utils/request';
import { getRewardRecord, addRewardRecord, updateRewardRecord, getStudentOptions } from '@/api/xskh/rewardRecord/rewardRecord';
import { listRewardType } from '@/api/xskh/rewardType/rewardType';
import { XskhRewardRecordForm as RewardRecordFormType, XskhStudentOptionVO } from '@/api/xskh/rewardRecord/rewardRecord/types';
import { XskhRewardTypeVo } from '@/api/xskh/rewardType/rewardType/types';
interface Props {}
const emit = defineEmits(['success']);
// 使用getCurrentInstance获取上下文
const { proxy } = getCurrentInstance() as any;
const { student_reward_status } = toRefs<any>(proxy?.useDict('student_reward_status'));
const rewardRecordFormRef = ref<FormInstance>();
const buttonLoading = ref(false);
// 学生选择器相关状态
const studentOptions = ref<XskhStudentOptionVO[]>([]);
const studentLoading = ref(false);
// 奖励类型选项
const rewardTypeOptions = ref<XskhRewardTypeVo[]>([]);
// 附件上传相关状态
const attachmentList = ref<any[]>([]);
const dialog = reactive({
visible: false,
title: ''
});
const initFormData: RewardRecordFormType = {
id: undefined,
studentId: undefined,
studentNo: undefined,
studentName: undefined,
collegeId: undefined,
collegeName: undefined,
majorId: undefined,
majorName: undefined,
classId: undefined,
className: undefined,
rewardTypeId: undefined,
rewardTypeName: undefined,
rewardDate: new Date().toISOString().split('T')[0], // 默认当前日期
rewardContent: undefined,
scoreAddition: undefined,
status: '1', // 默认生效
attachmentOssIds: undefined
};
const form = ref<RewardRecordFormType>({ ...initFormData });
const rules = reactive({
studentId: [{ required: true, message: '学生不能为空', trigger: 'change' }],
rewardTypeId: [{ required: true, message: '奖励类型不能为空', trigger: 'change' }],
rewardDate: [{ required: true, message: '奖励日期不能为空', trigger: 'blur' }],
rewardContent: [{ required: true, message: '奖励内容不能为空', trigger: 'blur' }],
scoreAddition: [
{ required: true, message: '加分分值不能为空', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
if (value === undefined || value === null || value === '') {
callback(new Error('加分分值不能为空'));
} else {
const num = Number(value);
if (isNaN(num)) {
callback(new Error('加分分值必须是数字'));
} else if (num < 0 || num > 999) {
callback(new Error('加分分值必须在0-999之间'));
} else {
callback();
}
}
},
trigger: 'blur'
}
],
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
});
// 上传相关配置
const uploadAction = computed(() => {
return import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload';
});
const uploadHeaders = computed(() => {
return {
'Authorization': `Bearer ${getToken() || ''}`,
'clientid': import.meta.env.VITE_APP_CLIENT_ID || 'e5cd7e4891bf95d1d19206ce24a7b32e'
};
});
const uploadData = computed(() => {
return {
bizType: 'reward_record_attachment'
};
});
/** 打开弹窗 */
const openDialog = async (id?: string | number, title?: string) => {
dialog.visible = true;
dialog.title = title || '新增';
resetForm();
// 加载奖励类型选项
await loadRewardTypes();
if (id) {
// 编辑/查看模式
const res = await getRewardRecord(id);
Object.assign(form.value, res.data);
// 确保scoreAddition为数字类型
if (form.value.scoreAddition !== undefined && form.value.scoreAddition !== null) {
form.value.scoreAddition = Number(form.value.scoreAddition);
}
// 处理附件数据
await handleAttachmentData();
}
};
/** 重置表单 */
const resetForm = () => {
form.value = { ...initFormData };
attachmentList.value = [];
rewardRecordFormRef.value?.resetFields();
};
/** 取消按钮 */
const cancel = () => {
dialog.visible = false;
resetForm();
};
/** 提交按钮 */
const submitForm = () => {
rewardRecordFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
// 处理附件数据
const attachmentOssIds = attachmentList.value.filter((file: any) => file.status === 'success' && file.ossId).map((file: any) => file.ossId);
form.value.attachmentOssIds = attachmentOssIds.join(',');
if (form.value.id) {
await updateRewardRecord(form.value).finally(() => (buttonLoading.value = false));
} else {
await addRewardRecord(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
emit('success');
}
});
};
/** 远程搜索学生 */
const remoteSearchStudents = async (query: string) => {
studentLoading.value = true;
try {
const res = await getStudentOptions(query.trim());
studentOptions.value = res.data || [];
} catch (error) {
console.error('搜索学生失败:', error);
studentOptions.value = [];
} finally {
studentLoading.value = false;
}
};
/** 学生选择处理 */
const handleStudentChange = (studentId: string | number) => {
if (!studentId) {
// 清空学生相关信息
form.value.studentNo = undefined;
form.value.studentName = undefined;
form.value.collegeName = undefined;
form.value.majorName = undefined;
form.value.className = undefined;
form.value.collegeId = undefined;
form.value.majorId = undefined;
form.value.classId = undefined;
return;
}
const selectedStudent = studentOptions.value.find((student) => student.id === studentId);
if (selectedStudent) {
form.value.studentId = selectedStudent.id;
form.value.studentNo = selectedStudent.studentNo;
form.value.studentName = selectedStudent.name;
form.value.collegeId = selectedStudent.collegeId;
form.value.collegeName = selectedStudent.collegeName;
form.value.majorId = selectedStudent.majorId;
form.value.majorName = selectedStudent.majorName;
form.value.classId = selectedStudent.classId;
form.value.className = selectedStudent.className;
}
};
/** 加载奖励类型 */
const loadRewardTypes = async () => {
try {
const res = await listRewardType({ pageNum: 1, pageSize: 1000, status: '0' }); // 只查询生效的数据
rewardTypeOptions.value = res.data?.rows || res.rows || [];
} catch (error) {
console.error('加载奖励类型失败:', error);
}
};
/** 奖励类型选择处理 */
const handleRewardTypeChange = (typeId: string | number) => {
if (!typeId) {
form.value.rewardTypeName = undefined;
return;
}
const selectedType = rewardTypeOptions.value.find((type) => type.id === typeId);
if (selectedType) {
form.value.rewardTypeName = selectedType.typeName;
}
};
// 文件处理相关方法(从DisciplineRecordForm复制)
const isImageFile = (file: any) => {
const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp'];
return imageTypes.includes(file.raw?.type) || (file.url && /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name));
};
const getFileOssId = (file: any) => {
return file.ossId || '';
};
const getFileExt = (fileName: string) => {
const ext = fileName.split('.').pop()?.toUpperCase();
return ext || 'FILE';
};
const getShortFileName = (fileName: string) => {
if (!fileName) return '';
const lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex === -1) {
return fileName.length > 12 ? fileName.substring(0, 12) + '...' : fileName;
}
const name = fileName.substring(0, lastDotIndex);
const ext = fileName.substring(lastDotIndex);
if (name.length <= 8) {
return fileName;
}
return name.substring(0, 8) + '...' + ext;
};
const handleFilePreview = (file: any) => {
if (isImageFile(file)) {
window.open(file.url);
} else {
handleDownloadAttachment(file);
}
};
const handleBeforeUpload = (file: any) => {
const isValidType = /\.(jpg|jpeg|png|gif|bmp|webp|pdf|doc|docx|txt|mp4|avi|mov)$/i.test(file.name);
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isValidType) {
ElMessage.error('上传文件格式不正确!');
return false;
}
if (!isLt10M) {
ElMessage.error('上传文件大小不能超过 10MB!');
return false;
}
return true;
};
const handleUploadSuccess = (response: any, file: any) => {
if (response.code === 200) {
file.url = response.data.url;
file.ossId = response.data.ossId;
ElMessage.success('文件上传成功');
} else {
ElMessage.error(response.msg || '文件上传失败');
}
};
const handleUploadError = (error: any, file: any) => {
console.error('文件上传失败:', error);
ElMessage.error('文件上传失败');
};
const handleFileChange = (file: any, fileList: any) => {
attachmentList.value = fileList;
};
const handleRemoveAttachmentConfirm = (file: any) => {
ElMessageBox.confirm('确定要删除这个文件吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
handleRemoveAttachment(file);
})
.catch(() => {});
};
const handleRemoveAttachment = (file: any) => {
const index = attachmentList.value.findIndex((item: any) => item.uid === file.uid);
if (index > -1) {
attachmentList.value.splice(index, 1);
ElMessage.success('文件删除成功');
}
};
const handleDownloadAttachment = async (file: any) => {
// 下载文件逻辑(参考DisciplineRecordForm)
let downloadUrl = '';
let fileName = file.name || 'attachment';
if (file.response && file.response.data && file.response.data.filePath) {
downloadUrl = file.response.data.filePath;
fileName = file.response.data.fileName || fileName;
} else if (file.ossId) {
downloadUrl = file.ossId;
} else if (file.raw) {
const link = document.createElement('a');
link.href = URL.createObjectURL(file.raw);
link.download = fileName;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(link.href);
return;
}
if (!downloadUrl) {
ElMessage.error('无法获取文件下载地址');
return;
}
try {
const loading = ElLoading.service({
text: '正在下载文件...',
target: document.body
});
const response = await request({
url: '/resource/oss/download/' + downloadUrl,
method: 'get',
responseType: 'blob',
timeout: 300000
});
if (!response) {
throw new Error('响应数据为空');
}
let blobData: Blob;
if (response instanceof Blob) {
blobData = response;
} else if (response.data instanceof Blob) {
blobData = response.data;
} else {
throw new Error('无效的响应数据格式');
}
const url = window.URL.createObjectURL(blobData);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
loading?.close();
} catch (error) {
console.error('下载文件失败:', error);
ElMessage.error('下载文件失败,请稍后重试');
}
};
const handleAttachmentData = async () => {
try {
if (!form.value.attachmentOssIds || !form.value.attachmentOssIds.toString().trim()) {
attachmentList.value = [];
return;
}
const ossIdsArray = form.value.attachmentOssIds
.toString()
.split(',')
.map((id) => id.trim())
.filter((id) => id);
if (ossIdsArray.length === 0) {
attachmentList.value = [];
return;
}
const response = await request({
url: `/resource/oss/listByIds/${ossIdsArray.join(',')}`,
method: 'get'
});
if (response.code === 200 && response.data) {
const attachmentFiles = response.data.map((item: any, index: number) => ({
uid: `existing_${index}_${Date.now()}`,
name: item.originalName || `附件${index + 1}`,
url: item.url,
ossId: item.ossId,
status: 'success',
response: {
code: 200,
data: {
url: item.url,
ossId: item.ossId
}
}
}));
attachmentList.value = attachmentFiles;
} else {
attachmentList.value = [];
}
} catch (error) {
console.error('处理附件数据失败:', error);
attachmentList.value = [];
}
};
// 暴露方法给父组件使用
defineExpose({
openDialog
});
</script>
<style scoped>
.student-option {
padding: 2px 0;
min-height: 32px;
}
.student-main {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 1px;
}
.student-name {
font-weight: 500;
color: #303133;
font-size: 13px;
}
.student-no {
font-size: 11px;
color: #606266;
padding: 0px 3px;
border-radius: 2px;
}
.student-info {
display: flex;
align-items: center;
gap: 4px;
font-size: 11px;
color: #909399;
margin-top: 1px;
}
.info-item:not(:last-child)::after {
content: '·';
margin-left: 6px;
color: #c0c4cc;
}
/* 附件上传容器样式 */
.attachment-upload-container {
width: 100%;
max-height: calc(100vh - 650px);
min-height: 200px;
overflow-y: auto;
padding-right: 8px;
border: 1px solid #e4e7ed;
border-radius: 6px;
background: #fafafa;
}
/* 附件上传区域内容样式 */
.attachment-upload-container .upload-demo {
padding: 12px;
min-height: 180px;
}
.attachment-upload-container .upload-demo .el-upload {
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: all 0.3s;
}
.attachment-upload-container .upload-demo .el-upload:hover {
border-color: #409eff;
}
.attachment-upload-container .upload-demo .el-upload__tip {
font-size: 12px;
color: #000000;
margin-top: 7px;
}
/* 调整拖拽上传图标与文字在同一行、居中对齐 */
.attachment-upload-container .upload-demo .el-upload-dragger1 {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 15px;
padding: 0px !important;
}
.attachment-upload-container .upload-demo .el-upload-dragger1 .el-icon--upload {
margin: 0;
padding: 0px !important;
}
/* 文件卡片占位封面样式 */
.attachment-upload-container .upload-demo .upload-file-placeholder {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #909399;
font-size: 14px;
}
.attachment-upload-container .upload-demo .upload-file-placeholder .el-icon {
font-size: 28px;
margin-bottom: 4px;
}
.attachment-upload-container .upload-demo .upload-file-placeholder .file-ext {
font-size: 12px;
text-transform: uppercase;
text-align: center;
}
/* 让附件卡片一行展示 5 个 */
.attachment-upload-container :deep(.el-upload-list--picture-card) {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 9px;
margin-top: 12px;
padding-bottom: 16px;
}
/* 确保卡片宽度自适应 */
.attachment-upload-container :deep(.el-upload-list--picture-card) .el-upload-list__item {
margin: 0;
height: 180px !important;
width: 100% !important;
}
:deep(.el-upload-dragger) {
border: 0px !important;
padding: 0px !important;
}
:deep(.el-upload-dragger) .el-upload-dragger1 {
border: 0px !important;
padding: 0px !important;
}
/* 文件卡片样式 */
.upload-file-card {
position: relative;
width: 100%;
height: 100%;
border-radius: 6px;
overflow: hidden;
background: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.3s;
}
.upload-file-card:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.upload-file-thumb {
position: relative;
width: 100%;
height: 110px;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.upload-thumb-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.upload-file-loading {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 10;
}
.loading-text {
margin-top: 8px;
font-size: 12px;
color: #606266;
}
.upload-file-name {
padding: 6px 8px;
font-size: 11px;
color: #303133;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
background: #fafafa;
border-top: 1px solid #f0f0f0;
line-height: 1.2;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
word-break: break-all;
max-width: 100%;
}
.upload-file-card-actions {
display: flex;
justify-content: center;
align-items: center;
padding: 6px 8px;
background: #fafafa;
border-top: 1px solid #f0f0f0;
transition: all 0.3s;
height: 38px;
}
.upload-file-card-actions .el-button-group {
background: transparent;
border-radius: 4px;
padding: 0;
}
.upload-file-card-actions .el-button {
padding: 4px 8px;
border: 1px solid #e0e0e0;
background: #fff;
font-size: 12px;
margin: 0 2px;
border-radius: 3px;
transition: all 0.3s;
}
.upload-file-card-actions .el-button:hover {
background: #f5f5f5;
border-color: #409eff;
}
.upload-file-card-actions .el-button.is-disabled {
opacity: 0.5;
cursor: not-allowed;
background: #f5f5f5;
}
/* 查看模式下的学生信息显示 */
.student-display {
display: flex;
align-items: center;
gap: 8px;
padding: 0 11px;
background: #f8f9fa;
border: 1px solid #e4e7ed;
border-radius: 4px;
font-size: 14px;
height: 32px;
width: 100%;
box-sizing: border-box;
}
.student-name-text {
font-weight: 500;
color: #303133;
}
.student-no-text {
color: #606266;
font-size: 13px;
}
/* 查看模式下的附件网格展示 */
.attachment-view-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 9px;
margin-top: 12px;
padding-bottom: 16px;
}
.attachment-view-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.attachment-item {
padding: 5px 10px;
background: #f5f7fa;
border-radius: 4px;
border: 1px solid #e4e7ed;
}
</style>
+289
View File
@@ -0,0 +1,289 @@
<template>
<div class="p-2 page-container">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams">
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="学号" prop="studentNo">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="学生姓名" prop="studentName">
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
<el-option v-for="dict in student_reward_status" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24" class="search-btn-container">
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['rewardRecord:rewardRecord:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['rewardRecord:rewardRecord:edit']"
>修改</el-button
>
</el-col>
<!-- <el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete()"
v-hasPermi="['rewardRecord:rewardRecord:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['rewardRecord:rewardRecord:export']"
>导出</el-button
>
</el-col> -->
<right-toolbar :showSearch="showSearch" @update:showSearch="showSearch = $event" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="rewardRecordList" @selection-change="handleSelectionChange" class="full-width-table">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="学号" align="center" prop="studentNo" min-width="120" />
<el-table-column label="学生姓名" align="center" prop="studentName" min-width="100" />
<el-table-column label="学院名称" align="center" prop="collegeName" min-width="140" show-overflow-tooltip />
<el-table-column label="专业名称" align="center" prop="majorName" min-width="140" show-overflow-tooltip />
<el-table-column label="班级名称" align="center" prop="className" min-width="120" show-overflow-tooltip />
<el-table-column label="奖励类型" align="center" prop="rewardTypeName" min-width="140" show-overflow-tooltip />
<el-table-column label="奖励日期" align="center" prop="rewardDate" min-width="130" />
<el-table-column label="加分分值" align="center" prop="scoreAddition" min-width="100">
<template #default="scope">
<span>{{ scope.row.scoreAddition ? parseFloat(scope.row.scoreAddition).toFixed(2) : '0.00' }}</span>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" min-width="80">
<template #default="scope">
<dict-tag :options="student_reward_status" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="180">
<template #default="scope">
<el-tooltip content="查看" placement="top">
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['rewardRecord:rewardRecord:query']"></el-button>
</el-tooltip>
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['rewardRecord:rewardRecord:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button
link
type="danger"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['rewardRecord:rewardRecord:remove']"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改学生奖励记录对话框 -->
<RewardRecordForm ref="rewardRecordFormRef" @success="handleSuccess" />
</div>
</template>
<script setup name="RewardRecord" lang="ts">
import { ref, reactive, toRefs, onMounted, getCurrentInstance, ComponentInternalInstance } from 'vue';
import { listRewardRecord, delRewardRecord } from '@/api/xskh/rewardRecord/rewardRecord';
import { XskhRewardRecordVO, XskhRewardRecordQuery, XskhRewardRecordForm as RewardRecordFormType } from '@/api/xskh/rewardRecord/rewardRecord/types';
import RewardRecordForm from './RewardRecordForm.vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { student_reward_status } = toRefs<any>(proxy?.useDict('student_reward_status'));
const rewardRecordList = ref<XskhRewardRecordVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const rewardRecordFormRef = ref<ComponentRef<typeof RewardRecordForm>>();
const initFormData: RewardRecordFormType = {
id: undefined,
studentId: undefined,
studentNo: undefined,
studentName: undefined,
collegeId: undefined,
collegeName: undefined,
majorId: undefined,
majorName: undefined,
classId: undefined,
className: undefined,
rewardTypeId: undefined,
rewardTypeName: undefined,
rewardDate: undefined,
rewardContent: undefined,
scoreAddition: undefined,
status: undefined,
attachmentOssIds: undefined
};
const data = reactive<PageData<RewardRecordFormType, XskhRewardRecordQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
studentId: undefined,
studentNo: undefined,
studentName: undefined,
collegeId: undefined,
collegeName: undefined,
majorId: undefined,
majorName: undefined,
classId: undefined,
className: undefined,
rewardTypeId: undefined,
rewardTypeName: undefined,
rewardDate: undefined,
rewardContent: undefined,
scoreAddition: undefined,
status: undefined,
params: {}
}
});
const { queryParams, form } = toRefs(data);
/** 查询学生奖励记录列表 */
const getList = async () => {
loading.value = true;
const res = await listRewardRecord(queryParams.value);
rewardRecordList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: XskhRewardRecordVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
rewardRecordFormRef.value?.openDialog(undefined, '新增');
};
/** 查看按钮操作 */
const handleView = (row: XskhRewardRecordVO) => {
rewardRecordFormRef.value?.openDialog(row.id, '查看');
};
/** 修改按钮操作 */
const handleUpdate = (row?: XskhRewardRecordVO) => {
const id = row?.id || ids.value[0];
rewardRecordFormRef.value?.openDialog(id, '修改');
};
/** 提交成功回调 */
const handleSuccess = () => {
getList();
};
/** 删除按钮操作 */
const handleDelete = async (row?: XskhRewardRecordVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除学生奖励记录编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
await delRewardRecord(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'rewardRecord/rewardRecord/export',
{
...queryParams.value
},
`rewardRecord_${new Date().getTime()}.xlsx`
);
};
onMounted(() => {
getList();
});
</script>
<style scoped>
/* 页面容器样式 */
.page-container {
padding: 8px;
}
/* 搜索按钮容器样式 */
.search-btn-container {
display: flex;
justify-content: flex-end;
margin-top: 10px;
.el-form-item {
margin-bottom: 0 !important;
}
}
/* 表格全宽样式 */
.full-width-table {
width: 100%;
}
/* 确保表格容器充满宽度 */
:deep(.el-table) {
width: 100%;
}
/* 表格内容区域充满宽度 */
:deep(.el-table__body-wrapper) {
width: 100%;
}
</style>
@@ -8,36 +8,36 @@
<!-- 左列:学生基本信息 -->
<el-col :span="12">
<el-form-item label="学号">
<div class="form-field-value">{{ disciplineRecord.studentNo }}</div>
<div class="form-field-value">{{ disciplineRecord?.studentNo || '' }}</div>
</el-form-item>
<el-form-item label="学生姓名">
<div class="form-field-value">{{ disciplineRecord.studentName }}</div>
<div class="form-field-value">{{ disciplineRecord?.studentName || '' }}</div>
</el-form-item>
<el-form-item label="学院名称">
<div class="form-field-value">{{ disciplineRecord.collegeName }}</div>
<div class="form-field-value">{{ disciplineRecord?.collegeName || '' }}</div>
</el-form-item>
<el-form-item label="专业名称">
<div class="form-field-value">{{ disciplineRecord.majorName }}</div>
<div class="form-field-value">{{ disciplineRecord?.majorName || '' }}</div>
</el-form-item>
<el-form-item label="班级名称">
<div class="form-field-value">{{ disciplineRecord.className }}</div>
<div class="form-field-value">{{ disciplineRecord?.className || '' }}</div>
</el-form-item>
</el-col>
<!-- 右列:违纪处分信息 -->
<el-col :span="12">
<el-form-item label="违纪处分类型">
<div class="form-field-value">{{ disciplineRecord.disciplineTypeName }}</div>
<div class="form-field-value">{{ disciplineRecord?.disciplineTypeName || '' }}</div>
</el-form-item>
<el-form-item label="违纪处分日期">
<div class="form-field-value">{{ disciplineRecord.disciplineDate }}</div>
<div class="form-field-value">{{ disciplineRecord?.disciplineDate || '' }}</div>
</el-form-item>
<el-form-item label="扣分分值">
<div class="form-field-value">{{ disciplineRecord.scoreDeduction }}</div>
<div class="form-field-value">{{ disciplineRecord?.scoreDeduction || '' }}</div>
</el-form-item>
<el-form-item label="状态">
<div class="form-field-value">
<dict-tag :options="student_discipline_status" :value="disciplineRecord.status" />
<dict-tag :options="student_discipline_status" :value="disciplineRecord?.status" />
</div>
</el-form-item>
</el-col>
@@ -47,7 +47,7 @@
<el-row>
<el-col :span="24">
<el-form-item label="违纪处分原因">
<div class="form-field-value textarea-value">{{ disciplineRecord.disciplineReason }}</div>
<div class="form-field-value textarea-value">{{ disciplineRecord?.disciplineReason || '' }}</div>
</el-form-item>
</el-col>
</el-row>
@@ -55,13 +55,13 @@
<el-row>
<el-col :span="24">
<el-form-item label="违纪处分内容">
<div class="form-field-value textarea-value">{{ disciplineRecord.disciplineContent }}</div>
<div class="form-field-value textarea-value">{{ disciplineRecord?.disciplineContent || '' }}</div>
</el-form-item>
</el-col>
</el-row>
<!-- 违纪记录附件展示(只读) -->
<el-row v-if="disciplineAttachmentList.length > 0">
<el-row v-if="disciplineAttachmentList && disciplineAttachmentList.length > 0">
<el-col :span="24">
<el-form-item label="违纪记录附件">
<div class="attachment-view-container">
@@ -102,22 +102,22 @@
<!-- 左列:申请信息 -->
<el-col :span="12">
<el-form-item label="申请时间">
<div class="form-field-value">{{ revokeData.applyTime }}</div>
<div class="form-field-value">{{ revokeData?.applyTime || '' }}</div>
</el-form-item>
<el-form-item label="申请状态">
<div class="form-field-value">
<dict-tag :options="student_revoke_status" :value="revokeData.status" />
<dict-tag :options="student_revoke_status" :value="revokeData?.status" />
</div>
</el-form-item>
</el-col>
<!-- 右列:审批信息 -->
<el-col :span="12">
<el-form-item label="审核时间" v-if="revokeData.approvalTime">
<el-form-item label="审核时间" v-if="revokeData?.approvalTime">
<div class="form-field-value">{{ revokeData.approvalTime }}</div>
</el-form-item>
<el-form-item label="审核人" v-if="revokeData.approvalBy">
<div class="form-field-value">{{ revokeData.approvalBy }}</div>
<el-form-item label="审核人" v-if="revokeData?.approvalByName">
<div class="form-field-value">{{ revokeData.approvalByName }}</div>
</el-form-item>
</el-col>
</el-row>
@@ -125,12 +125,12 @@
<el-row>
<el-col :span="24">
<el-form-item label="撤销申请原因">
<div class="form-field-value textarea-value">{{ revokeData.revokeReason }}</div>
<div class="form-field-value textarea-value">{{ revokeData?.revokeReason || '' }}</div>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="revokeData.approvalRemark">
<el-row v-if="revokeData?.approvalRemark">
<el-col :span="24">
<el-form-item label="审核备注">
<div class="form-field-value textarea-value">{{ revokeData.approvalRemark }}</div>
@@ -139,7 +139,7 @@
</el-row>
<!-- 撤销证明材料展示(只读) -->
<el-row v-if="revokeAttachmentList.length > 0">
<el-row v-if="revokeAttachmentList && revokeAttachmentList.length > 0">
<el-col :span="24">
<el-form-item label="撤销证明材料">
<div class="attachment-view-container">