食堂问卷调查

This commit is contained in:
yaojian
2026-03-11 11:30:51 +08:00
parent a7da30f6c4
commit 553dbe5137
22 changed files with 3589 additions and 133 deletions

View File

@@ -0,0 +1,515 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<!-- 搜索表单卡片 -->
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch" class="search-form">
<el-form-item label="申请类型" prop="applyType">
<el-select
v-model="searchForm.applyType"
placeholder="请选择申请类型"
clearable
style="width: 200px">
<el-option label="任职" value="1" />
<el-option label="调换" value="2" />
</el-select>
</el-form-item>
<el-form-item label="申请状态" prop="status">
<el-select
v-model="searchForm.status"
placeholder="请选择状态"
clearable
style="width: 200px">
<el-option label="待审核" value="0" />
<el-option label="驳回" value="1" />
<el-option label="撤回" value="2" />
<el-option label="通过" value="3" />
</el-select>
</el-form-item>
<el-form-item label="班级" prop="classCode">
<el-select
v-model="searchForm.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="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 内容卡片 -->
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
班主任调换申请列表
</span>
<div class="header-actions">
<el-button
icon="Plus"
type="primary"
@click="handleAdd">
新增申请
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10"
@queryTable="getDataList">
</right-toolbar>
</div>
</div>
</template>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table">
<el-table-column type="index" label="序号" width="70" align="center">
<template #default="{ $index }">
{{ $index + 1 + ((state.pagination?.current || 1) - 1) * (state.pagination?.size || 10) }}
</template>
</el-table-column>
<el-table-column prop="applyType" label="申请类型" align="center" width="100">
<template #default="scope">
<el-tag :type="scope.row.applyType === '1' ? 'success' : 'warning'" size="small">
{{ scope.row.applyType === '1' ? '任职' : '调换' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="className" label="班级" align="center" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.classNo }} - {{ scope.row.className }}
</template>
</el-table-column>
<el-table-column prop="fromTeacherName" label="原班主任" align="center" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.fromTeacherNo }} - {{ scope.row.fromTeacherName || '-' }}
</template>
</el-table-column>
<el-table-column prop="toTeacherName" label="拟任班主任" align="center" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.toTeacherNo }} - {{ scope.row.toTeacherName || '-' }}
</template>
</el-table-column>
<el-table-column prop="reason" label="申请原因" align="center" show-overflow-tooltip min-width="150" />
<el-table-column prop="effectTime" label="期望生效时间" align="center" width="160">
<template #default="scope">
{{ formatDateTime(scope.row.effectTime) }}
</template>
</el-table-column>
<el-table-column prop="status" label="状态" align="center" width="100">
<template #default="scope">
<el-tag :type="getStatusType(scope.row.status)" size="small">
{{ getStatusLabel(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="申请时间" align="center" width="160">
<template #default="scope">
{{ formatDateTime(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center" fixed="right">
<template #default="scope">
<el-button
v-if="scope.row.status === '0'"
icon="Check"
link
type="success"
@click="handleAudit(scope.row, '3')">
审批通过
</el-button>
<el-button
v-if="scope.row.status === '0'"
icon="Close"
link
type="danger"
@click="handleAudit(scope.row, '1')">
驳回
</el-button>
<el-button
v-if="scope.row.status === '0'"
icon="RefreshLeft"
link
type="warning"
@click="handleAudit(scope.row, '2')">
撤回
</el-button>
<el-button
v-if="scope.row.status === '0'"
icon="Delete"
link
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-wrapper">
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
</el-card>
</div>
<!-- 新增申请弹窗 -->
<el-dialog
v-model="dialogVisible"
title="新增班主任调换申请"
width="600px"
:close-on-click-modal="false">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px">
<el-form-item label="申请类型" prop="applyType">
<el-radio-group v-model="formData.applyType">
<el-radio label="1">任职</el-radio>
<el-radio label="2">调换</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="班级" prop="classCode">
<el-select
v-model="formData.classCode"
placeholder="请选择班级"
filterable
style="width: 100%"
@change="handleClassChange">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="`${item.classNo} - ${item.className}`"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="拟任班主任" prop="toTeacherNo">
<el-select
v-model="formData.toTeacherNo"
placeholder="请选择拟任班主任"
filterable
style="width: 100%">
<el-option
v-for="item in teacherList"
:key="item.teacherNo"
:label="`${item.teacherNo} - ${item.teacherName}`"
:value="item.teacherNo">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="申请原因" prop="reason">
<el-input v-model="formData.reason" type="textarea" :rows="3" placeholder="请输入申请原因" />
</el-form-item>
<el-form-item label="期望生效时间" prop="effectTime">
<el-date-picker
v-model="formData.effectTime"
type="datetime"
placeholder="请选择期望生效时间"
style="width: 100%"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">提交申请</el-button>
</template>
</el-dialog>
<!-- 审批意见弹窗 -->
<el-dialog
v-model="auditDialogVisible"
:title="auditDialogTitle"
width="500px"
:close-on-click-modal="false">
<el-form
ref="auditFormRef"
:model="auditFormData"
:rules="auditFormRules"
label-width="100px">
<el-form-item label="审批意见" prop="remark">
<el-input v-model="auditFormData.remark" type="textarea" :rows="3" placeholder="请输入审批意见" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="auditDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleAuditSubmit" :loading="auditLoading">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="ClassMasterJobApply">
import { reactive, ref, onMounted, computed } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, addObj, auditObj, delObj } from "/@/api/stuwork/classmasterjobapply";
import { list as getClassList } from "/@/api/basic/basicclass";
import { queryAllTeacher } from "/@/api/professional/professionaluser/teacherbase";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { Search, Document, Plus, Check, Close, RefreshLeft, Delete } from '@element-plus/icons-vue'
// 定义变量
const searchFormRef = ref()
const formRef = ref()
const auditFormRef = ref()
const showSearch = ref(true)
const dialogVisible = ref(false)
const auditDialogVisible = ref(false)
const submitLoading = ref(false)
const auditLoading = ref(false)
const classList = ref<any[]>([])
const teacherList = ref<any[]>([])
// 搜索表单
const searchForm = reactive({
applyType: '',
status: '',
classCode: ''
})
// 表单数据
const formData = reactive({
applyType: '2',
classCode: '',
toTeacherNo: '',
reason: '',
effectTime: ''
})
// 审批表单数据
const auditFormData = reactive({
id: '',
status: '',
remark: ''
})
// 表单验证规则
const formRules = {
applyType: [{ required: true, message: '请选择申请类型', trigger: 'change' }],
classCode: [{ required: true, message: '请选择班级', trigger: 'change' }],
toTeacherNo: [{ required: true, message: '请选择拟任班主任', trigger: 'change' }],
reason: [{ required: true, message: '请输入申请原因', trigger: 'blur' }]
}
// 审批表单验证规则
const auditFormRules = {
remark: [{ required: false, message: '请输入审批意见', trigger: 'blur' }]
}
// 审批弹窗标题
const auditDialogTitle = computed(() => {
if (auditFormData.status === '1') return '驳回申请'
if (auditFormData.status === '2') return '撤回申请'
return '审批通过'
})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: searchForm,
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state)
// 获取状态类型
const getStatusType = (status: string) => {
const map: Record<string, string> = {
'0': 'warning',
'1': 'danger',
'2': 'info',
'3': 'success'
}
return map[status] || 'info'
}
// 获取状态标签
const getStatusLabel = (status: string) => {
const map: Record<string, string> = {
'0': '待审核',
'1': '驳回',
'2': '撤回',
'3': '通过'
}
return map[status] || status
}
// 格式化日期时间
const formatDateTime = (dateTime: string) => {
if (!dateTime) return '-'
return dateTime
}
// 查询
const handleSearch = () => {
getDataList()
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
searchForm.applyType = ''
searchForm.status = ''
searchForm.classCode = ''
getDataList()
}
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassList()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
classList.value = []
}
}
// 获取教师列表
const getTeacherListData = async () => {
try {
const res = await queryAllTeacher()
if (res.data) {
teacherList.value = Array.isArray(res.data) ? res.data.map((item: any) => ({
teacherNo: item.teacherNo || item.teacherCode,
teacherName: item.teacherName || item.realName
})) : []
}
} catch (err) {
teacherList.value = []
}
}
// 班级选择变更
const handleClassChange = (classCode: string) => {
// 可以根据班级获取该班级的原班主任信息
// 这里暂时清空拟任班主任
formData.toTeacherNo = ''
// TODO: 获取可选教师列表
}
// 新增
const handleAdd = () => {
resetForm()
dialogVisible.value = true
}
// 重置表单
const resetForm = () => {
formData.applyType = '2'
formData.classCode = ''
formData.toTeacherNo = ''
formData.reason = ''
formData.effectTime = ''
formRef.value?.resetFields()
}
// 提交
const handleSubmit = async () => {
try {
await formRef.value?.validate()
submitLoading.value = true
await addObj(formData)
useMessage().success('提交成功')
dialogVisible.value = false
getDataList()
} catch (err: any) {
if (err?.msg) {
useMessage().error(err.msg)
}
} finally {
submitLoading.value = false
}
}
// 审批
const handleAudit = (row: any, status: string) => {
auditFormData.id = row.id
auditFormData.status = status
auditFormData.remark = ''
auditDialogVisible.value = true
}
// 审批提交
const handleAuditSubmit = async () => {
try {
await auditFormRef.value?.validate()
auditLoading.value = true
await auditObj(auditFormData)
useMessage().success('操作成功')
auditDialogVisible.value = false
getDataList()
} catch (err: any) {
if (err?.msg) {
useMessage().error(err.msg)
}
} finally {
auditLoading.value = false
}
}
// 删除
const handleDelete = async (row: any) => {
try {
await useMessageBox().confirm('确定要删除该申请吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
// 初始化
onMounted(() => {
getClassListData()
getTeacherListData()
})
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
</style>

View File

@@ -0,0 +1,117 @@
<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-form-item label="食堂名称" prop="diningHallName">
<el-input v-model="form.diningHallName" placeholder="请输入食堂名称" />
</el-form-item>
<el-form-item label="食堂位置" prop="diningHallPlace">
<el-input v-model="form.diningHallPlace" placeholder="请输入食堂位置" />
</el-form-item>
<el-form-item label="学年" prop="year">
<el-input v-model="form.year" placeholder="请输入学年2024-2025" />
</el-form-item>
<el-form-item label="学期" prop="period">
<el-select v-model="form.period" placeholder="请选择学期" style="width: 100%">
<el-option label="第一学期" value="1" />
<el-option label="第二学期" value="2" />
</el-select>
</el-form-item>
</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="DiningHallFormDialog">
import { ref, reactive, nextTick } from 'vue';
import { useMessage } from '/@/hooks/message';
import { addObj, putObj } from '/@/api/stuwork/dininghall';
const emit = defineEmits(['refresh']);
const dataFormRef = ref();
const visible = ref(false);
const loading = ref(false);
const operType = ref('add');
const form = reactive({
id: '',
diningHallName: '',
diningHallPlace: '',
year: '',
period: '',
});
const dataRules = {
diningHallName: [{ required: true, message: '请输入食堂名称', trigger: 'blur' }],
diningHallPlace: [{ required: true, message: '请输入食堂位置', trigger: 'blur' }],
year: [{ required: true, message: '请输入学年', trigger: 'blur' }],
period: [{ required: true, message: '请选择学期', trigger: 'change' }],
};
const openDialog = (type: string = 'add', row?: any) => {
visible.value = true;
operType.value = type;
nextTick(() => {
dataFormRef.value?.resetFields();
form.id = '';
form.diningHallName = '';
form.diningHallPlace = '';
form.year = '';
form.period = '';
if (type === 'edit' && row) {
form.id = row.id;
form.diningHallName = row.diningHallName || '';
form.diningHallPlace = row.diningHallPlace || '';
form.year = row.year || '';
form.period = row.period || '';
}
});
};
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({
diningHallName: form.diningHallName,
diningHallPlace: form.diningHallPlace,
year: form.year,
period: form.period,
});
useMessage().success('新增成功');
} else {
await putObj({
id: form.id,
diningHallName: form.diningHallName,
diningHallPlace: form.diningHallPlace,
year: form.year,
period: form.period,
});
useMessage().success('编辑成功');
}
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'));
} finally {
loading.value = false;
}
});
};
defineExpose({
openDialog,
});
</script>

View File

@@ -0,0 +1,159 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
食堂列表
</span>
<div class="header-actions">
<el-button icon="FolderAdd" type="primary" @click="formDialogRef.openDialog()"> </el-button>
<right-toolbar class="ml10" @queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</div>
</template>
<!-- 搜索表单 -->
<el-form :model="searchForm" inline class="search-form">
<el-form-item label="食堂名称">
<el-input v-model="searchForm.diningHallName" placeholder="请输入食堂名称" clearable style="width: 200px" />
</el-form-item>
<el-form-item label="学年">
<el-input v-model="searchForm.year" placeholder="请输入学年" clearable style="width: 150px" />
</el-form-item>
<el-form-item>
<el-button icon="Search" type="primary" @click="getDataList(true)"> </el-button>
<el-button icon="Refresh" @click="resetSearch"> </el-button>
</el-form-item>
</el-form>
<el-table
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table"
>
<el-table-column type="index" label="序号" width="70" align="center">
<template #default="{ $index }">
{{ $index + 1 }}
</template>
</el-table-column>
<el-table-column prop="diningHallName" label="食堂名称" min-width="200" show-overflow-tooltip />
<el-table-column prop="diningHallPlace" label="食堂位置" min-width="150" show-overflow-tooltip />
<el-table-column prop="year" label="学年" width="120" align="center" />
<el-table-column prop="period" label="学期" width="100" align="center">
<template #default="{ row }">
{{ row.period === '1' ? '第一学期' : row.period === '2' ? '第二学期' : '-' }}
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button icon="Edit" link type="primary" @click="handleEdit(scope.row)"> 编辑 </el-button>
<el-button icon="Delete" link type="danger" @click="handleDelete(scope.row)"> 删除 </el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="暂无数据" :image-size="120" />
</template>
</el-table>
<div class="pagination-wrapper">
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
</el-card>
</div>
<FormDialog ref="formDialogRef" @refresh="getDataList(false)" />
</div>
</template>
<script setup lang="ts" name="DiningHall">
import { ref, reactive, defineAsyncComponent } from 'vue';
import { BasicTableProps, useTable } from '/@/hooks/table';
import { fetchList, delObjs } from '/@/api/stuwork/dininghall';
import { useMessage, useMessageBox } from '/@/hooks/message';
import TableColumnControl from '/@/components/TableColumnControl/index.vue';
import { Document, Menu } from '@element-plus/icons-vue';
import { useTableColumnControl } from '/@/hooks/tableColumn';
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const formDialogRef = ref();
const columnControlRef = ref<any>();
const tableColumns = [
{ prop: 'diningHallName', label: '食堂名称' },
{ prop: 'diningHallPlace', label: '食堂位置' },
{ prop: 'year', label: '学年' },
{ prop: 'period', label: '学期' },
];
const { visibleColumns, visibleColumnsSorted, checkColumnVisible, handleColumnChange, handleColumnOrderChange } = useTableColumnControl(tableColumns);
const searchForm = reactive({
diningHallName: '',
year: '',
});
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: searchForm,
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total',
},
});
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state);
const resetSearch = () => {
searchForm.diningHallName = '';
searchForm.year = '';
getDataList(true);
};
const handleEdit = (row: any) => {
if (formDialogRef.value) {
formDialogRef.value.openDialog('edit', row);
}
};
const handleDelete = async (row: any) => {
try {
await useMessageBox().confirm('确定要删除该食堂吗?');
await delObjs([row.id]);
useMessage().success('删除成功');
getDataList();
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败');
}
}
};
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
</style>

View File

@@ -0,0 +1,158 @@
<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-form-item label="食堂" prop="diningHallId">
<el-select v-model="form.diningHallId" placeholder="请选择食堂" style="width: 100%">
<el-option v-for="item in diningHallList" :key="item.id" :label="item.diningHallName" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="题目名称" prop="voteTitle">
<el-input v-model="form.voteTitle" placeholder="请输入题目名称" />
</el-form-item>
<el-form-item label="题目类型" prop="voteProjectId">
<el-select v-model="form.voteProjectId" placeholder="请选择题目类型" style="width: 100%">
<el-option v-for="item in questionnaireList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="学年" prop="year">
<el-input v-model="form.year" placeholder="请输入学年2024-2025" />
</el-form-item>
<el-form-item label="学期" prop="period">
<el-select v-model="form.period" placeholder="请选择学期" style="width: 100%">
<el-option label="第一学期" value="1" />
<el-option label="第二学期" value="2" />
</el-select>
</el-form-item>
</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="DiningHallVoteFormDialog">
import { ref, reactive, nextTick } from 'vue';
import { useMessage } from '/@/hooks/message';
import { addObj, putObj, getQuestionnaireDict } from '/@/api/stuwork/dininghallvote';
import { getList as getDiningHallList } from '/@/api/stuwork/dininghall';
const props = defineProps<{
diningHallList?: any[];
}>();
const emit = defineEmits(['refresh']);
const dataFormRef = ref();
const visible = ref(false);
const loading = ref(false);
const operType = ref('add');
const diningHallList = ref<any[]>([]);
const questionnaireList = ref<any[]>([]);
const form = reactive({
id: '',
diningHallId: '',
voteTitle: '',
voteProjectId: '',
year: '',
period: '',
});
const dataRules = {
diningHallId: [{ required: true, message: '请选择食堂', trigger: 'change' }],
voteTitle: [{ required: true, message: '请输入题目名称', trigger: 'blur' }],
voteProjectId: [{ required: true, message: '请选择题目类型', trigger: 'change' }],
year: [{ required: true, message: '请输入学年', trigger: 'blur' }],
period: [{ required: true, message: '请选择学期', trigger: 'change' }],
};
const loadDiningHallList = async () => {
try {
const res = await getDiningHallList();
diningHallList.value = res.data || [];
} catch (error) {
console.error('加载食堂列表失败', error);
}
};
const loadQuestionnaireDict = async () => {
try {
const res = await getQuestionnaireDict();
questionnaireList.value = res.data || [];
} catch (error) {
console.error('加载题目类型失败', error);
}
};
const openDialog = (type: string = 'add', row?: any) => {
visible.value = true;
operType.value = type;
nextTick(() => {
dataFormRef.value?.resetFields();
form.id = '';
form.diningHallId = '';
form.voteTitle = '';
form.voteProjectId = '';
form.year = '';
form.period = '';
if (type === 'edit' && row) {
form.id = row.id;
form.diningHallId = row.diningHallId || '';
form.voteTitle = row.voteTitle || '';
form.voteProjectId = row.voteProjectId || '';
form.year = row.year || '';
form.period = row.period || '';
}
});
loadDiningHallList();
loadQuestionnaireDict();
};
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({
diningHallId: form.diningHallId,
voteTitle: form.voteTitle,
voteProjectId: form.voteProjectId,
year: form.year,
period: form.period,
});
useMessage().success('新增成功');
} else {
await putObj({
id: form.id,
diningHallId: form.diningHallId,
voteTitle: form.voteTitle,
voteProjectId: form.voteProjectId,
year: form.year,
period: form.period,
});
useMessage().success('编辑成功');
}
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || (operType.value === 'add' ? '新增失败' : '编辑失败'));
} finally {
loading.value = false;
}
});
};
defineExpose({
openDialog,
});
</script>

View File

@@ -0,0 +1,173 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
食堂调查题目列表
</span>
<div class="header-actions">
<el-button icon="FolderAdd" type="primary" @click="formDialogRef.openDialog()"> </el-button>
<right-toolbar class="ml10" @queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</div>
</template>
<!-- 搜索表单 -->
<el-form :model="searchForm" inline class="search-form">
<el-form-item label="食堂">
<el-select v-model="searchForm.diningHallId" placeholder="请选择食堂" clearable style="width: 200px">
<el-option v-for="item in diningHallList" :key="item.id" :label="item.diningHallName" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item>
<el-button icon="Search" type="primary" @click="getDataList(true)"> </el-button>
<el-button icon="Refresh" @click="resetSearch"> </el-button>
</el-form-item>
</el-form>
<el-table
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table"
>
<el-table-column type="index" label="序号" width="70" align="center">
<template #default="{ $index }">
{{ $index + 1 }}
</template>
</el-table-column>
<el-table-column prop="voteTitle" label="题目名称" min-width="200" show-overflow-tooltip />
<el-table-column prop="diningHallName" label="所属食堂" min-width="150" show-overflow-tooltip />
<el-table-column prop="year" label="学年" width="120" align="center" />
<el-table-column prop="period" label="学期" width="100" align="center">
<template #default="{ row }">
{{ row.period === '1' ? '第一学期' : row.period === '2' ? '第二学期' : '-' }}
</template>
</el-table-column>
<el-table-column prop="voteProjectName" label="题目类型" width="150" align="center" />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button icon="Edit" link type="primary" @click="handleEdit(scope.row)"> 编辑 </el-button>
<el-button icon="Delete" link type="danger" @click="handleDelete(scope.row)"> 删除 </el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="暂无数据" :image-size="120" />
</template>
</el-table>
<div class="pagination-wrapper">
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
</el-card>
</div>
<FormDialog ref="formDialogRef" @refresh="getDataList(false)" />
</div>
</template>
<script setup lang="ts" name="DiningHallVote">
import { ref, reactive, defineAsyncComponent, onMounted } from 'vue';
import { BasicTableProps, useTable } from '/@/hooks/table';
import { fetchList, delObjs } from '/@/api/stuwork/dininghallvote';
import { getList as getDiningHallList } from '/@/api/stuwork/dininghall';
import { useMessage, useMessageBox } from '/@/hooks/message';
import TableColumnControl from '/@/components/TableColumnControl/index.vue';
import { Document, Menu } from '@element-plus/icons-vue';
import { useTableColumnControl } from '/@/hooks/tableColumn';
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const formDialogRef = ref();
const columnControlRef = ref<any>();
const diningHallList = ref<any[]>([]);
const tableColumns = [
{ prop: 'voteTitle', label: '题目名称' },
{ prop: 'diningHallName', label: '所属食堂' },
{ prop: 'year', label: '学年' },
{ prop: 'period', label: '学期' },
{ prop: 'voteProjectName', label: '题目类型' },
];
const { visibleColumns, visibleColumnsSorted, checkColumnVisible, handleColumnChange, handleColumnOrderChange } = useTableColumnControl(tableColumns);
const searchForm = reactive({
diningHallId: '',
});
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: searchForm,
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total',
},
});
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state);
const resetSearch = () => {
searchForm.diningHallId = '';
getDataList(true);
};
const handleEdit = (row: any) => {
if (formDialogRef.value) {
formDialogRef.value.openDialog('edit', row);
}
};
const handleDelete = async (row: any) => {
try {
await useMessageBox().confirm('确定要删除该题目吗?');
await delObjs([row.id]);
useMessage().success('删除成功');
getDataList();
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败');
}
}
};
const loadDiningHallList = async () => {
try {
const res = await getDiningHallList();
diningHallList.value = res.data || [];
} catch (error) {
console.error('加载食堂列表失败', error);
}
};
onMounted(() => {
loadDiningHallList();
});
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
</style>

View File

@@ -0,0 +1,236 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><DataAnalysis /></el-icon>
食堂调查完成率统计
</span>
</div>
</template>
<!-- 标签页切换 -->
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
<el-tab-pane label="学生统计" name="student">
<!-- 搜索表单 -->
<el-form :model="searchForm" inline class="search-form">
<el-form-item label="学院">
<el-select v-model="searchForm.deptCode" placeholder="请选择学院" clearable style="width: 200px">
<el-option v-for="item in deptList" :key="item.deptCode" :label="item.deptName" :value="item.deptCode" />
</el-select>
</el-form-item>
<el-form-item>
<el-button icon="Search" type="primary" @click="getStudentData(true)"> </el-button>
<el-button icon="Refresh" @click="resetStudentSearch"> </el-button>
</el-form-item>
</el-form>
<el-table :data="studentData" v-loading="studentLoading" stripe class="modern-table">
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="deptName" label="学院" min-width="150" show-overflow-tooltip />
<el-table-column prop="className" label="班级" min-width="150" show-overflow-tooltip />
<el-table-column prop="classMaster" label="班主任" width="100" align="center" />
<el-table-column prop="allNum" label="总人数" width="100" align="center">
<template #default="{ row }">
<el-tag type="info">{{ row.allNum }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="completed" label="已完成" width="100" align="center">
<template #default="{ row }">
<el-tag type="success">{{ row.completed }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="noCompleted" label="未完成" width="100" align="center">
<template #default="{ row }">
<el-tag type="danger">{{ row.noCompleted }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="completionRate" label="完成率" width="120" align="center">
<template #default="{ row }">
<el-progress :percentage="parseFloat(row.completionRate) || 0" :stroke-width="15" :text-inside="true" />
</template>
</el-table-column>
<template #empty>
<el-empty description="暂无数据" :image-size="120" />
</template>
</el-table>
<div class="pagination-wrapper">
<pagination @size-change="studentSizeChange" @current-change="studentCurrentChange" v-bind="studentPagination" />
</div>
</el-tab-pane>
<el-tab-pane label="教职工统计" name="teacher">
<!-- 搜索表单 -->
<el-form :model="teacherSearchForm" inline class="search-form">
<el-form-item label="学院">
<el-select v-model="teacherSearchForm.deptCode" placeholder="请选择学院" clearable style="width: 200px">
<el-option v-for="item in deptList" :key="item.deptCode" :label="item.deptName" :value="item.deptCode" />
</el-select>
</el-form-item>
<el-form-item>
<el-button icon="Search" type="primary" @click="getTeacherData(true)"> </el-button>
<el-button icon="Refresh" @click="resetTeacherSearch"> </el-button>
</el-form-item>
</el-form>
<el-table :data="teacherData" v-loading="teacherLoading" stripe class="modern-table">
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="deptName" label="学院" min-width="150" show-overflow-tooltip />
<el-table-column prop="realName" label="姓名" width="100" align="center" />
<el-table-column prop="loginName" label="账号" width="120" align="center" />
<el-table-column prop="allNum" label="总人数" width="100" align="center">
<template #default="{ row }">
<el-tag type="info">{{ row.allNum }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="completed" label="已完成" width="100" align="center">
<template #default="{ row }">
<el-tag type="success">{{ row.completed }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="noCompleted" label="未完成" width="100" align="center">
<template #default="{ row }">
<el-tag type="danger">{{ row.noCompleted }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="completionRate" label="完成率" width="120" align="center">
<template #default="{ row }">
<el-progress :percentage="parseFloat(row.completionRate) || 0" :stroke-width="15" :text-inside="true" />
</template>
</el-table-column>
<template #empty>
<el-empty description="暂无数据" :image-size="120" />
</template>
</el-table>
<div class="pagination-wrapper">
<pagination @size-change="teacherSizeChange" @current-change="teacherCurrentChange" v-bind="teacherPagination" />
</div>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</div>
</template>
<script setup lang="ts" name="DiningHallVoteStatistics">
import { ref, reactive, onMounted } from 'vue';
import { getStatisticsList, getStatisticsListByTea } from '/@/api/stuwork/dininghallvoteresultanalysis';
import { DataAnalysis } from '@element-plus/icons-vue';
const activeTab = ref('student');
const deptList = ref<any[]>([]);
// 学生统计
const searchForm = reactive({
deptCode: '',
});
const studentData = ref<any[]>([]);
const studentLoading = ref(false);
const studentPagination = reactive({
current: 1,
size: 10,
total: 0,
});
// 教职工统计
const teacherSearchForm = reactive({
deptCode: '',
});
const teacherData = ref<any[]>([]);
const teacherLoading = ref(false);
const teacherPagination = reactive({
current: 1,
size: 10,
total: 0,
});
const getStudentData = async (resetPage = false) => {
if (resetPage) {
studentPagination.current = 1;
}
studentLoading.value = true;
try {
const res = await getStatisticsList({
current: studentPagination.current,
size: studentPagination.size,
deptCode: searchForm.deptCode,
});
studentData.value = res.data?.records || [];
studentPagination.total = res.data?.total || 0;
} catch (error) {
console.error('获取学生统计数据失败', error);
} finally {
studentLoading.value = false;
}
};
const resetStudentSearch = () => {
searchForm.deptCode = '';
getStudentData(true);
};
const studentSizeChange = (size: number) => {
studentPagination.size = size;
getStudentData();
};
const studentCurrentChange = (current: number) => {
studentPagination.current = current;
getStudentData();
};
const getTeacherData = async (resetPage = false) => {
if (resetPage) {
teacherPagination.current = 1;
}
teacherLoading.value = true;
try {
const res = await getStatisticsListByTea({
current: teacherPagination.current,
size: teacherPagination.size,
deptCode: teacherSearchForm.deptCode,
});
teacherData.value = res.data || [];
teacherPagination.total = res.data?.length || 0;
} catch (error) {
console.error('获取教职工统计数据失败', error);
} finally {
teacherLoading.value = false;
}
};
const resetTeacherSearch = () => {
teacherSearchForm.deptCode = '';
getTeacherData(true);
};
const teacherSizeChange = (size: number) => {
teacherPagination.size = size;
getTeacherData();
};
const teacherCurrentChange = (current: number) => {
teacherPagination.current = current;
getTeacherData();
};
const handleTabChange = (tab: string) => {
if (tab === 'student' && studentData.value.length === 0) {
getStudentData();
} else if (tab === 'teacher' && teacherData.value.length === 0) {
getTeacherData();
}
};
onMounted(() => {
getStudentData();
});
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
</style>

View File

@@ -0,0 +1,164 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
食堂调查明细列表
</span>
<div class="header-actions">
<right-toolbar class="ml10" @queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</div>
</template>
<!-- 搜索表单 -->
<el-form :model="searchForm" inline class="search-form">
<el-form-item label="学年">
<el-input v-model="searchForm.year" placeholder="请输入学年" clearable style="width: 150px" />
</el-form-item>
<el-form-item label="学期">
<el-select v-model="searchForm.period" placeholder="请选择学期" clearable style="width: 120px">
<el-option label="第一学期" value="1" />
<el-option label="第二学期" value="2" />
</el-select>
</el-form-item>
<el-form-item>
<el-button icon="Search" type="primary" @click="getDataList(true)"> </el-button>
<el-button icon="Refresh" @click="resetSearch"> </el-button>
</el-form-item>
</el-form>
<el-table
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table"
>
<el-table-column type="index" label="序号" width="70" align="center">
<template #default="{ $index }">
{{ $index + 1 }}
</template>
</el-table-column>
<el-table-column prop="loginName" label="填写人账号" width="120" align="center" />
<el-table-column prop="year" label="学年" width="120" align="center" />
<el-table-column prop="period" label="学期" width="100" align="center">
<template #default="{ row }">
{{ row.period === '1' ? '第一学期' : row.period === '2' ? '第二学期' : '-' }}
</template>
</el-table-column>
<el-table-column prop="diningHallName" label="食堂名称" min-width="150" show-overflow-tooltip />
<el-table-column prop="diningHallVoteScore" label="评分" width="80" align="center">
<template #default="{ row }">
<el-tag type="success">{{ row.diningHallVoteScore }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="isUnderstand" label="是否了解" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.isUnderstand === 1 ? 'success' : 'info'">
{{ row.isUnderstand === 1 ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="isStu" label="身份" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.isStu === 1 ? 'primary' : 'warning'">
{{ row.isStu === 1 ? '学生' : '教职工' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="mostDissatisfied" label="最不满意食堂" min-width="150" show-overflow-tooltip />
<el-table-column prop="mostVist" label="最常去食堂" min-width="150" show-overflow-tooltip />
<el-table-column prop="mostDissatisfiedLayer" label="最不满意楼层" width="120" align="center" />
<el-table-column prop="mostDissatisfiedWindow" label="最不满意窗口" min-width="150" show-overflow-tooltip />
<el-table-column prop="mostVisitLayer" label="最常去楼层" width="120" align="center" />
<el-table-column prop="createDate" label="填写时间" width="160" align="center" />
<template #empty>
<el-empty description="暂无数据" :image-size="120" />
</template>
</el-table>
<div class="pagination-wrapper">
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
</el-card>
</div>
</div>
</template>
<script setup lang="ts" name="DiningHallVoteResult">
import { ref, reactive } from 'vue';
import { BasicTableProps, useTable } from '/@/hooks/table';
import { fetchList } from '/@/api/stuwork/dininghallvoteresult';
import TableColumnControl from '/@/components/TableColumnControl/index.vue';
import { Document, Menu } from '@element-plus/icons-vue';
import { useTableColumnControl } from '/@/hooks/tableColumn';
const columnControlRef = ref<any>();
const tableColumns = [
{ prop: 'loginName', label: '填写人账号' },
{ prop: 'year', label: '学年' },
{ prop: 'period', label: '学期' },
{ prop: 'diningHallName', label: '食堂名称' },
{ prop: 'diningHallVoteScore', label: '评分' },
{ prop: 'isUnderstand', label: '是否了解' },
{ prop: 'isStu', label: '身份' },
{ prop: 'mostDissatisfied', label: '最不满意食堂' },
{ prop: 'mostVist', label: '最常去食堂' },
{ prop: 'mostDissatisfiedLayer', label: '最不满意楼层' },
{ prop: 'mostDissatisfiedWindow', label: '最不满意窗口' },
{ prop: 'mostVisitLayer', label: '最常去楼层' },
{ prop: 'createDate', label: '填写时间' },
];
const { visibleColumns, visibleColumnsSorted, checkColumnVisible, handleColumnChange, handleColumnOrderChange } = useTableColumnControl(tableColumns);
const searchForm = reactive({
year: '',
period: '',
});
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: searchForm,
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total',
},
});
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state);
const resetSearch = () => {
searchForm.year = '';
searchForm.period = '';
getDataList(true);
};
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
</style>

View File

@@ -0,0 +1,283 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><DataAnalysis /></el-icon>
毕业调查问卷完成率-部门
</span>
</div>
</template>
<!-- 搜索表单 -->
<el-form :model="searchForm" inline class="search-form">
<el-form-item label="毕业年份">
<el-input v-model="searchForm.graduationYear" placeholder="请输入毕业年份" clearable style="width: 150px" />
</el-form-item>
<el-form-item label="学院">
<el-select v-model="searchForm.deptCode" placeholder="请选择学院" clearable style="width: 200px">
<el-option v-for="item in deptList" :key="item.deptCode" :label="item.deptName" :value="item.deptCode" />
</el-select>
</el-form-item>
<el-form-item label="是否联院">
<el-select v-model="searchForm.isUnion" placeholder="请选择" clearable style="width: 120px">
<el-option label="是" value="1" />
<el-option label="否" value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button icon="Search" type="primary" @click="getData(true)"> </el-button>
<el-button icon="Refresh" @click="resetSearch"> </el-button>
</el-form-item>
</el-form>
<el-table :data="dataList" v-loading="loading" stripe class="modern-table">
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="deptName" label="学院" min-width="150" show-overflow-tooltip />
<el-table-column prop="shouldFilled" label="应填人数" width="100" align="center">
<template #default="{ row }">
<el-tag type="info">{{ row.shouldFilled }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="hasFilled" label="已填人数" width="100" align="center">
<template #default="{ row }">
<el-tag type="success">{{ row.hasFilled }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="noFilled" label="未填人数" width="100" align="center">
<template #default="{ row }">
<el-tag type="danger">{{ row.noFilled }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="completionRate" label="完成率" width="150" align="center">
<template #default="{ row }">
<el-progress
:percentage="parseFloat(row.completionRate) || 0"
:stroke-width="15"
:text-inside="true"
:color="getProgressColor(parseFloat(row.completionRate) || 0)"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="scope">
<el-button icon="View" link type="primary" @click="handleViewClass(scope.row)"> 查看班级 </el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="暂无数据" :image-size="120" />
</template>
</el-table>
</el-card>
</div>
<!-- 班级详情对话框 -->
<el-dialog v-model="classDialogVisible" title="班级填写情况" :width="900" :close-on-click-modal="false" draggable>
<el-table :data="classData" v-loading="classLoading" stripe>
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="className" label="班级名称" min-width="150" show-overflow-tooltip />
<el-table-column prop="shouldFilled" label="应填人数" width="100" align="center">
<template #default="{ row }">
<el-tag type="info">{{ row.shouldFilled }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="hasFilled" label="已填人数" width="100" align="center">
<template #default="{ row }">
<el-tag type="success">{{ row.hasFilled }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="noFilled" label="未填人数" width="100" align="center">
<template #default="{ row }">
<el-tag type="danger">{{ row.noFilled }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="completionRate" label="完成率" width="150" align="center">
<template #default="{ row }">
<el-progress
:percentage="parseFloat(row.completionRate) || 0"
:stroke-width="15"
:text-inside="true"
:color="getProgressColor(parseFloat(row.completionRate) || 0)"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="100" align="center">
<template #default="scope">
<el-button icon="View" link type="primary" @click="handleViewStudent(scope.row)"> 查看学生 </el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
<!-- 学生详情对话框 -->
<el-dialog v-model="studentDialogVisible" title="学生填写情况" :width="1100" :close-on-click-modal="false" draggable>
<el-table :data="studentData" v-loading="studentLoading" stripe>
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="stuNo" label="学号" width="120" align="center" />
<el-table-column prop="realName" label="姓名" width="100" align="center" />
<el-table-column prop="className" label="班级" min-width="150" show-overflow-tooltip />
<el-table-column prop="majorName" label="专业" min-width="150" show-overflow-tooltip />
<el-table-column prop="phone" label="手机号" width="120" align="center" />
<el-table-column prop="isWrite" label="是否填写" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.isWrite === 1 ? 'success' : 'danger'">
{{ row.isWrite === 1 ? '已填写' : '未填写' }}
</el-tag>
</template>
</el-table-column>
</el-table>
<div class="pagination-wrapper" style="margin-top: 15px">
<pagination @size-change="studentSizeChange" @current-change="studentCurrentChange" v-bind="studentPagination" />
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="EmploymentInformationSurveyCompletionRateDept">
import { ref, reactive, onMounted } from 'vue';
import { getStatisticsDept, getStatisticsClass, getClassStudentInfo } from '/@/api/stuwork/employmentinformationsurvey';
import { DataAnalysis } from '@element-plus/icons-vue';
import { useMessage } from '/@/hooks/message';
const searchForm = reactive({
graduationYear: '',
deptCode: '',
isUnion: '',
});
const dataList = ref<any[]>([]);
const loading = ref(false);
const deptList = ref<any[]>([]);
// 班级详情
const classDialogVisible = ref(false);
const classData = ref<any[]>([]);
const classLoading = ref(false);
const currentDeptCode = ref('');
// 学生详情
const studentDialogVisible = ref(false);
const studentData = ref<any[]>([]);
const studentLoading = ref(false);
const currentClassNo = ref('');
const studentPagination = reactive({
current: 1,
size: 10,
total: 0,
});
const getData = async (resetPage = false) => {
loading.value = true;
try {
const res = await getStatisticsDept({
graduationYear: searchForm.graduationYear,
deptCode: searchForm.deptCode,
isUnion: searchForm.isUnion,
});
dataList.value = res.data || [];
} catch (error) {
console.error('获取数据失败', error);
} finally {
loading.value = false;
}
};
const resetSearch = () => {
searchForm.graduationYear = '';
searchForm.deptCode = '';
searchForm.isUnion = '';
getData(true);
};
const handleViewClass = async (row: any) => {
classDialogVisible.value = true;
classLoading.value = true;
currentDeptCode.value = row.deptCode;
try {
const res = await getStatisticsClass({
deptCode: row.deptCode,
graduationYear: searchForm.graduationYear,
});
classData.value = res.data || [];
} catch (error) {
console.error('获取班级数据失败', error);
} finally {
classLoading.value = false;
}
};
const handleViewStudent = async (row: any) => {
studentDialogVisible.value = true;
studentLoading.value = true;
currentClassNo.value = row.classNo || row.classCode;
studentPagination.current = 1;
try {
const res = await getClassStudentInfo({
classCode: row.classNo || row.classCode,
year: searchForm.graduationYear,
current: studentPagination.current,
size: studentPagination.size,
});
studentData.value = res.data?.list || [];
studentPagination.total = res.data?.list?.length || 0;
} catch (error) {
console.error('获取学生数据失败', error);
} finally {
studentLoading.value = false;
}
};
const studentSizeChange = async (size: number) => {
studentPagination.size = size;
studentDialogVisible.value = true;
studentLoading.value = true;
try {
const res = await getClassStudentInfo({
classCode: currentClassNo.value,
year: searchForm.graduationYear,
current: studentPagination.current,
size: studentPagination.size,
});
studentData.value = res.data?.list || [];
} catch (error) {
console.error('获取学生数据失败', error);
} finally {
studentLoading.value = false;
}
};
const studentCurrentChange = async (current: number) => {
studentPagination.current = current;
studentDialogVisible.value = true;
studentLoading.value = true;
try {
const res = await getClassStudentInfo({
classCode: currentClassNo.value,
year: searchForm.graduationYear,
current: studentPagination.current,
size: studentPagination.size,
});
studentData.value = res.data?.list || [];
} catch (error) {
console.error('获取学生数据失败', error);
} finally {
studentLoading.value = false;
}
};
const getProgressColor = (percentage: number) => {
if (percentage >= 80) return '#67c23a';
if (percentage >= 50) return '#e6a23c';
return '#f56c6c';
};
onMounted(() => {
getData();
});
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
</style>

View File

@@ -1,214 +1,437 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<!-- 筛选 -->
<el-card class="search-card" shadow="never">
<!-- 筛选条件 -->
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
统计条件
筛选条件
</span>
</div>
</template>
<el-form :model="queryForm" :inline="true" class="search-form">
<el-form :model="searchForm" ref="searchFormRef" :inline="true" class="search-form">
<el-form-item label="毕业年份" prop="graduYear">
<el-select v-model="queryForm.graduYear" placeholder="请选择毕业年份" clearable style="width: 160px">
<el-option v-for="y in graduYearOptions" :key="y" :label="y + '年'" :value="y" />
<el-select v-model="searchForm.graduYear" placeholder="请选择毕业年份" clearable filterable style="width: 160px" @change="handleSearch">
<el-option v-for="y in graduYearOptions" :key="y" :label="y + '年'" :value="String(y)" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="loadStatistics">查询统计</el-button>
<el-button type="primary" icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 汇总卡片 -->
<el-row :gutter="16" class="summary-row">
<el-col :span="6">
<!-- 统计卡片 -->
<el-row :gutter="20" class="stat-cards">
<el-col :xs="24" :sm="12" :md="6">
<el-card shadow="hover" class="stat-card">
<div class="stat-label">毕业生总数</div>
<div class="stat-value">{{ summary.total }}</div>
<div class="stat-content">
<div class="stat-icon" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)">
<el-icon><UserFilled /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ statistics.total }}</div>
<div class="stat-label">毕业生总数</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" class="stat-card stat-success">
<div class="stat-label">确认毕业</div>
<div class="stat-value">{{ summary.confirmed }}</div>
<el-col :xs="24" :sm="12" :md="6">
<el-card shadow="hover" class="stat-card">
<div class="stat-content">
<div class="stat-icon" style="background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%)">
<el-icon><CircleCheck /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ statistics.confirmed }}</div>
<div class="stat-label">确认毕业</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" class="stat-card stat-warning">
<div class="stat-label">待确认</div>
<div class="stat-value">{{ summary.pending }}</div>
<el-col :xs="24" :sm="12" :md="6">
<el-card shadow="hover" class="stat-card">
<div class="stat-content">
<div class="stat-icon" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%)">
<el-icon><Warning /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ statistics.pending }}</div>
<div class="stat-label">待确认</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" class="stat-card stat-danger">
<div class="stat-label">不可毕业</div>
<div class="stat-value">{{ summary.rejected }}</div>
<el-col :xs="24" :sm="12" :md="6">
<el-card shadow="hover" class="stat-card">
<div class="stat-content">
<div class="stat-icon" style="background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%)">
<el-icon><CircleClose /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ statistics.rejected }}</div>
<div class="stat-label">不可毕业</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 按学院统计表格 -->
<el-card class="content-card" shadow="never">
<!-- 图表区域 -->
<el-row :gutter="20" class="chart-row">
<el-col :xs="24" :lg="12">
<el-card shadow="never" class="chart-card">
<template #header>
<div class="card-header">
<span class="card-title">学院毕业人数分布</span>
</div>
</template>
<div ref="deptChartRef" class="chart-container"></div>
</el-card>
</el-col>
<el-col :xs="24" :lg="12">
<el-card shadow="never" class="chart-card">
<template #header>
<div class="card-header">
<span class="card-title">毕业类型分布</span>
</div>
</template>
<div ref="typeChartRef" class="chart-container"></div>
</el-card>
</el-col>
</el-row>
<!-- 详细数据表格 -->
<el-card shadow="never" class="table-card">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
按学院统计
</span>
<span class="card-title">学院毕业详情</span>
</div>
</template>
<el-table :data="deptStats" v-loading="loading" stripe border class="modern-table">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="deptName" label="学院" min-width="140" show-overflow-tooltip />
<el-table-column prop="total" label="应毕业人数" width="110" align="center" />
<el-table-column prop="pending" label="待确认" width="90" align="center" />
<el-table-column prop="confirmed" label="确认毕业" width="100" align="center" />
<el-table-column prop="rejected" label="不可毕业" width="100" align="center" />
<el-table-column prop="completionRate" label="完成率" width="100" align="center">
<el-table :data="deptDetailList" v-loading="loading" stripe border>
<el-table-column prop="deptName" label="学院" align="center" />
<el-table-column prop="total" label="毕业生总数" align="center" />
<el-table-column prop="confirmed" label="确认毕业" align="center">
<template #default="scope">
<span :class="completionRateClass(scope.row.completionRate)">
{{ scope.row.completionRate }}
</span>
<el-tag type="success" size="small">{{ scope.row.confirmed }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="pending" label="待确认" align="center">
<template #default="scope">
<el-tag type="warning" size="small">{{ scope.row.pending }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="rejected" label="不可毕业" align="center">
<template #default="scope">
<el-tag type="danger" size="small">{{ scope.row.rejected }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="rate" label="毕业率" align="center" width="120">
<template #default="scope">
<el-progress :percentage="scope.row.rate" :stroke-width="10" :text-inside="true" />
</template>
</el-table-column>
</el-table>
<template #empty>
<el-empty description="请选择毕业年份并点击「查询统计」" :image-size="100" />
</template>
</el-card>
</div>
</div>
</template>
<script setup lang="ts" name="GradustuAnalyse">
import { reactive, ref, computed, onMounted } from 'vue';
import { useMessage } from '/@/hooks/message';
import { ref, reactive, computed, onMounted, onUnmounted, nextTick } from 'vue';
import { Search, UserFilled, CircleCheck, CircleClose, Warning } from '@element-plus/icons-vue';
import { fetchListForAnalyse } from '/@/api/stuwork/gradustu';
import { Search, Document } from '@element-plus/icons-vue';
import * as echarts from 'echarts';
const queryForm = reactive({
graduYear: '',
const searchFormRef = ref();
const showSearch = ref(true);
const loading = ref(false);
const dataList = ref<any[]>([]);
const deptChartRef = ref<HTMLElement>();
const typeChartRef = ref<HTMLElement>();
let deptChart: echarts.ECharts | null = null;
let typeChart: echarts.ECharts | null = null;
// 搜索表单
const searchForm = reactive({
graduYear: String(new Date().getFullYear()),
});
const loading = ref(false);
const rawList = ref<any[]>([]);
// 毕业年份选项
const graduYearOptions = computed(() => {
const y = new Date().getFullYear();
return Array.from({ length: 11 }, (_, i) => y - 5 + i);
});
// 汇总:总人数、确认毕业、待确认、不可毕业
const summary = computed(() => {
const list = rawList.value;
let pending = 0;
let confirmed = 0;
let rejected = 0;
list.forEach((item: any) => {
const s = String(item.status ?? '');
if (s === '0') pending++;
else if (s === '1') confirmed++;
else if (s === '-1') rejected++;
});
return {
total: list.length,
pending,
confirmed,
rejected,
};
// 统计数据
const statistics = computed(() => {
const total = dataList.value.length;
const confirmed = dataList.value.filter((item) => item.status === '1').length;
const pending = dataList.value.filter((item) => item.status === '0').length;
const rejected = dataList.value.filter((item) => item.status === '-1').length;
return { total, confirmed, pending, rejected };
});
// 学院聚合
const deptStats = computed(() => {
const list = rawList.value;
const map: Record<string, { deptCode: string; deptName: string; total: number; pending: number; confirmed: number; rejected: number }> = {};
list.forEach((item: any) => {
const code = item.deptCode || '未知';
const name = item.deptName || item.deptCode || '未知';
if (!map[code]) {
map[code] = { deptCode: code, deptName: name, total: 0, pending: 0, confirmed: 0, rejected: 0 };
// 学院详细数据
const deptDetailList = computed(() => {
const deptMap = new Map<string, { deptName: string; total: number; confirmed: number; pending: number; rejected: number }>();
dataList.value.forEach((item) => {
const deptName = item.deptName || '未知学院';
if (!deptMap.has(deptName)) {
deptMap.set(deptName, { deptName, total: 0, confirmed: 0, pending: 0, rejected: 0 });
}
const row = map[code];
row.total++;
const s = String(item.status ?? '');
if (s === '0') row.pending++;
else if (s === '1') row.confirmed++;
else if (s === '-1') row.rejected++;
const dept = deptMap.get(deptName)!;
dept.total++;
if (item.status === '1') dept.confirmed++;
else if (item.status === '0') dept.pending++;
else if (item.status === '-1') dept.rejected++;
});
return Object.values(map).map((row) => ({
...row,
completionRate: row.total > 0 ? ((row.confirmed / row.total) * 100).toFixed(1) + '%' : '0%',
return Array.from(deptMap.values()).map((item) => ({
...item,
rate: item.total > 0 ? Math.round((item.confirmed / item.total) * 100) : 0,
}));
});
const completionRateClass = (rate: string) => {
const num = parseFloat(rate);
if (num >= 100) return 'rate-high';
if (num >= 80) return 'rate-mid';
return 'rate-low';
};
const loadStatistics = async () => {
if (!queryForm.graduYear) {
useMessage().warning('请选择毕业年份');
// 查询数据
const handleSearch = async () => {
if (!searchForm.graduYear) {
return;
}
loading.value = true;
try {
const list = await fetchListForAnalyse(queryForm.graduYear);
rawList.value = list;
} catch (err: any) {
useMessage().error(err.msg || '获取数据失败');
rawList.value = [];
const res = await fetchListForAnalyse(searchForm.graduYear);
dataList.value = res;
await nextTick();
renderCharts();
} catch (err) {
console.error('获取数据失败', err);
dataList.value = [];
} finally {
loading.value = false;
}
};
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields();
searchForm.graduYear = String(new Date().getFullYear());
handleSearch();
};
// 渲染图表
const renderCharts = () => {
renderDeptChart();
renderTypeChart();
};
// 渲染学院分布图表
const renderDeptChart = () => {
if (!deptChartRef.value) return;
if (deptChart) {
deptChart.dispose();
}
deptChart = echarts.init(deptChartRef.value);
const chartData = deptDetailList.value.map((item) => ({
name: item.deptName,
value: item.total,
}));
const option: echarts.EChartsOption = {
tooltip: {
trigger: 'item',
formatter: '{b}: {c}人 ({d}%)',
},
legend: {
orient: 'vertical',
left: 'left',
top: 'center',
},
series: [
{
name: '毕业人数',
type: 'pie',
radius: ['40%', '70%'],
center: ['60%', '50%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2,
},
label: {
show: false,
position: 'center',
},
emphasis: {
label: {
show: true,
fontSize: 16,
fontWeight: 'bold',
},
},
labelLine: {
show: false,
},
data: chartData,
},
],
};
deptChart.setOption(option);
};
// 渲染毕业类型图表
const renderTypeChart = () => {
if (!typeChartRef.value) return;
if (typeChart) {
typeChart.dispose();
}
typeChart = echarts.init(typeChartRef.value);
const typeCount = {
段段清: dataList.value.filter((item) => item.type === '1').length,
正常毕业: dataList.value.filter((item) => item.type === '2').length,
未知: dataList.value.filter((item) => item.type !== '1' && item.type !== '2').length,
};
const chartData = Object.entries(typeCount)
.filter(([_, value]) => value > 0)
.map(([name, value]) => ({ name, value }));
const option: echarts.EChartsOption = {
tooltip: {
trigger: 'item',
formatter: '{b}: {c}人 ({d}%)',
},
legend: {
orient: 'vertical',
left: 'left',
top: 'center',
},
series: [
{
name: '毕业类型',
type: 'pie',
radius: '60%',
center: ['60%', '50%'],
data: chartData,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
],
};
typeChart.setOption(option);
};
// 窗口大小变化时重绘图表
const handleResize = () => {
deptChart?.resize();
typeChart?.resize();
};
onMounted(() => {
queryForm.graduYear = String(new Date().getFullYear());
handleSearch();
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
deptChart?.dispose();
typeChart?.dispose();
});
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
.summary-row {
margin-bottom: 16px;
.stat-cards {
margin-bottom: 20px;
}
.stat-card {
text-align: center;
.stat-content {
display: flex;
align-items: center;
padding: 10px 0;
}
.stat-icon {
width: 60px;
height: 60px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16px;
.el-icon {
font-size: 28px;
color: #fff;
}
}
.stat-info {
flex: 1;
}
.stat-value {
font-size: 28px;
font-weight: 600;
color: #303133;
line-height: 1.2;
}
.stat-label {
font-size: 14px;
color: var(--el-text-color-secondary);
}
.stat-value {
font-size: 24px;
font-weight: 600;
margin-top: 8px;
}
&.stat-success .stat-value {
color: var(--el-color-success);
}
&.stat-warning .stat-value {
color: var(--el-color-warning);
}
&.stat-danger .stat-value {
color: var(--el-color-danger);
color: #909399;
margin-top: 4px;
}
}
.rate-high {
color: var(--el-color-success);
font-weight: 500;
.chart-row {
margin-bottom: 20px;
}
.rate-mid {
color: var(--el-color-warning);
.chart-card {
.card-header {
display: flex;
align-items: center;
}
.card-title {
font-size: 16px;
font-weight: 500;
}
}
.rate-low {
color: var(--el-color-danger);
.chart-container {
height: 350px;
}
</style>
.table-card {
.card-header {
display: flex;
align-items: center;
}
.card-title {
font-size: 16px;
font-weight: 500;
}
}
</style>

View File

@@ -0,0 +1,306 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<!-- 搜索表单卡片 -->
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch" class="search-form">
<el-form-item label="异动类型编码" prop="turnoverType">
<el-input
v-model="searchForm.turnoverType"
placeholder="请输入异动类型编码"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="异动类型名称" prop="turnoverTypeName">
<el-input
v-model="searchForm.turnoverTypeName"
placeholder="请输入异动类型名称"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="是否流失" prop="isLoss">
<el-select
v-model="searchForm.isLoss"
placeholder="请选择"
clearable
style="width: 200px">
<el-option label="是" value="1" />
<el-option label="否" value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 内容卡片 -->
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Setting /></el-icon>
异动流失配置列表
</span>
<div class="header-actions">
<el-button
icon="Plus"
type="primary"
@click="handleAdd">
新增配置
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10"
@queryTable="getDataList">
</right-toolbar>
</div>
</div>
</template>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table">
<el-table-column type="index" label="序号" width="70" align="center">
<template #default="{ $index }">
{{ $index + 1 + ((state.pagination?.current || 1) - 1) * (state.pagination?.size || 10) }}
</template>
</el-table-column>
<el-table-column prop="turnoverType" label="异动类型编码" align="center" show-overflow-tooltip />
<el-table-column prop="turnoverTypeName" label="异动类型名称" align="center" show-overflow-tooltip />
<el-table-column prop="isLoss" label="是否流失" align="center" width="120">
<template #default="scope">
<el-tag :type="scope.row.isLoss === '1' ? 'danger' : 'success'" size="small">
{{ scope.row.isLoss === '1' ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="remarks" label="备注" align="center" show-overflow-tooltip min-width="150" />
<el-table-column prop="createTime" label="创建时间" align="center" width="180">
<template #default="scope">
{{ scope.row.createTime || '-' }}
</template>
</el-table-column>
<el-table-column label="操作" width="160" align="center" fixed="right">
<template #default="scope">
<el-button
icon="Edit"
link
type="primary"
@click="handleEdit(scope.row)">
编辑
</el-button>
<el-button
icon="Delete"
link
type="danger"
@click="handleDelete(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-wrapper">
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
</el-card>
</div>
<!-- 新增/编辑弹窗 -->
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="500px"
:close-on-click-modal="false">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px">
<el-form-item label="异动类型编码" prop="turnoverType">
<el-input v-model="formData.turnoverType" placeholder="请输入异动类型编码" />
</el-form-item>
<el-form-item label="异动类型名称" prop="turnoverTypeName">
<el-input v-model="formData.turnoverTypeName" placeholder="请输入异动类型名称" />
</el-form-item>
<el-form-item label="是否流失" prop="isLoss">
<el-radio-group v-model="formData.isLoss">
<el-radio label="1"></el-radio>
<el-radio label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input v-model="formData.remarks" type="textarea" :rows="3" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="StuTurnoverLossConfig">
import { reactive, ref } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, addObj, editObj, delObj } from "/@/api/stuwork/stuturnoverlossconfig";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { Search, Setting, Plus, Edit, Delete } from '@element-plus/icons-vue'
// 定义变量
const searchFormRef = ref()
const formRef = ref()
const showSearch = ref(true)
const dialogVisible = ref(false)
const dialogTitle = ref('新增配置')
const submitLoading = ref(false)
const isEdit = ref(false)
// 搜索表单
const searchForm = reactive({
turnoverType: '',
turnoverTypeName: '',
isLoss: ''
})
// 表单数据
const formData = reactive({
id: '',
turnoverType: '',
turnoverTypeName: '',
isLoss: '0',
remarks: ''
})
// 表单验证规则
const formRules = {
turnoverType: [{ required: true, message: '请输入异动类型编码', trigger: 'blur' }],
turnoverTypeName: [{ required: true, message: '请输入异动类型名称', trigger: 'blur' }],
isLoss: [{ required: true, message: '请选择是否流失', trigger: 'change' }]
}
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: searchForm,
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state)
// 查询
const handleSearch = () => {
getDataList()
}
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
searchForm.turnoverType = ''
searchForm.turnoverTypeName = ''
searchForm.isLoss = ''
getDataList()
}
// 新增
const handleAdd = () => {
isEdit.value = false
dialogTitle.value = '新增配置'
resetForm()
dialogVisible.value = true
}
// 编辑
const handleEdit = (row: any) => {
isEdit.value = true
dialogTitle.value = '编辑配置'
resetForm()
formData.id = row.id
formData.turnoverType = row.turnoverType
formData.turnoverTypeName = row.turnoverTypeName
formData.isLoss = row.isLoss
formData.remarks = row.remarks || ''
dialogVisible.value = true
}
// 重置表单
const resetForm = () => {
formData.id = ''
formData.turnoverType = ''
formData.turnoverTypeName = ''
formData.isLoss = '0'
formData.remarks = ''
formRef.value?.resetFields()
}
// 提交
const handleSubmit = async () => {
try {
await formRef.value?.validate()
submitLoading.value = true
if (isEdit.value) {
await editObj(formData)
useMessage().success('修改成功')
} else {
await addObj(formData)
useMessage().success('新增成功')
}
dialogVisible.value = false
getDataList()
} catch (err: any) {
if (err?.msg) {
useMessage().error(err.msg)
}
} finally {
submitLoading.value = false
}
}
// 删除
const handleDelete = async (row: any) => {
try {
await useMessageBox().confirm('确定要删除该配置吗?')
await delObj([row.id])
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '删除失败')
}
}
}
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
</style>